1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-20 21:59:42 +02:00

[WIP] Provide system restore facility

Co-Authored-By: Harvey Kandola <harvey@documize.com>
This commit is contained in:
sauls8t 2018-10-15 18:59:21 +01:00
parent 71a2860716
commit 516140dd7e
12 changed files with 597 additions and 51 deletions

View file

@ -43,7 +43,6 @@ func (s Store) Record(ctx domain.RequestContext, t audit.EventType) {
_, err = tx.Exec(s.Bind("INSERT INTO dmz_audit_log (c_orgid, c_userid, c_eventtype, c_ip, c_created) VALUES (?, ?, ?, ?, ?)"), _, err = tx.Exec(s.Bind("INSERT INTO dmz_audit_log (c_orgid, c_userid, c_eventtype, c_ip, c_created) VALUES (?, ?, ?, ?, ?)"),
e.OrgID, e.UserID, e.Type, e.IP, e.Created) e.OrgID, e.UserID, e.Type, e.IP, e.Created)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
s.Runtime.Log.Error("prepare audit insert", err) s.Runtime.Log.Error("prepare audit insert", err)

View file

@ -180,7 +180,28 @@ func (h *Handler) Restore(w http.ResponseWriter, r *http.Request) {
return return
} }
h.Runtime.Log.Info(fmt.Sprintf("%s %d %v %v", fileheader.Filename, len(b.Bytes()), overwriteOrg, createUsers)) h.Runtime.Log.Info(fmt.Sprintf("Restore file: %s %d", fileheader.Filename, len(b.Bytes())))
//
org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID)
if err != nil {
h.Runtime.Log.Error(method, err)
response.WriteServerError(w, method, err)
return
}
// Prepare context and start restore process.
spec := m.ImportSpec{OverwriteOrg: overwriteOrg, CreateUsers: createUsers, Org: org}
rh := restoreHandler{Runtime: h.Runtime, Store: h.Store, Context: ctx, Spec: spec}
err = rh.PerformRestore(b.Bytes(), r.ContentLength)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Runtime.Log.Info("Restore completed")
response.WriteEmpty(w) response.WriteEmpty(w)
} }

View file

