From 036f36ba1d86a8d266c65e07d1b924a2e1e8dbaf Mon Sep 17 00:00:00 2001 From: McMatts Date: Sun, 6 Jan 2019 13:50:12 +0000 Subject: [PATCH] Enable custom logo upload and rendering --- domain/backup/backup.go | 54 +---------- domain/backup/models.go | 78 +++++++++++++++ domain/backup/restore.go | 22 +++-- domain/meta/endpoint.go | 39 ++++++++ domain/organization/endpoint.go | 50 ++++++++++ domain/organization/store.go | 30 +++++- domain/space/endpoint.go | 43 +++++---- domain/space/store.go | 7 +- domain/store/storer.go | 2 + .../components/customize/general-settings.js | 52 +++++++++- gui/app/components/folder/settings-general.js | 59 +----------- gui/app/components/layout/logo-heading.js | 13 ++- gui/app/helpers/random-id.js | 19 ++++ gui/app/pods/auth/forgot/template.hbs | 5 +- gui/app/pods/auth/login/template.hbs | 5 +- gui/app/pods/auth/reset/template.hbs | 5 +- gui/app/pods/customize/general/controller.js | 6 +- gui/app/pods/customize/general/template.hbs | 4 +- gui/app/pods/folder/index/template.hbs | 8 +- gui/app/pods/folders/controller.js | 25 ++++- gui/app/pods/folders/route.js | 2 + gui/app/pods/folders/template.hbs | 57 +++++++++-- gui/app/services/icon.js | 69 ++++++++++++++ gui/app/services/organization.js | 7 ++ gui/app/styles/core/layout/headings.scss | 2 +- gui/app/styles/core/view/customize.scss | 6 ++ gui/app/styles/core/view/space.scss | 2 +- gui/app/styles/core/view/spaces.scss | 4 +- .../components/customize/general-settings.hbs | 94 ++++++++++--------- .../components/customize/space-labels.hbs | 2 +- .../components/folder/settings-general.hbs | 2 +- .../components/layout/logo-heading.hbs | 5 + model/audit/audit.go | 1 + model/org/meta.go | 1 - model/space/space.go | 3 + server/routing/routes.go | 2 + 36 files changed, 574 insertions(+), 211 deletions(-) create mode 100644 domain/backup/models.go create mode 100644 gui/app/helpers/random-id.js create mode 100644 gui/app/services/icon.js diff --git a/domain/backup/backup.go b/domain/backup/backup.go index 638faa68..27f0cb2d 100644 --- a/domain/backup/backup.go +++ b/domain/backup/backup.go @@ -46,7 +46,6 @@ import ( "github.com/documize/community/model/doc" "github.com/documize/community/model/group" "github.com/documize/community/model/link" - "github.com/documize/community/model/org" "github.com/documize/community/model/page" "github.com/documize/community/model/permission" "github.com/documize/community/model/pin" @@ -231,14 +230,14 @@ func (b backerHandler) dmzOrg(files *[]backupItem) (err error) { w = fmt.Sprintf(" WHERE c_refid='%s' ", b.Spec.OrgID) } - o := []org.Organization{} + o := []orgExtended{} err = b.Runtime.Db.Select(&o, `SELECT id, c_refid AS refid, c_title AS title, c_message AS message, c_domain AS domain, c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active, - c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, + c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, coalesce(c_sub,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS subscription, coalesce(c_authconfig,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS authconfig, c_maxtags AS maxtags, - c_created AS created, c_revised AS revised + c_theme AS theme, c_logo AS logo, c_created AS created, c_revised AS revised FROM dmz_org`+w) if err != nil { return @@ -255,10 +254,6 @@ func (b backerHandler) dmzOrg(files *[]backupItem) (err error) { // Config, User Config. func (b backerHandler) dmzConfig(files *[]backupItem) (err error) { - type config struct { - ConfigKey string `json:"key"` - ConfigValue string `json:"config"` - } c := []config{} err = b.Runtime.Db.Select(&c, `SELECT c_key AS configkey, c_config AS configvalue FROM dmz_config`) if err != nil { @@ -279,14 +274,7 @@ func (b backerHandler) dmzConfig(files *[]backupItem) (err error) { w = fmt.Sprintf(" where c_orgid='%s' ", b.Spec.OrgID) } - type userConfig struct { - OrgID string `json:"orgId"` - UserID string `json:"userId"` - ConfigKey string `json:"key"` - ConfigValue string `json:"config"` - } uc := []userConfig{} - err = b.Runtime.Db.Select(&uc, `select c_orgid AS orgid, c_userid AS userid, c_key AS configkey, c_config AS configvalue FROM dmz_user_config`+w) if err != nil { @@ -475,6 +463,8 @@ func (b backerHandler) dmzSpace(files *[]backupItem) (err error) { err = b.Runtime.Db.Select(&sp, `SELECT id, c_refid AS refid, c_name AS name, c_orgid AS orgid, c_userid AS userid, c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes, + c_icon AS icon, c_labelid AS labelid, c_desc AS description, + c_count_category As countcategory, c_count_content AS countcontent, c_created AS created, c_revised AS revised FROM dmz_space`+w) if err != nil { @@ -671,16 +661,6 @@ func (b backerHandler) dmzDocument(files *[]backupItem) (err error) { *files = append(*files, backupItem{Filename: "dmz_doc.json", Content: content}) // Vote - type vote struct { - RefID string `json:"refId"` - OrgID string `json:"orgId"` - DocumentID string `json:"documentId"` - VoterID string `json:"voterId"` - Vote int `json:"vote"` - Created time.Time `json:"created"` - Revised time.Time `json:"revised"` - } - vt := []vote{} err = b.Runtime.Db.Select(&vt, ` SELECT c_refid AS refid, c_orgid AS orgid, @@ -716,16 +696,6 @@ func (b backerHandler) dmzDocument(files *[]backupItem) (err error) { *files = append(*files, backupItem{Filename: "dmz_doc_link.json", Content: content}) // Comment - type comment struct { - RefID string `json:"feedbackId"` - OrgID string `json:"orgId"` - DocumentID string `json:"documentId"` - UserID string `json:"userId"` - Email string `json:"email"` - Feedback string `json:"feedback"` - Created time.Time `json:"created"` - } - cm := []comment{} err = b.Runtime.Db.Select(&cm, ` SELECT c_refid AS refid, c_orgid AS orgid, c_docid AS documentid, @@ -743,20 +713,6 @@ func (b backerHandler) dmzDocument(files *[]backupItem) (err error) { *files = append(*files, backupItem{Filename: "dmz_doc_comment.json", Content: content}) // Share - type share struct { - ID uint64 `json:"id"` - OrgID string `json:"orgId"` - UserID string `json:"userId"` - DocumentID string `json:"documentId"` - Email string `json:"email"` - Message string `json:"message"` - Viewed string `json:"viewed"` // recording each view as |date-viewed|date-viewed| - Secret string `json:"secret"` // secure token used to access document - Expires string `json:"expires"` // number of days from creation, value of 0 means never - Active bool `json:"active"` - Created time.Time `json:"created"` - } - sh := []share{} err = b.Runtime.Db.Select(&sh, ` SELECT id AS id, c_orgid AS orgid, c_docid AS documentid, diff --git a/domain/backup/models.go b/domain/backup/models.go new file mode 100644 index 00000000..01f13483 --- /dev/null +++ b/domain/backup/models.go @@ -0,0 +1,78 @@ +// 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 + +// Package backup handle data backup/restore to/from ZIP format. +package backup + +// Existing data models do not necessarily have fields to hold +// all data when loaded from the database. +// So we extend the existing models to hold additional fields +// for a complete backup and restore process. + +import ( + "time" + + "github.com/documize/community/model/org" +) + +type orgExtended struct { + org.Organization + Logo []byte `json:"logo"` +} + +type config struct { + ConfigKey string `json:"key"` + ConfigValue string `json:"config"` +} + +type userConfig struct { + OrgID string `json:"orgId"` + UserID string `json:"userId"` + ConfigKey string `json:"key"` + ConfigValue string `json:"config"` +} + +// Vote +type vote struct { + RefID string `json:"refId"` + OrgID string `json:"orgId"` + DocumentID string `json:"documentId"` + VoterID string `json:"voterId"` + Vote int `json:"vote"` + Created time.Time `json:"created"` + Revised time.Time `json:"revised"` +} + +// Comment +type comment struct { + RefID string `json:"feedbackId"` + OrgID string `json:"orgId"` + DocumentID string `json:"documentId"` + UserID string `json:"userId"` + Email string `json:"email"` + Feedback string `json:"feedback"` + Created time.Time `json:"created"` +} + +// Share +type share struct { + ID uint64 `json:"id"` + OrgID string `json:"orgId"` + UserID string `json:"userId"` + DocumentID string `json:"documentId"` + Email string `json:"email"` + Message string `json:"message"` + Viewed string `json:"viewed"` // recording each view as |date-viewed|date-viewed| + Secret string `json:"secret"` // secure token used to access document + Expires string `json:"expires"` // number of days from creation, value of 0 means never + Active bool `json:"active"` + Created time.Time `json:"created"` +} diff --git a/domain/backup/restore.go b/domain/backup/restore.go index 2930dbcf..50d2502c 100644 --- a/domain/backup/restore.go +++ b/domain/backup/restore.go @@ -39,7 +39,6 @@ import ( "github.com/documize/community/model/doc" "github.com/documize/community/model/group" "github.com/documize/community/model/link" - "github.com/documize/community/model/org" "github.com/documize/community/model/page" "github.com/documize/community/model/permission" "github.com/documize/community/model/pin" @@ -332,7 +331,7 @@ func (r *restoreHandler) readZip(filename string) (found bool, b []byte, err err func (r *restoreHandler) dmzOrg() (err error) { filename := "dmz_org.json" - org := []org.Organization{} + org := []orgExtended{} err = r.fileJSON(filename, &org) if err != nil { err = errors.Wrap(err, fmt.Sprintf("failed to load %s", filename)) @@ -363,12 +362,14 @@ func (r *restoreHandler) dmzOrg() (err error) { _, 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_sub, c_active, c_created, c_revised) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), + c_maxtags, c_verified, c_serial, c_sub, c_active, + c_theme, c_logo, 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].Subscription, + org[i].Theme, org[i].Logo, org[i].Active, org[i].Created, org[i].Revised) if err != nil { r.Context.Transaction.Rollback() @@ -402,6 +403,7 @@ func (r *restoreHandler) dmzOrg() (err error) { org[0].Serial = r.Spec.Org.Serial org[0].Title = r.Spec.Org.Title org[0].Subscription = r.Spec.Org.Subscription + org[0].Theme = r.Spec.Org.Theme } _, err = r.Context.Transaction.NamedExec(`UPDATE dmz_org SET @@ -612,8 +614,16 @@ func (r *restoreHandler) dmzSpace() (err error) { } 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, r.remapOrg(sp[i].OrgID), r.remapUser(sp[i].UserID), sp[i].Type, sp[i].Lifecycle, sp[i].Likes, sp[i].Created, sp[i].Revised) + _, 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_icon, c_desc, c_count_category, c_count_content, + c_labelid, c_created, c_revised) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), + sp[i].RefID, sp[i].Name, r.remapOrg(sp[i].OrgID), + r.remapUser(sp[i].UserID), sp[i].Type, sp[i].Lifecycle, + sp[i].Likes, sp[i].Icon, sp[i].Description, sp[i].CountCategory, + sp[i].CountContent, sp[i].LabelID, sp[i].Created, sp[i].Revised) if err != nil { r.Context.Transaction.Rollback() diff --git a/domain/meta/endpoint.go b/domain/meta/endpoint.go index 5cc47e0d..c4fe8cad 100644 --- a/domain/meta/endpoint.go +++ b/domain/meta/endpoint.go @@ -13,6 +13,7 @@ package meta import ( "bytes" + "encoding/base64" "fmt" "net/http" "strings" @@ -301,3 +302,41 @@ func (h *Handler) Themes(w http.ResponseWriter, r *http.Request) { response.WriteJSON(w, th) } + +// Logo returns site logo based upon request domain (e.g. acme.documize.com). +// The default Documize logo is returned if organization has not uploaded +// their own logo. +func (h *Handler) Logo(w http.ResponseWriter, r *http.Request) { + ctx := domain.GetRequestContext(r) + d := organization.GetSubdomainFromHost(r) + + logo, err := h.Store.Organization.Logo(ctx, d) + if err != nil { + h.Runtime.Log.Infof("unable to fetch logo for domain %s", d) + response.WriteNotFound(w) + return + } + + if len(string(logo)) == 0 { + logo, err = base64.StdEncoding.DecodeString(defaultLogo) + if err != nil { + h.Runtime.Log.Error("unable to decode default logo", err) + response.WriteEmpty(w) + return + } + } + + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(logo))) + w.WriteHeader(http.StatusOK) + + _, err = w.Write(logo) + if err != nil { + h.Runtime.Log.Error("failed to write org logo", err) + return + } +} + +var defaultLogo = ` +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 +` diff --git a/domain/organization/endpoint.go b/domain/organization/endpoint.go index 1bb2b9bc..b96ba761 100644 --- a/domain/organization/endpoint.go +++ b/domain/organization/endpoint.go @@ -12,8 +12,11 @@ package organization import ( + "bytes" "database/sql" "encoding/json" + "github.com/documize/community/model/audit" + "io" "io/ioutil" "net/http" @@ -99,3 +102,50 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { response.WriteJSON(w, org) } + +// UploadLogo stores log for organization. +func (h *Handler) UploadLogo(w http.ResponseWriter, r *http.Request) { + method := "organization.UploadLogo" + ctx := domain.GetRequestContext(r) + + if !ctx.Administrator { + response.WriteForbiddenError(w) + return + } + + // We use default logo if body is empty. + logo := []byte{} + + filedata, _, err := r.FormFile("attachment") + if err == nil { + b := new(bytes.Buffer) + _, err = io.Copy(b, filedata) + if err != nil { + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + } else { + logo = b.Bytes() + } + } + + ctx.Transaction, err = h.Runtime.Db.Beginx() + if err != nil { + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + + err = h.Store.Organization.UploadLogo(ctx, logo) + if err != nil { + ctx.Transaction.Rollback() + response.WriteServerError(w, method, err) + h.Runtime.Log.Error(method, err) + return + } + + ctx.Transaction.Commit() + + h.Store.Audit.Record(ctx, audit.EventTypeOrganizationLogo) + + response.WriteEmpty(w) +} diff --git a/domain/organization/store.go b/domain/organization/store.go index 0547cf64..0c7ad275 100644 --- a/domain/organization/store.go +++ b/domain/organization/store.go @@ -52,7 +52,7 @@ func (s Store) GetOrganization(ctx domain.RequestContext, id string) (org org.Or c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription, - c_maxtags AS maxtags, c_created AS created, c_revised AS revised, c_theme AS theme + c_maxtags AS maxtags, c_theme AS theme, c_created AS created, c_revised AS revised FROM dmz_org WHERE c_refid=?`), id) @@ -176,3 +176,31 @@ func (s Store) CheckDomain(ctx domain.RequestContext, domain string) string { return "" } + +// Logo fetchs stored image from store or NULL. +func (s Store) Logo(ctx domain.RequestContext, domain string) (l []byte, err error) { + row := s.Runtime.Db.QueryRow(s.Bind("SELECT c_logo FROM dmz_org WHERE c_domain=? AND c_active=true"), domain) + + err = row.Scan(&l) + if err == sql.ErrNoRows { + err = nil + return nil, nil + } + if err != nil { + return nil, err + } + + return l, nil +} + +// UploadLogo saves custom logo to the organization record. +func (s Store) UploadLogo(ctx domain.RequestContext, logo []byte) (err error) { + _, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_org SET c_logo=?, c_revised=? WHERE c_refid=?"), + logo, time.Now().UTC(), ctx.OrgID) + + if err != nil { + err = errors.Wrap(err, fmt.Sprintf("unable to save custom logo for org %s", ctx.OrgID)) + } + + return +} diff --git a/domain/space/endpoint.go b/domain/space/endpoint.go index 2cb0bb95..f9b86e70 100644 --- a/domain/space/endpoint.go +++ b/domain/space/endpoint.go @@ -99,6 +99,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { var sp space.Space sp.Name = model.Name + sp.Description = model.Description + sp.Icon = model.Icon + sp.LabelID = model.LabelID sp.RefID = uniqueid.Generate() sp.OrgID = ctx.OrgID sp.UserID = ctx.UserID @@ -675,52 +678,52 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { ok := true ctx.Transaction, ok = h.Runtime.StartTx() - if !ok { + if !ok { response.WriteError(w, method) return } _, err := h.Store.Document.DeleteBySpace(ctx, id) if err != nil { - h.Runtime.Rollback(ctx.Transaction) + h.Runtime.Rollback(ctx.Transaction) response.WriteServerError(w, method, err) return } _, err = h.Store.Permission.DeleteSpacePermissions(ctx, id) if err != nil { - h.Runtime.Rollback(ctx.Transaction) - response.WriteServerError(w, method, err) + h.Runtime.Rollback(ctx.Transaction) + response.WriteServerError(w, method, err) return } // remove category permissions _, err = h.Store.Permission.DeleteSpaceCategoryPermissions(ctx, id) if err != nil { - h.Runtime.Rollback(ctx.Transaction) - response.WriteServerError(w, method, err) + h.Runtime.Rollback(ctx.Transaction) + response.WriteServerError(w, method, err) return } _, err = h.Store.Pin.DeletePinnedSpace(ctx, id) if err != nil && err != sql.ErrNoRows { - h.Runtime.Rollback(ctx.Transaction) - response.WriteServerError(w, method, err) + h.Runtime.Rollback(ctx.Transaction) + response.WriteServerError(w, method, err) return } // remove category and members for space _, err = h.Store.Category.DeleteBySpace(ctx, id) if err != nil { - h.Runtime.Rollback(ctx.Transaction) - response.WriteServerError(w, method, err) + h.Runtime.Rollback(ctx.Transaction) + response.WriteServerError(w, method, err) return } _, err = h.Store.Space.Delete(ctx, id) if err != nil { - h.Runtime.Rollback(ctx.Transaction) - response.WriteServerError(w, method, err) + h.Runtime.Rollback(ctx.Transaction) + response.WriteServerError(w, method, err) return } @@ -728,22 +731,22 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { h.Runtime.Commit(ctx.Transaction) // Record this action. - ctx.Transaction, ok = h.Runtime.StartTx() - if !ok { - response.WriteError(w, method) - return - } + ctx.Transaction, ok = h.Runtime.StartTx() + if !ok { + response.WriteError(w, method) + return + } err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ SpaceID: id, SourceType: activity.SourceTypeSpace, ActivityType: activity.TypeDeleted}) if err != nil { - h.Runtime.Rollback(ctx.Transaction) - response.WriteServerError(w, method, err) + h.Runtime.Rollback(ctx.Transaction) + response.WriteServerError(w, method, err) } - h.Runtime.Commit(ctx.Transaction) + h.Runtime.Commit(ctx.Transaction) h.Store.Audit.Record(ctx, audit.EventTypeSpaceDelete) diff --git a/domain/space/store.go b/domain/space/store.go index 10dd96d1..83ddcc3b 100644 --- a/domain/space/store.go +++ b/domain/space/store.go @@ -30,7 +30,12 @@ type Store struct { // Add adds new folder into the store. func (s Store) Add(ctx domain.RequestContext, sp space.Space) (err error) { - _, 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_icon, c_desc, c_count_category, c_count_content, c_labelid, 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_icon, c_desc, c_count_category, c_count_content, + c_labelid, c_created, c_revised) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), sp.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Lifecycle, sp.Likes, sp.Icon, sp.Description, sp.CountCategory, sp.CountContent, sp.LabelID, sp.Created, sp.Revised) diff --git a/domain/store/storer.go b/domain/store/storer.go index 3995bf66..3d058fa6 100644 --- a/domain/store/storer.go +++ b/domain/store/storer.go @@ -154,6 +154,8 @@ type OrganizationStorer interface { RemoveOrganization(ctx domain.RequestContext, orgID string) (err error) UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error) CheckDomain(ctx domain.RequestContext, domain string) string + Logo(ctx domain.RequestContext, domain string) (l []byte, err error) + UploadLogo(ctx domain.RequestContext, l []byte) (err error) } // PinStorer defines required methods for pin management diff --git a/gui/app/components/customize/general-settings.js b/gui/app/components/customize/general-settings.js index e2742836..42aac071 100644 --- a/gui/app/components/customize/general-settings.js +++ b/gui/app/components/customize/general-settings.js @@ -32,6 +32,51 @@ export default Component.extend(Notifier, { this.set('maxTags', this.get('model.general.maxTags')); }, + didInsertElement() { + this._super(...arguments); + + let self = this; + let url = this.get('appMeta.endpoint'); + let orgId = this.get('appMeta.orgId'); + let uploadUrl = `${url}/organization/${orgId}/logo`; + + let dzone = new Dropzone("#upload-logo > div", { + headers: { + 'Authorization': 'Bearer ' + self.get('session.authToken') + }, + url: uploadUrl, + method: "post", + paramName: 'attachment', + clickable: true, + maxFilesize: 50, + parallelUploads: 1, + uploadMultiple: false, + addRemoveLinks: false, + autoProcessQueue: true, + createImageThumbnails: false, + + init: function () { + this.on("success", function (/*file, response*/ ) { + }); + + this.on("queuecomplete", function () { + self.notifySuccess('Logo uploaded'); + }); + + this.on("error", function (error, msg) { + self.notifyError(msg); + self.notifyError(error); + }); + } + }); + + dzone.on("complete", function (file) { + dzone.removeFile(file); + }); + + this.set('drop', dzone); + }, + actions: { change() { const selectEl = this.$('#maxTags')[0]; @@ -63,7 +108,7 @@ export default Component.extend(Notifier, { this.set('model.general.maxTags', this.get('maxTags')); - this.get('save')().then(() => { + this.get('onUpdate')().then(() => { this.notifySuccess('Saved'); set(this, 'titleError', false); set(this, 'messageError', false); @@ -74,6 +119,11 @@ export default Component.extend(Notifier, { onThemeChange(theme) { this.get('appMeta').setTheme(theme); this.set('model.general.theme', theme); + }, + + onDefaultLogo() { + this.get('onDefaultLogo')(this.get('appMeta.orgId')); + this.notifySuccess('Using default logo'); } } }); diff --git a/gui/app/components/folder/settings-general.js b/gui/app/components/folder/settings-general.js index bc6f70c3..2d79a071 100644 --- a/gui/app/components/folder/settings-general.js +++ b/gui/app/components/folder/settings-general.js @@ -20,6 +20,7 @@ import Component from '@ember/component'; export default Component.extend(AuthMixin, Notifier, { router: service(), spaceSvc: service('folder'), + iconSvc: service('icon'), localStorage: service('localStorage'), isSpaceAdmin: computed('permissions', function() { return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage'); @@ -40,7 +41,7 @@ export default Component.extend(AuthMixin, Notifier, { init() { this._super(...arguments); - this.populateIconList(); + this.set('iconList', this.get('iconSvc').getSpaceIconList()); }, didReceiveAttrs() { @@ -76,62 +77,6 @@ export default Component.extend(AuthMixin, Notifier, { this.set('spaceIcon', icon); }, - populateIconList() { - let list = this.get('iconList'); - let constants = this.get('constants'); - - list = A([]); - - list.pushObject(constants.IconMeta.Star); - list.pushObject(constants.IconMeta.Support); - list.pushObject(constants.IconMeta.Message); - list.pushObject(constants.IconMeta.Apps); - list.pushObject(constants.IconMeta.Box); - list.pushObject(constants.IconMeta.Gift); - list.pushObject(constants.IconMeta.Design); - list.pushObject(constants.IconMeta.Bulb); - list.pushObject(constants.IconMeta.Metrics); - list.pushObject(constants.IconMeta.PieChart); - list.pushObject(constants.IconMeta.BarChart); - list.pushObject(constants.IconMeta.Finance); - list.pushObject(constants.IconMeta.Lab); - list.pushObject(constants.IconMeta.Code); - list.pushObject(constants.IconMeta.Help); - list.pushObject(constants.IconMeta.Manuals); - list.pushObject(constants.IconMeta.Flow); - list.pushObject(constants.IconMeta.Out); - list.pushObject(constants.IconMeta.In); - list.pushObject(constants.IconMeta.Partner); - list.pushObject(constants.IconMeta.Org); - list.pushObject(constants.IconMeta.Home); - list.pushObject(constants.IconMeta.Infinite); - list.pushObject(constants.IconMeta.Todo); - list.pushObject(constants.IconMeta.Procedure); - list.pushObject(constants.IconMeta.Outgoing); - list.pushObject(constants.IconMeta.Incoming); - list.pushObject(constants.IconMeta.Travel); - list.pushObject(constants.IconMeta.Winner); - list.pushObject(constants.IconMeta.Roadmap); - list.pushObject(constants.IconMeta.Money); - list.pushObject(constants.IconMeta.Security); - list.pushObject(constants.IconMeta.Tune); - list.pushObject(constants.IconMeta.Guide); - list.pushObject(constants.IconMeta.Smile); - list.pushObject(constants.IconMeta.Rocket); - list.pushObject(constants.IconMeta.Time); - list.pushObject(constants.IconMeta.Cup); - list.pushObject(constants.IconMeta.Marketing); - list.pushObject(constants.IconMeta.Announce); - list.pushObject(constants.IconMeta.Devops); - list.pushObject(constants.IconMeta.World); - list.pushObject(constants.IconMeta.Plan); - list.pushObject(constants.IconMeta.Components); - list.pushObject(constants.IconMeta.People); - list.pushObject(constants.IconMeta.Checklist); - - this.set('iconList', list); - }, - actions: { onSetSpaceType(t) { this.set('spaceType', t); diff --git a/gui/app/components/layout/logo-heading.js b/gui/app/components/layout/logo-heading.js index 76877524..f47a136b 100644 --- a/gui/app/components/layout/logo-heading.js +++ b/gui/app/components/layout/logo-heading.js @@ -9,9 +9,20 @@ // // https://documize.com +import { inject as service } from '@ember/service'; import Component from '@ember/component'; export default Component.extend({ + appMeta: service(), icon: null, - meta: null + meta: null, + logo: false, + + didReceiveAttrs() { + this._super(...arguments); + if (this.get('logo')) { + let cb = + new Date(); + this.set('cacheBuster', cb); + } + } }); diff --git a/gui/app/helpers/random-id.js b/gui/app/helpers/random-id.js new file mode 100644 index 00000000..ad209173 --- /dev/null +++ b/gui/app/helpers/random-id.js @@ -0,0 +1,19 @@ +// 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 stringUtil from '../utils/string'; +import { helper } from '@ember/component/helper'; + +// Usage: {{random-id}} +export default helper(function() { + return stringUtil.makeId(10); +}); + diff --git a/gui/app/pods/auth/forgot/template.hbs b/gui/app/pods/auth/forgot/template.hbs index 667e145a..eb97bb25 100644 --- a/gui/app/pods/auth/forgot/template.hbs +++ b/gui/app/pods/auth/forgot/template.hbs @@ -1,8 +1,9 @@