]>
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, | |
82a38653 | 108 | autoShow: true, |
ab2538f5 | 109 | listeners: { |
82a38653 | 110 | destroy: () => me.reload(), |
ab2538f5 | 111 | }, |
82a38653 | 112 | }); |
ab2538f5 WB |
113 | }, |
114 | ||
115 | addRecovery: async function() { | |
116 | let me = this; | |
117 | ||
118 | Ext.create('Proxmox.window.AddTfaRecovery', { | |
82a38653 | 119 | autoShow: true, |
ab2538f5 | 120 | listeners: { |
82a38653 | 121 | destroy: () => me.reload(), |
ab2538f5 | 122 | }, |
82a38653 | 123 | }); |
ab2538f5 WB |
124 | }, |
125 | ||
20b39dd8 WB |
126 | addYubico: function() { |
127 | let me = this; | |
128 | ||
129 | Ext.create('Proxmox.window.AddYubico', { | |
130 | isCreate: true, | |
82a38653 | 131 | autoShow: true, |
20b39dd8 | 132 | listeners: { |
82a38653 | 133 | destroy: () => me.reload(), |
20b39dd8 | 134 | }, |
82a38653 | 135 | }); |
20b39dd8 WB |
136 | }, |
137 | ||
ab2538f5 WB |
138 | editItem: function() { |
139 | let me = this; | |
140 | let view = me.getView(); | |
141 | let selection = view.getSelection(); | |
142 | if (selection.length !== 1 || selection[0].id.endsWith("/recovery")) { | |
143 | return; | |
144 | } | |
145 | ||
146 | Ext.create('Proxmox.window.TfaEdit', { | |
147 | 'tfa-id': selection[0].data.fullid, | |
82a38653 | 148 | autoShow: true, |
ab2538f5 | 149 | listeners: { |
82a38653 | 150 | destroy: () => me.reload(), |
ab2538f5 | 151 | }, |
82a38653 | 152 | }); |
ab2538f5 WB |
153 | }, |
154 | ||
155 | renderUser: fullid => fullid.split('/')[0], | |
156 | ||
157 | renderEnabled: enabled => { | |
158 | if (enabled === undefined) { | |
159 | return Proxmox.Utils.yesText; | |
160 | } else { | |
161 | return Proxmox.Utils.format_boolean(enabled); | |
162 | } | |
163 | }, | |
164 | ||
165 | onRemoveButton: function(btn, event, record) { | |
166 | let me = this; | |
167 | ||
168 | Ext.create('Proxmox.tfa.confirmRemove', { | |
169 | ...record.data, | |
170 | callback: password => me.removeItem(password, record), | |
82a38653 TL |
171 | autoShow: true, |
172 | }); | |
ab2538f5 WB |
173 | }, |
174 | ||
175 | removeItem: async function(password, record) { | |
176 | let me = this; | |
177 | ||
178 | if (password !== null) { | |
179 | password = '?password=' + encodeURIComponent(password); | |
180 | } else { | |
181 | password = ''; | |
182 | } | |
183 | ||
184 | try { | |
185 | me.getView().mask(gettext('Please wait...'), 'x-mask-loading'); | |
186 | await Proxmox.Async.api2({ | |
187 | url: `/api2/extjs/access/tfa/${record.id}${password}`, | |
188 | method: 'DELETE', | |
189 | }); | |
190 | me.reload(); | |
191 | } catch (response) { | |
192 | Ext.Msg.alert(gettext('Error'), response.result.message); | |
193 | } finally { | |
194 | me.getView().unmask(); | |
195 | } | |
196 | }, | |
197 | }, | |
198 | ||
199 | viewConfig: { | |
200 | trackOver: false, | |
201 | }, | |
202 | ||
203 | listeners: { | |
204 | itemdblclick: 'editItem', | |
205 | }, | |
206 | ||
207 | columns: [ | |
208 | { | |
209 | header: gettext('User'), | |
210 | width: 200, | |
211 | sortable: true, | |
212 | dataIndex: 'fullid', | |
213 | renderer: 'renderUser', | |
214 | }, | |
215 | { | |
216 | header: gettext('Enabled'), | |
217 | width: 80, | |
218 | sortable: true, | |
219 | dataIndex: 'enable', | |
220 | renderer: 'renderEnabled', | |
221 | }, | |
222 | { | |
223 | header: gettext('TFA Type'), | |
224 | width: 80, | |
225 | sortable: true, | |
226 | dataIndex: 'type', | |
227 | }, | |
228 | { | |
229 | header: gettext('Created'), | |
230 | width: 150, | |
231 | sortable: true, | |
232 | dataIndex: 'created', | |
65c39bc0 | 233 | renderer: t => !t ? 'N/A' : Proxmox.Utils.render_timestamp(t), |
ab2538f5 WB |
234 | }, |
235 | { | |
236 | header: gettext('Description'), | |
237 | width: 300, | |
238 | sortable: true, | |
239 | dataIndex: 'description', | |
240 | renderer: Ext.String.htmlEncode, | |
241 | flex: 1, | |
242 | }, | |
243 | ], | |
244 | ||
245 | tbar: [ | |
246 | { | |
247 | text: gettext('Add'), | |
20b39dd8 | 248 | cbind: {}, |
ab2538f5 WB |
249 | menu: { |
250 | xtype: 'menu', | |
251 | items: [ | |
252 | { | |
253 | text: gettext('TOTP'), | |
254 | itemId: 'totp', | |
255 | iconCls: 'fa fa-fw fa-clock-o', | |
256 | handler: 'addTotp', | |
257 | }, | |
258 | { | |
bfc6233d | 259 | text: gettext('WebAuthn'), |
ab2538f5 WB |
260 | itemId: 'webauthn', |
261 | iconCls: 'fa fa-fw fa-shield', | |
262 | handler: 'addWebauthn', | |
263 | }, | |
264 | { | |
265 | text: gettext('Recovery Keys'), | |
266 | itemId: 'recovery', | |
267 | iconCls: 'fa fa-fw fa-file-text-o', | |
268 | handler: 'addRecovery', | |
269 | }, | |
20b39dd8 | 270 | { |
05da27ed | 271 | text: gettext('Yubico OTP'), |
20b39dd8 | 272 | itemId: 'yubico', |
05da27ed | 273 | iconCls: 'fa fa-fw fa-yahoo', // close enough |
20b39dd8 WB |
274 | handler: 'addYubico', |
275 | cbind: { | |
276 | hidden: '{!yubicoEnabled}', | |
277 | }, | |
278 | }, | |
ab2538f5 WB |
279 | ], |
280 | }, | |
281 | }, | |
282 | '-', | |
283 | { | |
284 | xtype: 'proxmoxButton', | |
285 | text: gettext('Edit'), | |
286 | handler: 'editItem', | |
287 | enableFn: rec => !rec.id.endsWith("/recovery"), | |
288 | disabled: true, | |
289 | }, | |
290 | { | |
291 | xtype: 'proxmoxButton', | |
292 | disabled: true, | |
293 | text: gettext('Remove'), | |
294 | getRecordName: rec => rec.data.description, | |
295 | handler: 'onRemoveButton', | |
296 | }, | |
297 | ], | |
298 | }); |