mirror of
https://github.com/documize/community.git
synced 2025-07-19 21:29:42 +02:00
Provide Backup/Restore for system and tenant
This commit is contained in:
parent
b3383f46ca
commit
ec1939c01d
11 changed files with 2128 additions and 780 deletions
|
@ -49,7 +49,6 @@ import (
|
||||||
"github.com/documize/community/model/permission"
|
"github.com/documize/community/model/permission"
|
||||||
"github.com/documize/community/model/pin"
|
"github.com/documize/community/model/pin"
|
||||||
"github.com/documize/community/model/space"
|
"github.com/documize/community/model/space"
|
||||||
"github.com/documize/community/model/user"
|
|
||||||
uuid "github.com/nu7hatch/gouuid"
|
uuid "github.com/nu7hatch/gouuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -304,7 +303,7 @@ func (b backerHandler) dmzUserAccount(files *[]backupItem) (err error) {
|
||||||
w = fmt.Sprintf(" , dmz_user_account a WHERE u.c_refid=a.c_userid AND a.c_orgid='%s' ", b.Spec.OrgID)
|
w = fmt.Sprintf(" , dmz_user_account a WHERE u.c_refid=a.c_userid AND a.c_orgid='%s' ", b.Spec.OrgID)
|
||||||
}
|
}
|
||||||
|
|
||||||
u := []user.User{}
|
u := []m.User{}
|
||||||
err = b.Runtime.Db.Select(&u, `SELECT u.id, u.c_refid AS refid,
|
err = b.Runtime.Db.Select(&u, `SELECT u.id, u.c_refid AS refid,
|
||||||
u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email,
|
u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email,
|
||||||
u.c_initials AS initials, u.c_globaladmin AS globaladmin,
|
u.c_initials AS initials, u.c_globaladmin AS globaladmin,
|
||||||
|
@ -740,13 +739,13 @@ func (b backerHandler) dmzDocument(files *[]backupItem) (err error) {
|
||||||
// Share
|
// Share
|
||||||
type share struct {
|
type share struct {
|
||||||
ID uint64 `json:"id"`
|
ID uint64 `json:"id"`
|
||||||
OrgID string `json:"-"`
|
OrgID string `json:"orgId"`
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
DocumentID string `json:"documentId"`
|
DocumentID string `json:"documentId"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Viewed string `json:"viewed"` // recording each view as |date-viewed|date-viewed|
|
Viewed string `json:"viewed"` // recording each view as |date-viewed|date-viewed|
|
||||||
Secret string `json:"-"` // secure token used to access document
|
Secret string `json:"secret"` // secure token used to access document
|
||||||
Expires string `json:"expires"` // number of days from creation, value of 0 means never
|
Expires string `json:"expires"` // number of days from creation, value of 0 means never
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
|
@ -756,7 +755,7 @@ func (b backerHandler) dmzDocument(files *[]backupItem) (err error) {
|
||||||
err = b.Runtime.Db.Select(&sh, `
|
err = b.Runtime.Db.Select(&sh, `
|
||||||
SELECT id AS id, c_orgid AS orgid, c_docid AS documentid,
|
SELECT id AS id, c_orgid AS orgid, c_docid AS documentid,
|
||||||
c_userid AS userid, c_email AS email, c_message AS message, c_viewed AS viewed,
|
c_userid AS userid, c_email AS email, c_message AS message, c_viewed AS viewed,
|
||||||
c_expires AS expires, c_active AS active, c_created AS created
|
c_expires AS expires, c_active AS active, c_secret AS secret, c_created AS created
|
||||||
FROM dmz_doc_share`+w)
|
FROM dmz_doc_share`+w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "select.docshare")
|
return errors.Wrap(err, "select.docshare")
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -29,7 +29,7 @@ type Store struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
//**************************************************
|
//**************************************************
|
||||||
// Page Revisions
|
// Page
|
||||||
//**************************************************
|
//**************************************************
|
||||||
|
|
||||||
// Add inserts the given page into the page table, adds that page to the queue of pages to index and audits that the page has been added.
|
// Add inserts the given page into the page table, adds that page to the queue of pages to index and audits that the page has been added.
|
||||||
|
@ -157,7 +157,7 @@ func (s Store) Update(ctx domain.RequestContext, page page.Page, refID, userID s
|
||||||
c_name, c_body, c_rawbody, c_config, c_created, c_revised)
|
c_name, c_body, c_rawbody, c_config, c_created, c_revised)
|
||||||
SELECT ? AS refid, a.c_orgid, a.c_docid, a.c_userid AS ownerid, a.c_refid AS sectionid,
|
SELECT ? AS refid, a.c_orgid, a.c_docid, a.c_userid AS ownerid, a.c_refid AS sectionid,
|
||||||
? AS userid, a.c_contenttype, a.c_type, a.c_name, a.c_body,
|
? AS userid, a.c_contenttype, a.c_type, a.c_name, a.c_body,
|
||||||
b.c_rawbody, b.c_config, ? AS c_created, ? As c_revised
|
b.c_rawbody, b.c_config, ? AS c_created, ? AS c_revised
|
||||||
FROM dmz_section a, dmz_section_meta b
|
FROM dmz_section a, dmz_section_meta b
|
||||||
WHERE a.c_refid=? AND a.c_refid=b.c_sectionid`),
|
WHERE a.c_refid=? AND a.c_refid=b.c_sectionid`),
|
||||||
refID, userID, time.Now().UTC(), time.Now().UTC(), page.RefID)
|
refID, userID, time.Now().UTC(), time.Now().UTC(), page.RefID)
|
||||||
|
|
1346
embed/bindata.go
1346
embed/bindata.go
File diff suppressed because one or more lines are too long
|
@ -17,12 +17,15 @@ import Component from '@ember/component';
|
||||||
|
|
||||||
export default Component.extend(Notifier, Modal, {
|
export default Component.extend(Notifier, Modal, {
|
||||||
appMeta: service(),
|
appMeta: service(),
|
||||||
|
router: service(),
|
||||||
browserSvc: service('browser'),
|
browserSvc: service('browser'),
|
||||||
buttonLabel: 'Start Backup',
|
backupLabel: 'Backup',
|
||||||
|
backupSystemLabel: 'System Backup',
|
||||||
backupSpec: null,
|
backupSpec: null,
|
||||||
backupFilename: '',
|
backupFilename: '',
|
||||||
backupError: false,
|
backupError: false,
|
||||||
backupSuccess: false,
|
backupSuccess: false,
|
||||||
|
backupRunning: false,
|
||||||
restoreSpec: null,
|
restoreSpec: null,
|
||||||
restoreButtonLabel: 'Restore',
|
restoreButtonLabel: 'Restore',
|
||||||
restoreUploadReady: false,
|
restoreUploadReady: false,
|
||||||
|
@ -43,6 +46,8 @@ export default Component.extend(Notifier, Modal, {
|
||||||
|
|
||||||
this.set('restoreFile', null);
|
this.set('restoreFile', null);
|
||||||
this.set('confirmRestore', '');
|
this.set('confirmRestore', '');
|
||||||
|
|
||||||
|
this.set('backupType', { Tenant: true, System: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
|
@ -54,33 +59,44 @@ export default Component.extend(Notifier, Modal, {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
doBackup() {
|
||||||
onBackup() {
|
|
||||||
this.showWait();
|
this.showWait();
|
||||||
this.set('buttonLabel', 'Please wait, backup running...');
|
|
||||||
this.set('backupFilename', '');
|
this.set('backupFilename', '');
|
||||||
this.set('backupSuccess', false);
|
this.set('backupSuccess', false);
|
||||||
this.set('backupFailed', false);
|
this.set('backupFailed', false);
|
||||||
|
this.set('backupRunning', true);
|
||||||
|
|
||||||
// If Documize Global Admin we perform system-level backup.
|
|
||||||
// Otherwise it is current tenant backup.
|
|
||||||
let spec = this.get('backupSpec');
|
let spec = this.get('backupSpec');
|
||||||
if (this.get('session.isGlobalAdmin')) {
|
|
||||||
spec.org = "*";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.get('onBackup')(spec).then((filename) => {
|
this.get('onBackup')(spec).then((filename) => {
|
||||||
this.showDone();
|
this.showDone();
|
||||||
this.set('buttonLabel', 'Start Backup');
|
this.set('backupLabel', 'Start Backup');
|
||||||
this.set('backupSuccess', true);
|
this.set('backupSuccess', true);
|
||||||
this.set('backupFilename', filename);
|
this.set('backupFilename', filename);
|
||||||
|
this.set('backupRunning', false);
|
||||||
}, ()=> {
|
}, ()=> {
|
||||||
this.showDone();
|
this.showDone();
|
||||||
this.set('buttonLabel', 'Run Backup');
|
this.set('backupLabel', 'Run Backup');
|
||||||
this.set('backupFailed', true);
|
this.set('backupFailed', true);
|
||||||
|
this.set('backupRunning', false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onBackup() {
|
||||||
|
// We perform tenant level backup.
|
||||||
|
this.set('backupSpec.org', this.get('appMeta.orgId'));
|
||||||
|
|
||||||
|
this.doBackup();
|
||||||
|
},
|
||||||
|
|
||||||
|
onSystemBackup() {
|
||||||
|
// We perform system-level backup.
|
||||||
|
this.set('backupSpec.org', '*');
|
||||||
|
|
||||||
|
this.doBackup();
|
||||||
|
},
|
||||||
|
|
||||||
onShowRestoreModal() {
|
onShowRestoreModal() {
|
||||||
this.modalOpen("#confirm-restore-modal", {"show": true}, '#confirm-restore');
|
this.modalOpen("#confirm-restore-modal", {"show": true}, '#confirm-restore');
|
||||||
},
|
},
|
||||||
|
@ -132,11 +148,12 @@ export default Component.extend(Notifier, Modal, {
|
||||||
|
|
||||||
this.get('onRestore')(spec, filedata).then(() => {
|
this.get('onRestore')(spec, filedata).then(() => {
|
||||||
this.showDone();
|
this.showDone();
|
||||||
this.set('buttonLabel', 'Restore');
|
this.set('backupLabel', 'Restore');
|
||||||
this.set('restoreSuccess', true);
|
this.set('restoreSuccess', true);
|
||||||
|
this.get('router').transitionTo('auth.logout');
|
||||||
}, ()=> {
|
}, ()=> {
|
||||||
this.showDone();
|
this.showDone();
|
||||||
this.set('restorButtonLabel', 'Restore');
|
this.set('restorbackupLabel', 'Restore');
|
||||||
this.set('restoreFailed', true);
|
this.set('restoreFailed', true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -162,12 +162,7 @@
|
||||||
border: 1px solid $color-border;
|
border: 1px solid $color-border;
|
||||||
padding: 20px 20px;
|
padding: 20px 20px;
|
||||||
background-color: lighten($color-green, 60%);
|
background-color: lighten($color-green, 60%);
|
||||||
|
color: $color-off-black;
|
||||||
> .explain {
|
|
||||||
color: $color-gray;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .backup-fail {
|
> .backup-fail {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
|
@ -186,6 +181,7 @@
|
||||||
margin: 50px 0;
|
margin: 50px 0;
|
||||||
padding: 20px 20px;
|
padding: 20px 20px;
|
||||||
background-color: lighten($color-red, 60%);
|
background-color: lighten($color-red, 60%);
|
||||||
|
color: $color-off-black;
|
||||||
|
|
||||||
> .restore-fail {
|
> .restore-fail {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
|
|
|
@ -7,21 +7,29 @@
|
||||||
margin: 0 0 5px 0;
|
margin: 0 0 5px 0;
|
||||||
|
|
||||||
> .material-icons {
|
> .material-icons {
|
||||||
font-size: 1.4rem;
|
font-size: 1.5rem;
|
||||||
color: $color-gray;
|
color: $color-gray;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .selected {
|
> .selected {
|
||||||
color: $color-link;
|
color: $color-blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $color-link;
|
color: $color-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .text {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
vertical-align: sub;
|
||||||
|
color: $color-off-black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-radio-selected {
|
.ui-radio-selected {
|
||||||
color: $color-link;
|
color: $color-blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,23 +12,35 @@
|
||||||
<div class="backup-restore">
|
<div class="backup-restore">
|
||||||
<div class="backup-zone">
|
<div class="backup-zone">
|
||||||
{{#if session.isGlobalAdmin}}
|
{{#if session.isGlobalAdmin}}
|
||||||
<div class="explain">
|
|
||||||
<p>
|
<p>
|
||||||
Documize is a multi-tenanted application enabling both "tech.mycompany.com" and "sales.mycompany.com" to run using the same executable/database.
|
Documize is a multi-tenanted application enabling both "tech.mycompany.com" and "sales.mycompany.com" to run using the same executable/database.
|
||||||
As a Documize <b>Global</b> Administrator, you will be performing a complete system-wide backup across all tenants.
|
As a Documize <b>Global Administrator</b>, you will be performing a complete system-wide backup across all tenants.
|
||||||
A regular Documize Administrator can login to perform just a tenant-level backup (e.g. marketing.mycompany.com).
|
A Documize <b>Tenant Administrator</b> can login to perform a tenant-level backup (e.g. marketing.mycompany.com).
|
||||||
|
</p>
|
||||||
|
{{else}}
|
||||||
|
<p>
|
||||||
|
Documize is a multi-tenanted application enabling both "tech.mycompany.com" and "sales.mycompany.com" to run using the same executable/database.
|
||||||
|
A Documize <b>Global Administrator</b>, you will be performing a complete system-wide backup across all tenants.
|
||||||
|
As a Documize <b>Tenant Administrator</b> you can perform a tenant-level backup (e.g. marketing.mycompany.com).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<p class="font-weight-bold">It can take several minutes to complete the backup process — please be patient while the backup operation is in progress</p>
|
<p>It can take <b>several minutes</b> to complete the backup process — please be patient while the backup operation is in progress.</p>
|
||||||
|
|
||||||
<div class="margin-top-30 margin-bottom-20">
|
<div class="margin-top-30 margin-bottom-20">
|
||||||
{{#ui/ui-checkbox selected=backupSpec.retain}}
|
{{#ui/ui-checkbox selected=backupSpec.retain}}
|
||||||
Retain backup file on server
|
Retain backup file on server
|
||||||
{{/ui/ui-checkbox}}
|
{{/ui/ui-checkbox}}
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-success mb-3" {{action 'onBackup'}}>{{buttonLabel}}</button>
|
|
||||||
|
|
||||||
|
{{#if backupRunning}}
|
||||||
|
<h3 class="text-success">Backup running, please wait...</h3>
|
||||||
|
{{else}}
|
||||||
|
<button class="btn btn-success mb-3" {{action 'onBackup'}}>BACKUP TENANT</button>
|
||||||
|
{{#if session.isGlobalAdmin}}
|
||||||
|
<div class="button-gap" />
|
||||||
|
<button class="btn btn-success mb-3" {{action 'onSystemBackup'}}>BACKUP SYSTEM</button>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
{{#if backupFailed}}
|
{{#if backupFailed}}
|
||||||
<div class="backup-fail">Backup failed — please check server logs</div>
|
<div class="backup-fail">Backup failed — please check server logs</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -41,13 +53,10 @@
|
||||||
<div class="backup-restore">
|
<div class="backup-restore">
|
||||||
<div class="restore-zone">
|
<div class="restore-zone">
|
||||||
{{#if session.isGlobalAdmin}}
|
{{#if session.isGlobalAdmin}}
|
||||||
<div class="explain">
|
<p class="text-danger">Restore from a <b>system backup</b> should only be performed on an <b>empty Documize database.</b></p>
|
||||||
<p class="font-weight-bold">
|
|
||||||
You should only perform a restore to an empty Documize instance.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<p class="font-weight-bold">It can take up to an hour to complete the restore process — please be patient while the restore operation is in progress</p>
|
<p>Restore operation will <b>re-create</b> users, groups, permissions, spaces, categories and content.</p>
|
||||||
|
<p>It can take <b>several minutes</b> to complete the restore process — please be patient while the restore operation is in progress.</p>
|
||||||
|
|
||||||
<div class="margin-top-30 margin-bottom-20">
|
<div class="margin-top-30 margin-bottom-20">
|
||||||
<div class="custom-file">
|
<div class="custom-file">
|
||||||
|
@ -55,9 +64,6 @@
|
||||||
<label class="custom-file-label" for="restore-file">Choose backup file</label>
|
<label class="custom-file-label" for="restore-file">Choose backup file</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="margin-top-20"></div>
|
<div class="margin-top-20"></div>
|
||||||
{{#ui/ui-checkbox selected=restoreSpec.overwriteOrg}}
|
|
||||||
Overwrite settings — SMTP, authentication, integrations and other settings
|
|
||||||
{{/ui/ui-checkbox}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if restoreFailed}}
|
{{#if restoreFailed}}
|
||||||
|
|
|
@ -4,5 +4,5 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
<i class="material-icons">radio_button_unchecked</i>
|
<i class="material-icons">radio_button_unchecked</i>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{yield}}
|
<div class="text">{{yield}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,10 +13,11 @@
|
||||||
package backup
|
package backup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/documize/community/model/org"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
|
"github.com/documize/community/model"
|
||||||
|
"github.com/documize/community/model/org"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manifest contains backup meta information.
|
// Manifest contains backup meta information.
|
||||||
|
@ -74,4 +75,24 @@ type ImportSpec struct {
|
||||||
|
|
||||||
// Handle to the current organization being used for restore process.
|
// Handle to the current organization being used for restore process.
|
||||||
Org org.Organization
|
Org org.Organization
|
||||||
|
|
||||||
|
// Was the backup file for a global system backup?
|
||||||
|
// TRUE if Manifest.Org = "*".
|
||||||
|
GlobalBackup bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents user object for backup/restore operations.
|
||||||
|
// We include user specific secrets in such operations.
|
||||||
|
type User struct {
|
||||||
|
model.BaseEntity
|
||||||
|
Firstname string `json:"firstname"`
|
||||||
|
Lastname string `json:"lastname"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Initials string `json:"initials"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
GlobalAdmin bool `json:"global"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Salt string `json:"salt"`
|
||||||
|
Reset string `json:"reset"`
|
||||||
|
LastVersion string `json:"lastVersion"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import "github.com/documize/community/model"
|
||||||
// Organization defines a company that uses this app.
|
// Organization defines a company that uses this app.
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
model.BaseEntity
|
model.BaseEntity
|
||||||
Company string `json:"-"`
|
Company string `json:"company"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
|
@ -26,6 +26,6 @@ type Organization struct {
|
||||||
AuthConfig string `json:"authConfig"`
|
AuthConfig string `json:"authConfig"`
|
||||||
ConversionEndpoint string `json:"conversionEndpoint"`
|
ConversionEndpoint string `json:"conversionEndpoint"`
|
||||||
MaxTags int `json:"maxTags"`
|
MaxTags int `json:"maxTags"`
|
||||||
Serial string `json:"-"`
|
Serial string `json:"serial"`
|
||||||
Active bool `json:"-"`
|
Active bool `json:"active"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue