]>
Commit | Line | Data |
---|---|---|
ab2538f5 WB |
1 | Ext.define('pmx-tfa-users', { |
2 | extend: 'Ext.data.Model', | |
3 | fields: ['userid'], | |
4 | idProperty: 'userid', | |
5 | proxy: { | |
6 | type: 'proxmox', | |
7 | url: '/api2/json/access/tfa', | |
8 | }, | |
9 | }); | |
10 | ||
11 | Ext.define('pmx-tfa-entry', { | |
12 | extend: 'Ext.data.Model', | |
13 | fields: ['fullid', 'userid', 'type', 'description', 'created', 'enable'], | |
14 | idProperty: 'fullid', | |
15 | }); | |
16 | ||
17 | ||
18 | Ext.define('Proxmox.panel.TfaView', { | |
19 | extend: 'Ext.grid.GridPanel', | |
20 | alias: 'widget.pmxTfaView', | |
20b39dd8 | 21 | mixins: ['Proxmox.Mixin.CBind'], |
ab2538f5 WB |
22 | |
23 | title: gettext('Second Factors'), | |
24 | reference: 'tfaview', | |
25 | ||
26 | issuerName: 'Proxmox', | |
20b39dd8 WB |
27 | yubicoEnabled: false, |
28 | ||
29 | cbindData: function(initialConfig) { | |
30 | let me = this; | |
31 | return { | |
32 | yubicoEnabled: me.yubicoEnabled, | |
33 | }; | |
34 | }, | |
ab2538f5 WB |
35 | |
36 | store: { | |
37 | type: 'diff', | |
38 | autoDestroy: true, | |
39 | autoDestroyRstore: true, | |
40 | model: 'pmx-tfa-entry', | |
41 | rstore: { | |
42 | type: 'store', | |
43 | proxy: 'memory', | |
44 | storeid: 'pmx-tfa-entry', | |
45 | model: 'pmx-tfa-entry', | |
46 | }, | |
47 | }, | |
48 | ||
49 | controller: { | |
50 | xclass: 'Ext.app.ViewController', | |
51 | ||
52 | init: function(view) { | |
53 | let me = this; | |
54 | view.tfaStore = Ext.create('Proxmox.data.UpdateStore', { | |
55 | autoStart: true, | |
56 | interval: 5 * 1000, | |
57 | storeid: 'pmx-tfa-users', | |
58 | model: 'pmx-tfa-users', | |
59 | }); | |
60 | view.tfaStore.on('load', this.onLoad, this); | |
61 | view.on('destroy', view.tfaStore.stopUpdate); | |
62 | Proxmox.Utils.monStoreErrors(view, view.tfaStore); | |
63 | }, | |
64 | ||
65 | reload: function() { this.getView().tfaStore.load(); }, | |
66 | ||
67 | onLoad: function(store, data, success) { | |
68 | if (!success) return; | |
69 | ||
70 | let records = []; | |
71 | Ext.Array.each(data, user => { | |
72 | Ext.Array.each(user.data.entries, entry => { | |
73 | records.push({ | |
74 | fullid: `${user.id}/${entry.id}`, | |
75 | userid: user.id, | |
76 | type: entry.type, | |
77 | description: entry.description, | |
78 | created: entry.created, | |
79 | enable: entry.enable, | |
80 | }); | |
81 | }); | |
82 | }); | |
83 | ||
84 | let rstore = this.getView().store.rstore; | |
85 | rstore.loadData(records); | |
86 | rstore.fireEvent('load', rstore, records, true); | |
87 | }, | |
88 | ||
89 | addTotp: function() { | |
90 | let me = this; | |
91 | ||
92 | Ext.create('Proxmox.window.AddTotp', { | |
93 | isCreate: true, | |
94 | issuerName: me.getView().issuerName, | |
95 | listeners: { | |
96 | destroy: function() { | |
97 | me.reload(); | |
98 | }, | |
99 | }, | |
100 | }).show(); | |
101 | }, | |
102 | ||
103 | addWebauthn: function() { | |
104 | let me = this; | |
105 | ||
106 | Ext.create('Proxmox.window.AddWebauthn', { | |
107 | isCreate: true, | |
108 | listeners: { | |
109 | destroy: function() { | |
110 | me.reload(); | |
111 | }, | |
112 | }, | |
113 | }).show(); | |
114 | }, | |
115 | ||
116 | addRecovery: async function() { | |
117 | let me = this; | |
118 | ||
119 | Ext.create('Proxmox.window.AddTfaRecovery', { | |
120 | listeners: { | |
121 | destroy: function() { | |
122 | me.reload(); | |
123 | }, | |
124 | }, | |
125 | }).show(); | |
126 | }, | |
127 | ||
20b39dd8 WB |
128 | addYubico: function() { |
129 | let me = this; | |
130 | ||
131 | Ext.create('Proxmox.window.AddYubico', { | |
132 | isCreate: true, | |
133 | listeners: { | |
134 | destroy: function() { | |
135 | me.reload(); | |
136 | }, | |
137 | }, | |
138 | }).show(); | |
139 | }, | |
140 | ||
ab2538f5 WB |
141 | editItem: function() { |
142 | let me = this; | |
143 | let view = me.getView(); | |
144 | let selection = view.getSelection(); | |
145 | if (selection.length !== 1 || selection[0].id.endsWith("/recovery")) { | |
146 | return; | |
147 | } | |
148 | ||
149 | Ext.create('Proxmox.window.TfaEdit', { | |
150 | 'tfa-id': selection[0].data.fullid, | |
151 | listeners: { | |
152 | destroy: function() { | |
153 | me.reload(); | |
154 | }, | |
155 | }, | |
156 | }).show(); | |
157 | }, | |
158 | ||
159 | renderUser: fullid => fullid.split('/')[0], | |
160 | ||
161 | renderEnabled: enabled => { | |
162 | if (enabled === undefined) { | |
163 | return Proxmox.Utils.yesText; | |
164 | } else { | |
165 | return Proxmox.Utils.format_boolean(enabled); | |
166 | } | |
167 | }, | |
168 | ||
169 | onRemoveButton: function(btn, event, record) { | |
170 | let me = this; | |
171 | ||
172 | Ext.create('Proxmox.tfa.confirmRemove', { | |
173 | ...record.data, | |
174 | callback: password => me.removeItem(password, record), | |
175 | }) | |
176 | .show(); | |
177 | }, | |
178 | ||
179 | removeItem: async function(password, record) { | |
180 | let me = this; | |
181 | ||
182 | if (password !== null) { | |
183 | password = '?password=' + encodeURIComponent(password); | |
184 | } else { | |
185 | password = ''; | |
186 | } | |
187 | ||
188 | try { | |
189 | me.getView().mask(gettext('Please wait...'), 'x-mask-loading'); | |
190 | await Proxmox.Async.api2({ | |
191 | url: `/api2/extjs/access/tfa/${record.id}${password}`, | |
192 | method: 'DELETE', | |
193 | }); | |
194 | me.reload(); | |
195 | } catch (response) { | |
196 | Ext.Msg.alert(gettext('Error'), response.result.message); | |
197 | } finally { | |
198 | me.getView().unmask(); | |
199 | } | |
200 | }, | |
201 | }, | |
202 | ||
203 | viewConfig: { | |
204 | trackOver: false, | |
205 | }, | |
206 | ||
207 | listeners: { | |
208 | itemdblclick: 'editItem', | |
209 | }, | |
210 | ||
211 | columns: [ | |
212 | { | |
213 | header: gettext('User'), | |
214 | width: 200, | |
215 | sortable: true, | |
216 | dataIndex: 'fullid', | |
217 | renderer: 'renderUser', | |
218 | }, | |
219 | { | |
220 | header: gettext('Enabled'), | |
221 | width: 80, | |
222 | sortable: true, | |
223 | dataIndex: 'enable', | |
224 | renderer: 'renderEnabled', | |
225 | }, | |
226 | { | |
227 | header: gettext('TFA Type'), | |
228 | width: 80, | |
229 | sortable: true, | |
230 | dataIndex: 'type', | |
231 | }, | |
232 | { | |
233 | header: gettext('Created'), | |
234 | width: 150, | |
235 | sortable: true, | |
236 | dataIndex: 'created', | |
65c39bc0 | 237 | renderer: t => !t ? 'N/A' : Proxmox.Utils.render_timestamp(t), |
ab2538f5 WB |
238 | }, |
239 | { | |
240 | header: gettext('Description'), | |
241 | width: 300, | |
242 | sortable: true, | |
243 | dataIndex: 'description', | |
244 | renderer: Ext.String.htmlEncode, | |
245 | flex: 1, | |
246 | }, | |
247 | ], | |
248 | ||
249 | tbar: [ | |
250 | { | |
251 | text: gettext('Add'), | |
20b39dd8 | 252 | cbind: {}, |
ab2538f5 WB |
253 | menu: { |
254 | xtype: 'menu', | |
255 | items: [ | |
256 | { | |
257 | text: gettext('TOTP'), | |
258 | itemId: 'totp', | |
259 | iconCls: 'fa fa-fw fa-clock-o', | |
260 | handler: 'addTotp', | |
261 | }, | |
262 | { | |
263 | text: gettext('Webauthn'), | |
264 | itemId: 'webauthn', | |
265 | iconCls: 'fa fa-fw fa-shield', | |
266 | handler: 'addWebauthn', | |
267 | }, | |
268 | { | |
269 | text: gettext('Recovery Keys'), | |
270 | itemId: 'recovery', | |
271 | iconCls: 'fa fa-fw fa-file-text-o', | |
272 | handler: 'addRecovery', | |
273 | }, | |
20b39dd8 WB |
274 | { |
275 | text: gettext('Yubico'), | |
276 | itemId: 'yubico', | |
277 | iconCls: 'fa fa-fw fa-yahoo', | |
278 | handler: 'addYubico', | |
279 | cbind: { | |
280 | hidden: '{!yubicoEnabled}', | |
281 | }, | |
282 | }, | |
ab2538f5 WB |
283 | ], |
284 | }, | |
285 | }, | |
286 | '-', | |
287 | { | |
288 | xtype: 'proxmoxButton', | |
289 | text: gettext('Edit'), | |
290 | handler: 'editItem', | |
291 | enableFn: rec => !rec.id.endsWith("/recovery"), | |
292 | disabled: true, | |
293 | }, | |
294 | { | |
295 | xtype: 'proxmoxButton', | |
296 | disabled: true, | |
297 | text: gettext('Remove'), | |
298 | getRecordName: rec => rec.data.description, | |
299 | handler: 'onRemoveButton', | |
300 | }, | |
301 | ], | |
302 | }); |