]> git.proxmox.com Git - proxmox-widget-toolkit.git/blame - src/panel/TfaView.js
tfa view: avoid showing start of unix epoch when no creation date
[proxmox-widget-toolkit.git] / src / panel / TfaView.js
CommitLineData
ab2538f5
WB
1Ext.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
11Ext.define('pmx-tfa-entry', {
12 extend: 'Ext.data.Model',
13 fields: ['fullid', 'userid', 'type', 'description', 'created', 'enable'],
14 idProperty: 'fullid',
15});
16
17
18Ext.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});