]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/storage/PBSEdit.js
2 Ext
.define('PVE.Storage.PBSKeyShow', {
3 extend
: 'Ext.window.Window',
4 xtype
: 'pvePBSKeyShow',
5 mixins
: ['Proxmox.Mixin.CBind'],
10 title
: gettext('Important: Save your Encryption Key'),
12 // avoid that esc closes this by mistake, force user to more manual action
33 fieldLabel
: gettext('Key'),
35 inputId
: 'encryption-key-value',
43 html
: gettext('Keep your encryption key safe, but easily accessible for disaster recovery.')
44 + '<br>' + gettext('We recommend the following safe-keeping strategy:'),
52 html
: '1. ' + gettext('Save the key in your password manager.'),
57 text
: gettext('Copy Key'),
58 iconCls
: 'fa fa-clipboard x-btn-icon-el-default-toolbar-small',
59 cls
: 'x-btn-default-toolbar-small proxmox-inline-button',
61 handler: function(b
) {
62 document
.getElementById('encryption-key-value').select();
63 document
.execCommand("copy");
74 html
: '2. ' + gettext('Download the key to a USB (pen) drive, placed in secure vault.'),
79 text
: gettext('Download'),
80 iconCls
: 'fa fa-download x-btn-icon-el-default-toolbar-small',
81 cls
: 'x-btn-default-toolbar-small proxmox-inline-button',
83 handler: function(b
) {
84 let win
= this.up('window');
86 let pveID
= PVE
.ClusterName
|| window
.location
.hostname
;
87 let name
= `pve-${pveID}-storage-${win.sid}.enc`;
89 let hiddenElement
= document
.createElement('a');
90 hiddenElement
.href
= 'data:attachment/text,' + encodeURI(win
.key
);
91 hiddenElement
.target
= '_blank';
92 hiddenElement
.download
= name
;
93 hiddenElement
.click();
104 html
: '3. ' + gettext('Print as paperkey, laminated and placed in secure vault.'),
109 text
: gettext('Print Key'),
110 iconCls
: 'fa fa-print x-btn-icon-el-default-toolbar-small',
111 cls
: 'x-btn-default-toolbar-small proxmox-inline-button',
113 handler: function(b
) {
114 let win
= this.up('window');
115 win
.paperkey(win
.key
);
125 padding
: '10 10 10 10',
127 html
: gettext('Please save the encryption key - losing it will render any backup created with it unusable'),
132 text
: gettext('Close'),
133 handler: function(b
) {
134 let win
= this.up('window');
139 paperkey: function(keyString
) {
142 const key
= JSON
.parse(keyString
);
145 let qrdiv
= document
.createElement('div');
146 let qrcode
= new QRCode(qrdiv
, {
149 correctLevel
: QRCode
.CorrectLevel
.H
,
151 qrcode
.makeCode(keyString
);
154 if (key
.fingerprint
) {
155 shortKeyFP
= PVE
.Utils
.render_pbs_fingerprint(key
.fingerprint
);
158 let printFrame
= document
.createElement("iframe");
159 Object
.assign(printFrame
.style
, {
167 const prettifiedKey
= JSON
.stringify(key
, null, 2);
168 const keyQrBase64
= qrdiv
.children
[0].toDataURL("image/png");
169 const html
= `<html><head><script>
170 window.addEventListener('DOMContentLoaded', (ev) => window.print());
171 </script><style>@media print and (max-height: 150mm) {
172 h4, p { margin: 0; font-size: 1em; }
173 }</style></head><body style="padding: 5px;">
174 <h4>Encryption Key - Storage '${me.sid}' (${shortKeyFP})</h4>
175 <p style="font-size:1.2em;font-family:monospace;white-space:pre-wrap;overflow-wrap:break-word;">
176 -----BEGIN PROXMOX BACKUP KEY-----
178 -----END PROXMOX BACKUP KEY-----</p>
179 <center><img style="width: 100%; max-width: ${qrwidth}px;" src="${keyQrBase64}"></center>
182 printFrame
.src
= "data:text/html;base64," + btoa(html
);
183 document
.body
.appendChild(printFrame
);
184 me
.on('destroy', () => document
.body
.removeChild(printFrame
));
188 Ext
.define('PVE.panel.PBSEncryptionKeyTab', {
189 extend
: 'Proxmox.panel.InputPanel',
190 xtype
: 'pvePBSEncryptionKeyTab',
191 mixins
: ['Proxmox.Mixin.CBind'],
193 onlineHelp
: 'storage_pbs_encryption',
195 onGetValues: function(form
) {
197 if (form
.cryptMode
=== 'upload') {
198 values
['encryption-key'] = form
['crypt-key-upload'];
199 } else if (form
.cryptMode
=== 'autogenerate') {
200 values
['encryption-key'] = 'autogen';
201 } else if (form
.cryptMode
=== 'none') {
202 if (!this.isCreate
) {
203 values
.delete = ['encryption-key'];
209 setValues: function(values
) {
211 let vm
= me
.getViewModel();
213 let cryptKeyInfo
= values
['encryption-key'];
215 let icon
= '<span class="fa fa-lock good"></span> ';
216 if (cryptKeyInfo
.match(/^[a-fA-F0-9]{2}:/)) { // new style fingerprint
217 let shortKeyFP
= PVE
.Utils
.render_pbs_fingerprint(cryptKeyInfo
);
218 values
['crypt-key-fp'] = icon
+ `${gettext('Active')} - ${gettext('Fingerprint')} ${shortKeyFP}`;
220 // old key without FP
221 values
['crypt-key-fp'] = icon
+ gettext('Active');
224 values
['crypt-key-fp'] = gettext('None');
225 let cryptModeNone
= me
.down('radiofield[inputValue=none]');
226 cryptModeNone
.setBoxLabel(gettext('Do not encrypt backups'));
227 cryptModeNone
.setValue(true);
229 vm
.set('keepCryptVisible', !!cryptKeyInfo
);
230 vm
.set('allowEdit', !cryptKeyInfo
);
232 me
.callParent([values
]);
238 keepCryptVisible
: false,
241 showDangerousHint
: get => {
242 let allowEdit
= get('allowEdit');
243 return get('keepCryptVisible') && allowEdit
;
250 xtype
: 'displayfield',
251 name
: 'crypt-key-fp',
252 fieldLabel
: gettext('Encryption Key'),
257 name
: 'crypt-allow-edit',
258 boxLabel
: gettext('Edit existing encryption key (dangerous!)'),
261 isDirty
: () => false,
263 hidden
: '{!keepCryptVisible}',
264 value
: '{allowEdit}',
271 boxLabel
: gettext('Keep encryption key'),
274 hidden
: '{isCreate}',
275 checked
: '{!isCreate}',
278 hidden
: '{!keepCryptVisible}',
279 disabled
: '{!allowEdit}',
289 disabled
: '{!isCreate}',
290 checked
: '{isCreate}',
291 boxLabel
: get => get('isCreate')
292 ? gettext('Do not encrypt backups')
293 : gettext('Delete existing encryption key'),
296 disabled
: '{!allowEdit}',
302 inputValue
: 'autogenerate',
303 boxLabel
: gettext('Auto-generate a client encryption key'),
306 disabled
: '{!isCreate}',
309 disabled
: '{!allowEdit}',
315 inputValue
: 'upload',
316 boxLabel
: gettext('Upload an existing client encryption key'),
319 disabled
: '{!isCreate}',
322 disabled
: '{!allowEdit}',
325 change: function(f
, value
) {
326 let panel
= this.up('inputpanel');
327 if (!panel
.rendered
) {
330 let uploadKeyField
= panel
.down('field[name=crypt-key-upload]');
331 uploadKeyField
.setDisabled(!value
);
332 uploadKeyField
.setHidden(!value
);
334 let uploadKeyButton
= panel
.down('filebutton[name=crypt-upload-button]');
335 uploadKeyButton
.setDisabled(!value
);
336 uploadKeyButton
.setHidden(!value
);
339 uploadKeyField
.validate();
341 uploadKeyField
.reset();
347 xtype
: 'fieldcontainer',
351 xtype
: 'proxmoxtextfield',
352 name
: 'crypt-key-upload',
353 fieldLabel
: gettext('Key'),
360 emptyText
: gettext('You can drag-and-drop a key file here.'),
361 validator: function(value
) {
365 key
= JSON
.parse(value
);
367 return "Failed to parse key - " + e
;
369 if (key
.data
=== undefined) {
370 return "Does not seems like a valid Proxmox Backup key!";
375 afterRender: function() {
376 if (!window
.FileReader
) {
377 // No FileReader support in this browser
380 let cancel = function(ev
) {
382 if (ev
.preventDefault
) {
386 this.inputEl
.on('dragover', cancel
);
387 this.inputEl
.on('dragenter', cancel
);
388 this.inputEl
.on('drop', ev
=> {
390 let files
= ev
.event
.dataTransfer
.files
;
391 PVE
.Utils
.loadTextFromFile(files
[0], v
=> this.setValue(v
));
397 name
: 'crypt-upload-button',
398 iconCls
: 'fa fa-fw fa-folder-open-o x-btn-icon-el-default-toolbar-small',
399 cls
: 'x-btn-default-toolbar-small proxmox-inline-button',
404 change: function(btn
, e
, value
) {
406 let field
= btn
.up().down('proxmoxtextfield[name=crypt-key-upload]');
407 PVE
.Utils
.loadTextFromFile(ev
.target
.files
[0], v
=> field
.setValue(v
));
419 html
: // `<b style="color:red;font-weight:600;">${gettext('Warning')}</b>: ` +
420 `<span class="fa fa-exclamation-triangle" style="color:red;font-size:14px;"></span> ` +
421 gettext('Deleting or replacing the encryption key will break restoring backups created with it!'),
424 hidden
: '{!showDangerousHint}',
430 Ext
.define('PVE.storage.PBSInputPanel', {
431 extend
: 'PVE.panel.StorageBase',
433 onlineHelp
: 'storage_pbs',
435 apiCallDone: function(success
, response
, options
) {
436 let res
= response
.result
.data
;
437 if (!(res
&& res
.config
&& res
.config
['encryption-key'])) {
440 let key
= res
.config
['encryption-key'];
441 Ext
.create('PVE.Storage.PBSKeyShow', {
452 xtype
: 'pvePBSEncryptionKeyTab',
453 title
: gettext('Encryption'),
457 setValues: function(values
) {
460 let server
= values
.server
;
461 if (values
.port
!== undefined) {
462 if (Proxmox
.Utils
.IP6_match
.test(server
)) {
463 server
= `[${server}]`;
465 server
+= `:${values.port}`;
467 values
.hostport
= server
;
469 return me
.callParent([values
]);
472 initComponent: function() {
477 xtype
: me
.isCreate
? 'proxmoxtextfield' : 'displayfield',
478 fieldLabel
: gettext('Server'),
484 change: function(field
, newvalue
) {
485 let server
= newvalue
;
488 let match
= Proxmox
.Utils
.HostPort_match
.exec(newvalue
);
489 if (match
=== null) {
490 match
= Proxmox
.Utils
.HostPortBrackets_match
.exec(newvalue
);
491 if (match
=== null) {
492 match
= Proxmox
.Utils
.IP6_dotnotation_match
.exec(newvalue
);
496 if (match
!== null) {
498 if (match
[2] !== undefined) {
503 field
.up('inputpanel').down('field[name=server]').setValue(server
);
504 field
.up('inputpanel').down('field[name=port]').setValue(port
);
509 xtype
: 'proxmoxtextfield',
512 submitValue
: me
.isCreate
, // it is fixed
515 xtype
: 'proxmoxtextfield',
517 deleteEmpty
: !me
.isCreate
,
521 xtype
: me
.isCreate
? 'textfield' : 'displayfield',
524 emptyText
: gettext('Example') + ': admin@pbs',
525 fieldLabel
: gettext('Username'),
527 regexText
: gettext('Example') + ': admin@pbs',
531 xtype
: me
.isCreate
? 'textfield' : 'displayfield',
532 inputType
: 'password',
534 value
: me
.isCreate
? '' : '********',
535 emptyText
: me
.isCreate
? gettext('None') : '',
536 fieldLabel
: gettext('Password'),
543 xtype
: 'displayfield',
547 fieldLabel
: gettext('Content'),
550 xtype
: me
.isCreate
? 'textfield' : 'displayfield',
553 fieldLabel
: 'Datastore',
557 xtype
: me
.isCreate
? 'textfield' : 'displayfield',
560 emptyText
: gettext('Root'),
561 fieldLabel
: gettext('Namespace'),
568 xtype
: 'proxmoxtextfield',
570 value
: me
.isCreate
? null : undefined,
571 fieldLabel
: gettext('Fingerprint'),
572 emptyText
: gettext('Server certificate SHA-256 fingerprint, required for self-signed certificates'),
573 regex
: /[A-Fa-f0-9]{2}(:[A-Fa-f0-9]{2}){31}/,
574 regexText
: gettext('Example') + ': AB:CD:EF:...',
575 deleteEmpty
: !me
.isCreate
,