]> git.proxmox.com Git - proxmox-backup.git/blob - www/NavigationTree.js
ui: Show if Filter includes or excludes
[proxmox-backup.git] / www / NavigationTree.js
1 Ext.define('pbs-datastore-list', {
2 extend: 'Ext.data.Model',
3 fields: ['name', 'comment', 'maintenance'],
4 proxy: {
5 type: 'proxmox',
6 url: "/api2/json/admin/datastore",
7 },
8 idProperty: 'store',
9 });
10
11 Ext.define('pbs-tape-drive-list', {
12 extend: 'Ext.data.Model',
13 fields: ['name', 'changer'],
14 proxy: {
15 type: 'proxmox',
16 url: "/api2/json/tape/drive",
17 },
18 idProperty: 'name',
19 });
20
21 Ext.define('PBS.store.NavigationStore', {
22 extend: 'Ext.data.TreeStore',
23
24 storeId: 'NavigationStore',
25
26 root: {
27 expanded: true,
28 children: [
29 {
30 text: gettext('Dashboard'),
31 iconCls: 'fa fa-tachometer',
32 path: 'pbsDashboard',
33 leaf: true,
34 },
35 {
36 text: gettext('Notes'),
37 iconCls: 'fa fa-sticky-note-o',
38 path: 'pbsNodeNotes',
39 leaf: true,
40 },
41 {
42 text: gettext('Configuration'),
43 iconCls: 'fa fa-gears',
44 path: 'pbsSystemConfiguration',
45 expanded: true,
46 children: [
47 {
48 text: gettext('Access Control'),
49 iconCls: 'fa fa-key',
50 path: 'pbsAccessControlPanel',
51 leaf: true,
52 },
53 {
54 text: gettext('Remotes'),
55 iconCls: 'fa fa-server',
56 path: 'pbsRemoteView',
57 leaf: true,
58 },
59 {
60 text: gettext('Traffic Control'),
61 iconCls: 'fa fa-signal fa-rotate-90',
62 path: 'pbsTrafficControlView',
63 leaf: true,
64 },
65 {
66 text: gettext('Certificates'),
67 iconCls: 'fa fa-certificate',
68 path: 'pbsCertificateConfiguration',
69 leaf: true,
70 },
71 {
72 text: gettext('Subscription'),
73 iconCls: 'fa fa-support',
74 path: 'pbsSubscription',
75 leaf: true,
76 },
77 ],
78 },
79 {
80 text: gettext('Administration'),
81 iconCls: 'fa fa-wrench',
82 path: 'pbsServerAdministration',
83 expanded: true,
84 leaf: false,
85 children: [
86 {
87 text: gettext('Shell'),
88 iconCls: 'fa fa-terminal',
89 path: 'pbsXtermJsConsole',
90 leaf: true,
91 },
92 {
93 text: gettext('Storage / Disks'),
94 iconCls: 'fa fa-hdd-o',
95 path: 'pbsStorageAndDiskPanel',
96 leaf: true,
97 },
98 ],
99 },
100 {
101 text: "Tape Backup",
102 iconCls: 'pbs-icon-tape',
103 id: 'tape_management',
104 path: 'pbsTapeManagement',
105 expanded: true,
106 children: [],
107 },
108 {
109 text: gettext('Datastore'),
110 iconCls: 'fa fa-archive',
111 id: 'datastores',
112 path: 'pbsDataStores',
113 expanded: true,
114 expandable: false,
115 leaf: false,
116 children: [
117 {
118 text: gettext('Add Datastore'),
119 iconCls: 'fa fa-plus-circle',
120 leaf: true,
121 id: 'addbutton',
122 virtualEntry: true,
123 },
124 ],
125 },
126 ],
127 },
128 });
129
130 Ext.define('CustomTreeListItem', {
131 extend: 'Ext.list.TreeItem',
132 xtype: 'qtiptreelistitem',
133
134 nodeUpdate: function(node, modifiedFieldNames) {
135 this.callParent(arguments);
136 const qtip = node ? node.get('qtip') : null;
137 if (qtip) {
138 this.element.dom.setAttribute('data-qtip', qtip);
139 } else {
140 this.element.dom.removeAttribute('data-qtip');
141 }
142 },
143 });
144
145 Ext.define('PBS.view.main.NavigationTree', {
146 extend: 'Ext.list.Tree',
147 xtype: 'navigationtree',
148
149 animation: false,
150 expanderOnly: true,
151 expanderFirst: false,
152 store: 'NavigationStore',
153 ui: 'nav',
154
155 defaults: {
156 xtype: 'qtiptreelistitem',
157 },
158
159 controller: {
160 xclass: 'Ext.app.ViewController',
161
162 init: function(view) {
163 view.rstore = Ext.create('Proxmox.data.UpdateStore', {
164 autoStart: true,
165 interval: 15 * 1000,
166 storeId: 'pbs-datastore-list', // NOTE: this is queried by selectors, avoid change!
167 model: 'pbs-datastore-list',
168 });
169
170 view.rstore.on('load', this.onLoad, this);
171 view.on('destroy', view.rstore.stopUpdate);
172
173 if (view.tapeStore === undefined) {
174 view.tapeStore = Ext.create('Proxmox.data.UpdateStore', {
175 autoStart: true,
176 interval: 60 * 1000,
177 storeid: 'pbs-tape-drive-list',
178 model: 'pbs-tape-drive-list',
179 });
180 view.tapeStore.on('load', this.onTapeDriveLoad, this);
181 view.on('destroy', view.tapeStore.stopUpdate);
182 }
183 },
184
185 onTapeDriveLoad: function(store, records, success) {
186 if (!success) return;
187
188 let view = this.getView();
189 let root = view.getStore().getRoot();
190
191 records.sort((a, b) => a.data.name.localeCompare(b.data.name));
192
193 let list = root.findChild('id', 'tape_management', false);
194 let existingChildren = {};
195 for (const drive of records) {
196 let path, text, iconCls;
197 if (drive.data.changer !== undefined) {
198 text = drive.data.changer;
199 path = `Changer-${text}`;
200 iconCls = 'fa fa-exchange';
201 } else {
202 text = drive.data.name;
203 path = `Drive-${text}`;
204 iconCls = 'pbs-icon-tape-drive';
205 }
206 existingChildren[path] = {
207 text,
208 path,
209 iconCls,
210 leaf: true,
211 };
212 }
213
214 let paths = Object.keys(existingChildren).sort();
215
216 let oldIdx = 0;
217 for (let newIdx = 0; newIdx < paths.length; newIdx++) {
218 let newPath = paths[newIdx];
219 // find index to insert
220 while (oldIdx < list.childNodes.length && newPath > list.getChildAt(oldIdx).data.path) {
221 oldIdx++;
222 }
223
224 if (oldIdx >= list.childNodes.length || list.getChildAt(oldIdx).data.path !== newPath) {
225 list.insertChild(oldIdx, existingChildren[newPath]);
226 }
227 }
228
229 let toRemove = [];
230 list.eachChild((child) => {
231 if (!existingChildren[child.data.path]) {
232 toRemove.push(child);
233 }
234 });
235 toRemove.forEach((child) => list.removeChild(child, true));
236
237 if (view.pathToSelect !== undefined) {
238 let path = view.pathToSelect;
239 delete view.pathToSelect;
240 view.select(path, true);
241 }
242 },
243
244 onLoad: function(store, records, success) {
245 if (!success) {
246 return;
247 }
248 let view = this.getView();
249 let root = view.getStore().getRoot();
250
251 records.sort((a, b) => a.id.localeCompare(b.id));
252
253 let list = root.findChild('id', 'datastores', false);
254 let getChildTextAt = i => list.getChildAt(i).data.text;
255 let existingChildren = {};
256 for (let i = 0, j = 0, length = records.length; i < length; i++) {
257 let name = records[i].id;
258 existingChildren[name] = true;
259
260 while (name.localeCompare(getChildTextAt(j)) > 0 && (j+1) < list.childNodes.length) {
261 j++;
262 }
263
264 let [qtip, iconCls] = ['', 'fa fa-database'];
265 const maintenance = records[i].data.maintenance;
266 if (maintenance) {
267 const [type, message] = PBS.Utils.parseMaintenanceMode(maintenance);
268 qtip = `${type}${message ? ': ' + message : ''}`;
269 let mainenanceTypeCls = type === 'delete' ? 'destroying' : 'maintenance';
270 iconCls = `fa fa-database pmx-tree-icon-custom ${mainenanceTypeCls}`;
271 }
272
273 if (getChildTextAt(j).localeCompare(name) !== 0) {
274 list.insertChild(j, {
275 text: name,
276 qtip,
277 path: `DataStore-${name}`,
278 iconCls,
279 leaf: true,
280 });
281 } else {
282 let oldChild = list.getChildAt(j);
283 oldChild.set('qtip', qtip);
284 oldChild.set('iconCls', iconCls);
285 }
286 }
287
288 // remove entries which are not existing anymore
289 let toRemove = [];
290 list.eachChild(child => {
291 if (!existingChildren[child.data.text] && !child.data.virtualEntry) {
292 toRemove.push(child);
293 }
294 });
295 toRemove.forEach(child => list.removeChild(child, true));
296
297 if (view.pathToSelect !== undefined) {
298 let path = view.pathToSelect;
299 delete view.pathToSelect;
300 view.select(path, true);
301 }
302 },
303 },
304
305 listeners: {
306 itemclick: function(tl, info) {
307 if (info.node.data.id === 'addbutton') {
308 let me = this;
309 Ext.create('PBS.DataStoreEdit', {
310 listeners: {
311 destroy: () => me.rstore.reload(),
312 },
313 }).show();
314 return false;
315 }
316 return true;
317 },
318 },
319
320 reloadTapeStore: function() {
321 let me = this;
322 me.tapeStore.load();
323 },
324
325 select: function(path, silent) {
326 var me = this;
327 if (me.rstore.isLoaded() && me.tapeStore.isLoaded()) {
328 if (silent) {
329 me.suspendEvents(false);
330 }
331 var item = me.getStore().findRecord('path', path, 0, false, true, true);
332 me.setSelection(item);
333 if (silent) {
334 me.resumeEvents(true);
335 }
336 } else {
337 me.pathToSelect = path;
338 }
339 },
340 });