@ -12,9 +12,497 @@
// Package backup handle data backup/restore to/from ZIP format. // Package backup handle data backup/restore to/from ZIP format.
package backup package backup
// DESIGN // The restore operation allows an admin to upload a backup file.
// ------ // ID, created and revised attributes values are maintained as per backup.
//
// The restore operation allows an admin to upload a backup file
import () import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/action"
"github.com/documize/community/model/audit"
m "github.com/documize/community/model/backup"
"github.com/documize/community/model/category"
"github.com/documize/community/model/org"
"github.com/documize/community/model/space"
"github.com/pkg/errors"
)
// Handler contains the runtime information such as logging and database.
type restoreHandler struct {
Runtime *env.Runtime
Store *store.Store
Spec m.ImportSpec
Context domain.RequestContext
Zip *zip.Reader
}
// PerformRestore will unzip backup file and verify contents
// are suitable for restore operation.
func (r *restoreHandler) PerformRestore(b []byte, l int64) (err error) {
// Read zip file into handler for subsequent processing.
z, err := zip.NewReader(bytes.NewReader(b), l)
if err != nil {
err = errors.Wrap(err, "cannot read zip file")
return
}
r.Zip = z
// Unpack manifest for backup host details.
err = r.manifest()
if err != nil {
return
}
// Organization.
err = r.dmzOrg()
if err != nil {
return
}
// Config.
err = r.dmzConfig()
if err != nil {
return
}
// Audit Log.
err = r.dmzAudit()
if err != nil {
return
}
// Action.
err = r.dmzAction()
if err != nil {
return
}
// Space.
err = r.dmzSpace()
if err != nil {
return
}
// Category.
err = r.dmzCategory()
if err != nil {
return
}
// CategoryMember.
err = r.dmzCategoryMember()
if err != nil {
return
}
return nil
}
func (r *restoreHandler) manifest() (err error) {
found, zi, err := r.readZip("manifest.json")
if !found {
err = errors.Wrap(err, "missing manifest.json")
return
}
if err != nil {
err = errors.Wrap(err, "failed to process manifest.json")
return
}
err = json.Unmarshal(zi, &r.Spec.Manifest)
if err != nil {
err = errors.Wrap(err, "failed to read manifest as JSON")
return
}
r.Runtime.Log.Info("Extracted manifest.json")
return nil
}
// Reads file and unmarshals content as JSON.
func (r *restoreHandler) fileJSON(filename string, v interface{}) (err error) {
found, zi, err := r.readZip(filename)
if !found {
err = errors.Wrap(err, fmt.Sprintf("missing %s", filename))
return
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to process %s", filename))
return
}
err = json.Unmarshal(zi, &v)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to read %s as JSON", filename))
return
}
return nil
}
// Fetches file from zip reader.
func (r *restoreHandler) readZip(filename string) (found bool, b []byte, err error) {
found = false
for _, zf := range r.Zip.File {
if zf.Name == filename {
src, e := zf.Open()
if e != nil {
e = errors.Wrap(e, fmt.Sprintf("cannot open %s", filename))
return true, b, e
}
defer src.Close()
b, e = ioutil.ReadAll(src)
if e != nil {
e = errors.Wrap(e, fmt.Sprintf("cannot read %s", filename))
return true, b, e
}
found = true
err = nil
break
}
}
return
}
// Organization.
func (r *restoreHandler) dmzOrg() (err error) {
filename := "dmz_org.json"
org := []org.Organization{}
err = r.fileJSON(filename, &org)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to load %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Extracted %s", filename))
r.Context.Transaction, err = r.Runtime.Db.Beginx()
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to start TX for %s", filename))
return
}
for i := range org {
// If same tenant (domain) then patch OrgID.
if org[i].Domain == r.Spec.Org.Domain {
org[i].RefID = r.Spec.Org.RefID
// Update org settings if allowed to do so.
if !r.Spec.OverwriteOrg {
org[i].AllowAnonymousAccess = r.Spec.Org.AllowAnonymousAccess
org[i].AuthProvider = r.Spec.Org.AuthProvider
org[i].AuthConfig = r.Spec.Org.AuthConfig
org[i].Company = r.Spec.Org.Company
org[i].ConversionEndpoint = r.Spec.Org.ConversionEndpoint
org[i].Email = r.Spec.Org.Email
org[i].MaxTags = r.Spec.Org.MaxTags
org[i].Message = r.Spec.Org.Message
org[i].Serial = r.Spec.Org.Serial
org[i].Title = r.Spec.Org.Title
}
_, err = r.Context.Transaction.NamedExec(`UPDATE dmz_org SET
c_anonaccess=:allowanonymousaccess,
c_authprovider=:authprovider,
c_authconfig=:authconfig,
c_company=:company,
c_service=:conversionendpoint,
c_email=:email,
c_maxtags=:maxtags,
c_message=:message,
c_title=:title,
c_serial=:serial
WHERE c_refid=:refid`, &org[i])
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, "unable to overwrite current organization settings")
return
}
} else {
// Add new organization.
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(`
INSERT INTO dmz_org (c_refid, c_company, c_title, c_message,
c_domain, c_service, c_email, c_anonaccess, c_authprovider, c_authconfig,
c_maxtags, c_verified, c_serial, c_active, c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
org[i].RefID, org[i].Company, org[i].Title, org[i].Message,
strings.ToLower(org[i].Domain), org[i].ConversionEndpoint, strings.ToLower(org[i].Email),
org[i].AllowAnonymousAccess, org[i].AuthProvider, org[i].AuthConfig,
org[i].MaxTags, true, org[i].Serial, org[i].Active, org[i].Created, org[i].Revised)
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to insert %s %s", filename, org[i].RefID))
return
}
}
}
err = r.Context.Transaction.Commit()
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to commit %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Processed %s %d records", filename, len(org)))
return nil
}
// Config.
func (r *restoreHandler) dmzConfig() (err error) {
filename := "dmz_config.json"
type config struct {
ConfigKey string `json:"key"`
ConfigValue string `json:"config"`
}
c := []config{}
err = r.fileJSON(filename, &c)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to load %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Extracted %s", filename))
for i := range c {
err = r.Store.Setting.Set(c[i].ConfigKey, c[i].ConfigValue)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to insert %s %s", filename, c[i].ConfigKey))
return
}
}
r.Runtime.Log.Info(fmt.Sprintf("Processed %s %d records", filename, len(c)))
return nil
}
// Audit Log.
func (r *restoreHandler) dmzAudit() (err error) {
filename := "dmz_audit_log.json"
log := []audit.AppEvent{}
err = r.fileJSON(filename, &log)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to load %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Extracted %s", filename))
r.Context.Transaction, err = r.Runtime.Db.Beginx()
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to start TX for %s", filename))
return
}
for i := range log {
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind("INSERT INTO dmz_audit_log (c_orgid, c_userid, c_eventtype, c_ip, c_created) VALUES (?, ?, ?, ?, ?)"),
log[i].OrgID, log[i].UserID, log[i].Type, log[i].IP, log[i].Created)
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to insert %s %d", filename, log[i].ID))
return
}
}
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind("INSERT INTO dmz_audit_log (c_orgid, c_userid, c_eventtype, c_ip, c_created) VALUES (?, ?, ?, ?, ?)"),
r.Context.OrgID, r.Context.UserID, "restored-database", r.Context.ClientIP, time.Now().UTC())
err = r.Context.Transaction.Commit()
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to commit %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Processed %s %d records", filename, len(log)))
return nil
}
// Action.
func (r *restoreHandler) dmzAction() (err error) {
filename := "dmz_action.json"
ac := []action.UserAction{}
err = r.fileJSON(filename, &ac)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to load %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Extracted %s", filename))
r.Context.Transaction, err = r.Runtime.Db.Beginx()
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to start TX for %s", filename))
return
}
for i := range ac {
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind("INSERT INTO dmz_action (c_refid, c_orgid, c_userid, c_docid, c_actiontype, c_note, c_requestorid, c_requested, c_due, c_reftype, c_reftypeid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
ac[i].RefID, ac[i].OrgID, ac[i].UserID, ac[i].DocumentID, ac[i].ActionType, ac[i].Note, ac[i].RequestorID, ac[i].Requested, ac[i].Due, ac[i].RefType, ac[i].RefTypeID)
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to insert %s %s", filename, ac[i].RefID))
return
}
}
err = r.Context.Transaction.Commit()
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to commit %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Processed %s %d records", filename, len(ac)))
return nil
}
// Space.
func (r *restoreHandler) dmzSpace() (err error) {
filename := "dmz_space.json"
sp := []space.Space{}
err = r.fileJSON(filename, &sp)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to load %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Extracted %s", filename))
r.Context.Transaction, err = r.Runtime.Db.Beginx()
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to start TX for %s", filename))
return
}
for i := range sp {
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind("INSERT INTO dmz_space (c_refid, c_name, c_orgid, c_userid, c_type, c_lifecycle, c_likes, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
sp[i].RefID, sp[i].Name, sp[i].OrgID, sp[i].UserID, sp[i].Type, sp[i].Lifecycle, sp[i].Likes, sp[i].Created, sp[i].Revised)
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to insert %s %s", filename, sp[i].RefID))
return
}
}
err = r.Context.Transaction.Commit()
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to commit %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Processed %s %d records", filename, len(sp)))
return nil
}
// Category.
func (r *restoreHandler) dmzCategory() (err error) {
filename := "dmz_category.json"
ct := []category.Category{}
err = r.fileJSON(filename, &ct)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to load %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Extracted %s", filename))
r.Context.Transaction, err = r.Runtime.Db.Beginx()
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to start TX for %s", filename))
return
}
for i := range ct {
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(`
INSERT INTO dmz_category (c_refid, c_orgid, c_spaceid, c_name, c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?)`),
ct[i].RefID, ct[i].OrgID, ct[i].SpaceID, ct[i].Name, ct[i].Created, ct[i].Revised)
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to insert %s %s", filename, ct[i].RefID))
return
}
}
err = r.Context.Transaction.Commit()
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to commit %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Processed %s %d records", filename, len(ct)))
return nil
}
// CategoryMember.
func (r *restoreHandler) dmzCategoryMember() (err error) {
filename := "dmz_category_member.json"
cm := []category.Member{}
err = r.fileJSON(filename, &cm)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to load %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Extracted %s", filename))
r.Context.Transaction, err = r.Runtime.Db.Beginx()
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to start TX for %s", filename))
return
}
for i := range cm {
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(`
INSERT INTO dmz_category_member
(c_refid, c_orgid, c_categoryid, c_spaceid, c_docid, c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?, ?)`),
cm[i].RefID, cm[i].OrgID, cm[i].CategoryID, cm[i].SpaceID, cm[i].DocumentID, cm[i].Created, cm[i].Revised)
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to insert %s %s", filename, cm[i].RefID))
return
}
}
err = r.Context.Transaction.Commit()
if err != nil {
r.Context.Transaction.Rollback()
err = errors.Wrap(err, fmt.Sprintf("unable to commit %s", filename))
return
}
r.Runtime.Log.Info(fmt.Sprintf("Processed %s %d records", filename, len(cm)))
return nil
}

View file

@ -32,11 +32,7 @@ type Store struct {
// AddOrganization inserts the passed organization record into the organization table. // AddOrganization inserts the passed organization record into the organization table.
func (s Store) AddOrganization(ctx domain.RequestContext, org org.Organization) (err error) { func (s Store) AddOrganization(ctx domain.RequestContext, org org.Organization) (err error) {
org.Created = time.Now().UTC() _, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, c_domain, c_email, c_anonaccess, c_serial, c_maxtags, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
org.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(
s.Bind("INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, c_domain, c_email, c_anonaccess, c_serial, c_maxtags, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
org.RefID, org.Company, org.Title, org.Message, strings.ToLower(org.Domain), org.RefID, org.Company, org.Title, org.Message, strings.ToLower(org.Domain),
strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.MaxTags, org.Created, org.Revised) strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.MaxTags, org.Created, org.Revised)

View file

@ -20,6 +20,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings" "strings"
"time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/event" "github.com/documize/community/core/event"
@ -102,6 +103,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
sp.UserID = ctx.UserID sp.UserID = ctx.UserID
sp.Type = space.ScopePrivate sp.Type = space.ScopePrivate
sp.Lifecycle = wf.LifecycleLive sp.Lifecycle = wf.LifecycleLive
sp.UserID = ctx.UserID
sp.Created = time.Now().UTC()
sp.Revised = time.Now().UTC()
err = h.Store.Space.Add(ctx, sp) err = h.Store.Space.Add(ctx, sp)
if err != nil { if err != nil {

View file

@ -31,6 +31,9 @@ func TestSpace(t *testing.T) {
sp.UserID = ctx.UserID sp.UserID = ctx.UserID
sp.Type = space.ScopePublic sp.Type = space.ScopePublic
sp.Name = "PublicTestSpace" sp.Name = "PublicTestSpace"
sp.UserID = ctx.UserID
sp.Created = time.Now().UTC()
sp.Revised = time.Now().UTC()
err = s.Space.Add(ctx, sp) err = s.Space.Add(ctx, sp)
if err != nil { if err != nil {
@ -98,6 +101,9 @@ func TestSpace(t *testing.T) {
sp2.OrgID = ctx.OrgID sp2.OrgID = ctx.OrgID
sp2.Type = space.ScopePrivate sp2.Type = space.ScopePrivate
sp2.Name = "PrivateTestSpace" sp2.Name = "PrivateTestSpace"
sp.UserID = ctx.UserID
sp.Created = time.Now().UTC()
sp.Revised = time.Now().UTC()
err = s.Space.Add(ctx, sp2) err = s.Space.Add(ctx, sp2)
if err != nil { if err != nil {

View file

@ -30,10 +30,6 @@ type Store struct {
// Add adds new folder into the store. // Add adds new folder into the store.
func (s Store) Add(ctx domain.RequestContext, sp space.Space) (err error) { func (s Store) Add(ctx domain.RequestContext, sp space.Space) (err error) {
sp.UserID = ctx.UserID
sp.Created = time.Now().UTC()
sp.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_space (c_refid, c_name, c_orgid, c_userid, c_type, c_lifecycle, c_likes, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"), _, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_space (c_refid, c_name, c_orgid, c_userid, c_type, c_lifecycle, c_likes, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
sp.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Lifecycle, sp.Likes, sp.Created, sp.Revised) sp.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Lifecycle, sp.Likes, sp.Created, sp.Revised)

View file

@ -12,9 +12,10 @@
import $ from 'jquery'; import $ from 'jquery';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Notifier from '../../mixins/notifier'; import Notifier from '../../mixins/notifier';
import Modal from '../../mixins/modal';
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend(Notifier, { export default Component.extend(Notifier, Modal, {
appMeta: service(), appMeta: service(),
browserSvc: service('browser'), browserSvc: service('browser'),
buttonLabel: 'Start Backup', buttonLabel: 'Start Backup',
@ -23,9 +24,9 @@ export default Component.extend(Notifier, {
backupError: false, backupError: false,
backupSuccess: false, backupSuccess: false,
restoreSpec: null, restoreSpec: null,
restoreButtonLabel: 'Perform Restore', restoreButtonLabel: 'Restore',
restoreUploading: false,
restoreUploadReady: false, restoreUploadReady: false,
confirmRestore: '',
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
@ -41,6 +42,7 @@ export default Component.extend(Notifier, {
}); });
this.set('restoreFile', null); this.set('restoreFile', null);
this.set('confirmRestore', '');
}, },
didInsertElement() { didInsertElement() {
@ -79,7 +81,26 @@ export default Component.extend(Notifier, {
}); });
}, },
onRestore() { onShowRestoreModal() {
this.modalOpen("#confirm-restore-modal", {"show": true}, '#confirm-restore');
},
onRestore(e) {
e.preventDefault();
let typed = this.get('confirmRestore');
typed = typed.toLowerCase();
if (typed !== 'restore' || typed === '') {
$("#confirm-restore").addClass("is-invalid").focus();
return;
}
this.set('confirmRestore', '');
$("#confirm-restore").removeClass("is-invalid");
this.modalClose('#confirm-restore-modal');
// do we have upload file? // do we have upload file?
// let files = document.getElementById("restore-file").files; // let files = document.getElementById("restore-file").files;
// if (is.undefined(files) || is.null(files)) { // if (is.undefined(files) || is.null(files)) {
@ -111,17 +132,16 @@ export default Component.extend(Notifier, {
this.get('onRestore')(spec, filedata).then(() => { this.get('onRestore')(spec, filedata).then(() => {
this.showDone(); this.showDone();
this.set('buttonLabel', 'Perform Restore'); this.set('buttonLabel', 'Restore');
this.set('restoreSuccess', true); this.set('restoreSuccess', true);
}, ()=> { }, ()=> {
this.showDone(); this.showDone();
this.set('restorButtonLabel', 'Perform Restore'); this.set('restorButtonLabel', 'Restore');
this.set('restoreFailed', true); this.set('restoreFailed', true);
}); });
}, },
upload(event) { upload(event) {
this.set('restoreUploading', true);
this.set('restoreUploadReady', false); this.set('restoreUploadReady', false);
this.set('restoreFile', null); this.set('restoreFile', null);
@ -130,7 +150,6 @@ export default Component.extend(Notifier, {
this.set('restoreFile', file); this.set('restoreFile', file);
this.set('restoreUploadReady', true); this.set('restoreUploadReady', true);
this.set('restoreUploading', false);
// let imageData; // let imageData;
// reader.onload = () => { // reader.onload = () => {
@ -146,3 +165,7 @@ export default Component.extend(Notifier, {
} }
} }
}); });
// {{#ui/ui-checkbox selected=restoreSpec.recreateUsers}}
// Recreate user accounts &mdash; users, groups, permissions
// {{/ui/ui-checkbox}}

View file

@ -17,15 +17,11 @@ export default Controller.extend({
actions: { actions: {
onBackup(spec) { onBackup(spec) {
if(this.get('session.isAdmin')) { return this.get('global').backup(spec);
return this.get('global').backup(spec);
}
}, },
onRestore(spec, filedata) { onRestore(spec, filedata) {
if(this.get('session.isAdmin')) { return this.get('global').restore(spec, filedata);
return this.get('global').restore(spec, filedata);
}
} }
} }
}); });

View file

@ -144,11 +144,11 @@ export default Service.extend({
// Run backup. // Run backup.
backup(spec) { backup(spec) {
if (!this.get('sessionService.isGlobalAdmin') || this.get('sessionService.isAdmin')) {
return;
}
return new EmberPromise((resolve, reject) => { return new EmberPromise((resolve, reject) => {
if (!this.get('sessionService.isGlobalAdmin') || !this.get('sessionService.isAdmin')) {
reject();
}
let url = this.get('appMeta.endpoint'); let url = this.get('appMeta.endpoint');
let token = this.get('sessionService.session.content.authenticated.token'); let token = this.get('sessionService.session.content.authenticated.token');
let uploadUrl = `${url}/global/backup?token=${token}`; let uploadUrl = `${url}/global/backup?token=${token}`;
@ -196,6 +196,10 @@ export default Service.extend({
data.set('restore-file', file); data.set('restore-file', file);
return new EmberPromise((resolve, reject) => { return new EmberPromise((resolve, reject) => {
if (!this.get('sessionService.isGlobalAdmin') || !this.get('sessionService.isAdmin')) {
reject();
}
let url = this.get('appMeta.endpoint'); let url = this.get('appMeta.endpoint');
let token = this.get('sessionService.session.content.authenticated.token'); let token = this.get('sessionService.session.content.authenticated.token');
let uploadUrl = `${url}/global/restore?token=${token}&org=${spec.overwriteOrg}&users=${spec.recreateUsers}`; let uploadUrl = `${url}/global/restore?token=${token}&org=${spec.overwriteOrg}&users=${spec.recreateUsers}`;

View file

@ -42,10 +42,8 @@
<div class="restore-zone"> <div class="restore-zone">
{{#if session.isGlobalAdmin}} {{#if session.isGlobalAdmin}}
<div class="explain"> <div class="explain">
<p class="font-weight-bold">WARNING:</p> <p class="font-weight-bold">
<p> You should only perform a restore to an empty Documize instance.
You should only perform a restore on a <b>new Documize instance</b> and NOT on the original instance.
Duplicate data might exist if you restore onto the same instance without first removing previous data.
</p> </p>
</div> </div>
{{/if}} {{/if}}
@ -56,22 +54,10 @@
<input type="file" class="custom-file-input" id="restore-file" accept="application/zip" multiple=false onchange={{action "upload"}}> <input type="file" class="custom-file-input" id="restore-file" accept="application/zip" multiple=false onchange={{action "upload"}}>
<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="restore-upload-busy">
{{#if restoreUploadReady}}
<div class="ready">Ready to start restore</div>
{{/if}}
{{#if restoreUploading}}
<img src="/assets/img/busy-gray.gif" />
<div class="wait">Uploading file</div>
{{/if}}
</div>
<div class="margin-top-20"></div> <div class="margin-top-20"></div>
{{#ui/ui-checkbox selected=restoreSpec.overwriteOrg}} {{#ui/ui-checkbox selected=restoreSpec.overwriteOrg}}
Overwrite settings &mdash; SMTP, authentication, integrations and other settings Overwrite settings &mdash; SMTP, authentication, integrations and other settings
{{/ui/ui-checkbox}} {{/ui/ui-checkbox}}
{{#ui/ui-checkbox selected=restoreSpec.recreateUsers}}
Recreate user accounts &mdash; users, groups, permissions
{{/ui/ui-checkbox}}
</div> </div>
{{#if restoreFailed}} {{#if restoreFailed}}
@ -80,10 +66,30 @@
<div class="restore-success">Restore completed &mdash; restart your browser and log in</div> <div class="restore-success">Restore completed &mdash; restart your browser and log in</div>
{{else}} {{else}}
{{#if restoreUploadReady}} {{#if restoreUploadReady}}
<button class="btn btn-danger mb-3" {{action 'onRestore'}}>{{restoreButtonLabel}}</button> <button class="btn btn-danger mb-3" {{action 'onShowRestoreModal'}}>{{restoreButtonLabel}}</button>
{{/if}} {{/if}}
{{/if}} {{/if}}
</div> </div>
</div> </div>
</div>
<div id="confirm-restore-modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">Confirm Restore</div>
<div class="modal-body">
<form onsubmit={{action 'onRestore'}}>
<div class="form-group">
<label for="delete-space-name">Please type RESTORE to commence the process</label>
{{input type='text' id="confirm-restore" class="form-control mousetrap" placeholder="Please type RESTORE" value=confirmRestore}}
<small class="form-text text-muted">You should only restore to an empty Documize instance</small>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" onclick={{action 'onRestore'}}>Start Restore</button>
</div>
</div>
</div>
</div> </div>

View file

@ -13,6 +13,7 @@
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"
@ -67,4 +68,10 @@ type ImportSpec struct {
// Recreate users. // Recreate users.
CreateUsers bool `json:"createUsers"` CreateUsers bool `json:"createUsers"`
// As found in backup file.
Manifest Manifest
// Handle to the current organization being used for restore process.
Org org.Organization
} }