]>
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
);
187 Ext
.define('PVE.panel.PBSEncryptionKeyTab', {
188 extend
: 'Proxmox.panel.InputPanel',
189 xtype
: 'pvePBSEncryptionKeyTab',
190 mixins
: ['Proxmox.Mixin.CBind'],
192 onlineHelp
: 'storage_pbs_encryption',
194 onGetValues: function(form
) {
196 if (form
.cryptMode
=== 'upload') {
197 values
['encryption-key'] = form
['crypt-key-upload'];
198 } else if (form
.cryptMode
=== 'autogenerate') {
199 values
['encryption-key'] = 'autogen';
200 } else if (form
.cryptMode
=== 'none') {
201 if (!this.isCreate
) {
202 values
.delete = ['encryption-key'];
208 setValues: function(values
) {
210 let vm
= me
.getViewModel();
212 let cryptKeyInfo
= values
['encryption-key'];
214 let icon
= '<span class="fa fa-lock good"></span> ';
215 if (cryptKeyInfo
.match(/^[a-fA-F0-9]{2}:/)) { // new style fingerprint
216 let shortKeyFP
= PVE
.Utils
.render_pbs_fingerprint(cryptKeyInfo
);
217 values
['crypt-key-fp'] = icon
+ `${gettext('Active')} - ${gettext('Fingerprint')} ${shortKeyFP}`;
219 // old key without FP
220 values
['crypt-key-fp'] = icon
+ gettext('Active');
223 values
['crypt-key-fp'] = gettext('None');
224 let cryptModeNone
= me
.down('radiofield[inputValue=none]');
225 cryptModeNone
.setBoxLabel(gettext('Do not encrypt backups'));
226 cryptModeNone
.setValue(true);
228 vm
.set('keepCryptVisible', !!cryptKeyInfo
);
229 vm
.set('allowEdit', !cryptKeyInfo
);
231 me
.callParent([values
]);
237 keepCryptVisible
: false,
240 showDangerousHint
: get => {
241 let allowEdit
= get('allowEdit');
242 return get('keepCryptVisible') && allowEdit
;
249 xtype
: 'displayfield',
250 name
: 'crypt-key-fp',
251 fieldLabel
: gettext('Encryption Key'),
256 name
: 'crypt-allow-edit',
257 boxLabel
: gettext('Edit existing encryption key (dangerous!)'),
260 isDirty
: () => false,
262 hidden
: '{!keepCryptVisible}',
263 value
: '{allowEdit}',
270 boxLabel
: gettext('Keep encryption key'),
273 hidden
: '{isCreate}',
274 checked
: '{!isCreate}',
277 hidden
: '{!keepCryptVisible}',
278 disabled
: '{!allowEdit}',
288 disabled
: '{!isCreate}',
289 checked
: '{isCreate}',
290 boxLabel
: get => get('isCreate')
291 ? gettext('Do not encrypt backups')
292 : gettext('Delete existing encryption key'),
295 disabled
: '{!allowEdit}',
301 inputValue
: 'autogenerate',
302 boxLabel
: gettext('Auto-generate a client encryption key'),
305 disabled
: '{!isCreate}',
308 disabled
: '{!allowEdit}',
314 inputValue
: 'upload',
315 boxLabel
: gettext('Upload an existing client encryption key'),
318 disabled
: '{!isCreate}',
321 disabled
: '{!allowEdit}',
324 change: function(f
, value
) {
325 let panel
= this.up('inputpanel');
326 if (!panel
.rendered
) {
329 let uploadKeyField
= panel
.down('field[name=crypt-key-upload]');
330 uploadKeyField
.setDisabled(!value
);
331 uploadKeyField
.setHidden(!value
);
333 let uploadKeyButton
= panel
.down('filebutton[name=crypt-upload-button]');
334 uploadKeyButton
.setDisabled(!value
);
335 uploadKeyButton
.setHidden(!value
);
338 uploadKeyField
.validate();
340 uploadKeyField
.reset();
346 xtype
: 'fieldcontainer',
350 xtype
: 'proxmoxtextfield',
351 name
: 'crypt-key-upload',
352 fieldLabel
: gettext('Key'),
359 emptyText
: gettext('You can drag-and-drop a key file here.'),
360 validator: function(value
) {
364 key
= JSON
.parse(value
);
366 return "Failed to parse key - " + e
;
368 if (typeof key
.data
=== undefined) {
369 return "Does not seems like a valid Proxmox Backup key!";
374 afterRender: function() {
375 if (!window
.FileReader
) {
376 // No FileReader support in this browser
379 let cancel = function(ev
) {
381 if (ev
.preventDefault
) {
385 this.inputEl
.on('dragover', cancel
);
386 this.inputEl
.on('dragenter', cancel
);
387 this.inputEl
.on('drop', ev
=> {
389 let files
= ev
.event
.dataTransfer
.files
;
390 PVE
.Utils
.loadTextFromFile(files
[0], v
=> this.setValue(v
));
396 name
: 'crypt-upload-button',
397 iconCls
: 'fa fa-fw fa-folder-open-o x-btn-icon-el-default-toolbar-small',
398 cls
: 'x-btn-default-toolbar-small proxmox-inline-button',
403 change: function(btn
, e
, value
) {
405 let field
= btn
.up().down('proxmoxtextfield[name=crypt-key-upload]');
406 PVE
.Utils
.loadTextFromFile(ev
.target
.files
[0], v
=> field
.setValue(v
));
418 html
: // `<b style="color:red;font-weight:600;">${gettext('Warning')}</b>: ` +
419 `<span class="fa fa-exclamation-triangle" style="color:red;font-size:14px;"></span> ` +
420 gettext('Deleting or replacing the encryption key will break restoring backups created with it!'),
423 hidden
: '{!showDangerousHint}',
429 Ext
.define('PVE.storage.PBSInputPanel', {
430 extend
: 'PVE.panel.StorageBase',
432 onlineHelp
: 'storage_pbs',
434 apiCallDone: function(success
, response
, options
) {
435 let res
= response
.result
.data
;
436 if (!(res
&& res
.config
&& res
.config
['encryption-key'])) {
439 let key
= res
.config
['encryption-key'];
440 Ext
.create('PVE.Storage.PBSKeyShow', {
451 xtype
: 'pvePBSEncryptionKeyTab',
452 title
: gettext('Encryption'),
456 setValues: function(values
) {
459 let server
= values
.server
;
460 if (values
.port
!== undefined) {
461 if (Proxmox
.Utils
.IP6_match
.test(server
)) {
462 server
= `[${server}]`;
464 server
+= `:${values.port}`;
466 values
.hostport
= server
;
468 return me
.callParent([values
]);
471 initComponent: function() {
476 xtype
: me
.isCreate
? 'proxmoxtextfield' : 'displayfield',
477 fieldLabel
: gettext('Server'),
483 change: function(field
, newvalue
) {
484 let server
= newvalue
;
487 let match
= Proxmox
.Utils
.HostPort_match
.exec(newvalue
);
488 if (match
=== null) {
489 match
= Proxmox
.Utils
.HostPortBrackets_match
.exec(newvalue
);
490 if (match
=== null) {
491 match
= Proxmox
.Utils
.IP6_dotnotation_match
.exec(newvalue
);
495 if (match
!== null) {
497 if (match
[2] !== undefined) {
502 field
.up('inputpanel').down('field[name=server]').setValue(server
);
503 field
.up('inputpanel').down('field[name=port]').setValue(port
);
508 xtype
: 'proxmoxtextfield',
513 xtype
: 'proxmoxtextfield',
515 deleteEmpty
: !me
.isCreate
,
519 xtype
: me
.isCreate
? 'textfield' : 'displayfield',
522 emptyText
: gettext('Example') + ': admin@pbs',
523 fieldLabel
: gettext('Username'),
525 regexText
: gettext('Example') + ': admin@pbs',
529 xtype
: me
.isCreate
? 'textfield' : 'displayfield',
530 inputType
: 'password',
532 value
: me
.isCreate
? '' : '********',
533 emptyText
: me
.isCreate
? gettext('None') : '',
534 fieldLabel
: gettext('Password'),
541 xtype
: 'displayfield',
545 fieldLabel
: gettext('Content'),
548 xtype
: me
.isCreate
? 'textfield' : 'displayfield',
551 fieldLabel
: 'Datastore',
558 xtype
: 'proxmoxtextfield',
560 value
: me
.isCreate
? null : undefined,
561 fieldLabel
: gettext('Fingerprint'),
562 emptyText
: gettext('Server certificate SHA-256 fingerprint, required for self-signed certificates'),
563 regex
: /[A-Fa-f0-9]{2}(:[A-Fa-f0-9]{2}){31}/,
564 regexText
: gettext('Example') + ': AB:CD:EF:...',
565 deleteEmpty
: !me
.isCreate
,