diff --git a/gui/app/components/customize/backup-restore.js b/gui/app/components/customize/backup-restore.js new file mode 100644 index 00000000..ef0a8b4d --- /dev/null +++ b/gui/app/components/customize/backup-restore.js @@ -0,0 +1,32 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +import { inject as service } from '@ember/service'; +import Notifier from '../../mixins/notifier'; +import Component from '@ember/component'; + +export default Component.extend(Notifier, { + appMeta: service(), + browserSvc: service('browser'), + buttonLabel: 'Run Backup', + + actions: { + onBackup() { + this.showWait(); + this.set('buttonLabel', 'Please wait, backup running...'); + + this.get('onBackup')({}).then(() => { + this.set('buttonLabel', 'Run Backup'); + this.showDone(); + }); + } + } +}); diff --git a/gui/app/pods/customize/backup/controller.js b/gui/app/pods/customize/backup/controller.js new file mode 100644 index 00000000..24fb8608 --- /dev/null +++ b/gui/app/pods/customize/backup/controller.js @@ -0,0 +1,25 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +import { inject as service } from '@ember/service'; +import Controller from '@ember/controller'; + +export default Controller.extend({ + global: service(), + + actions: { + onBackup(spec) { + if(this.get('session.isAdmin')) { + return this.get('global').backup(spec); + } + } + } +}); diff --git a/gui/app/pods/customize/backup/route.js b/gui/app/pods/customize/backup/route.js new file mode 100644 index 00000000..db44f882 --- /dev/null +++ b/gui/app/pods/customize/backup/route.js @@ -0,0 +1,33 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +import { inject as service } from '@ember/service'; +import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; +import Route from '@ember/routing/route'; + +export default Route.extend(AuthenticatedRouteMixin, { + appMeta: service(), + session: service(), + global: service(), + + beforeModel() { + if (!this.get("session.isAdmin")) { + this.transitionTo('auth.login'); + } + }, + + model() { + }, + + activate() { + this.get('browser').setTitle('Backup/Restore'); + } +}); diff --git a/gui/app/pods/customize/backup/template.hbs b/gui/app/pods/customize/backup/template.hbs new file mode 100644 index 00000000..bf880678 --- /dev/null +++ b/gui/app/pods/customize/backup/template.hbs @@ -0,0 +1 @@ +{{customize/backup-restore onBackup=(action 'onBackup')}} diff --git a/gui/app/router.js b/gui/app/router.js index 84dcc6dd..69219ae5 100644 --- a/gui/app/router.js +++ b/gui/app/router.js @@ -98,6 +98,9 @@ export default Router.map(function () { this.route('integrations', { path: 'integrations' }); + this.route('backup', { + path: 'backup' + }); } ); diff --git a/gui/app/services/global.js b/gui/app/services/global.js index 051a4f9c..8cc26e23 100644 --- a/gui/app/services/global.js +++ b/gui/app/services/global.js @@ -9,12 +9,14 @@ // // https://documize.com +import { Promise as EmberPromise } from 'rsvp'; import Service, { inject as service } from '@ember/service'; export default Service.extend({ sessionService: service('session'), ajax: service(), appMeta: service(), + browserSvc: service('browser'), store: service(), // Returns SMTP configuration. @@ -137,5 +139,50 @@ export default Service.extend({ method: 'POST', }); } + }, + + // Run tenant level backup. + backup(spec) { + + return new EmberPromise((resolve) => { + let url = this.get('appMeta.endpoint'); + let token = this.get('sessionService.session.content.authenticated.token'); + let uploadUrl = `${url}/global/backup?token=${token}`; + + var xhr = new XMLHttpRequest(); + xhr.open('POST', uploadUrl); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.responseType = 'blob'; + + xhr.onload = function() { + if (this.status == 200) { + // get binary data as a response + var blob = this.response; + + let a = document.createElement("a"); + a.style = "display: none"; + document.body.appendChild(a); + + let url = window.URL.createObjectURL(blob); + a.href = url; + a.download = xhr.getResponseHeader('x-documize-filename').replace('"', ''); + a.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + resolve(); + } + } + + xhr.send(JSON.stringify(spec)); + }); + + // return this.get('ajax').raw(`global/backup`, { + // method: 'post', + // data: JSON.stringify(spec), + // contentType: 'json', + // dataType: 'text' + // }); } }); diff --git a/gui/app/templates/components/customize/backup-restore.hbs b/gui/app/templates/components/customize/backup-restore.hbs new file mode 100644 index 00000000..bab9fcdf --- /dev/null +++ b/gui/app/templates/components/customize/backup-restore.hbs @@ -0,0 +1,18 @@ + +
+
+
+

Backup & Restore

+

Export all documents and settings to a single ZIP file.

+
+
+
+ +
+
+
+

It can take several minutes to complete the backup process — please be patient while the backup is running.

+
{{buttonLabel}}
+
+
+
diff --git a/server/middleware.go b/server/middleware.go index b2443cd8..7ca901c3 100644 --- a/server/middleware.go +++ b/server/middleware.go @@ -39,7 +39,7 @@ func (m *middleware) cors(w http.ResponseWriter, r *http.Request, next http.Hand w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS, PATCH") w.Header().Set("Access-Control-Allow-Headers", "host, content-type, accept, authorization, origin, referer, user-agent, cache-control, x-requested-with") - w.Header().Set("Access-Control-Expose-Headers", "x-documize-version, x-documize-status") + w.Header().Set("Access-Control-Expose-Headers", "x-documize-version, x-documize-status, x-documize-filename, Content-Disposition, Content-Length") if r.Method == "OPTIONS" { w.Header().Add("X-Documize-Version", m.Runtime.Product.Version) diff --git a/server/routing/routes.go b/server/routing/routes.go index f2ce22bf..7078232b 100644 --- a/server/routing/routes.go +++ b/server/routing/routes.go @@ -19,6 +19,7 @@ import ( "github.com/documize/community/domain/auth" "github.com/documize/community/domain/auth/keycloak" "github.com/documize/community/domain/auth/ldap" + "github.com/documize/community/domain/backup" "github.com/documize/community/domain/block" "github.com/documize/community/domain/category" "github.com/documize/community/domain/conversion" @@ -57,6 +58,7 @@ func RegisterEndpoints(rt *env.Runtime, s *store.Store) { space := space.Handler{Runtime: rt, Store: s} block := block.Handler{Runtime: rt, Store: s} group := group.Handler{Runtime: rt, Store: s} + backup := backup.Handler{Runtime: rt, Store: s} section := section.Handler{Runtime: rt, Store: s} setting := setting.Handler{Runtime: rt, Store: s} category := category.Handler{Runtime: rt, Store: s} @@ -217,6 +219,7 @@ func RegisterEndpoints(rt *env.Runtime, s *store.Store) { AddPrivate(rt, "global/sync/keycloak", []string{"GET", "OPTIONS"}, nil, keycloak.Sync) AddPrivate(rt, "global/ldap/preview", []string{"POST", "OPTIONS"}, nil, ldap.Preview) AddPrivate(rt, "global/ldap/sync", []string{"GET", "OPTIONS"}, nil, ldap.Sync) + AddPrivate(rt, "global/backup", []string{"POST", "OPTIONS"}, nil, backup.Backup) Add(rt, RoutePrefixRoot, "robots.txt", []string{"GET", "OPTIONS"}, nil, meta.RobotsTxt) Add(rt, RoutePrefixRoot, "sitemap.xml", []string{"GET", "OPTIONS"}, nil, meta.Sitemap)