mirror of
https://github.com/documize/community.git
synced 2025-07-19 13:19:43 +02:00
Improve Space permissions
Closes out loopholes that allowed managers to kick owners.
This commit is contained in:
parent
09635b67ab
commit
5d632712e0
30 changed files with 1015 additions and 877 deletions
|
@ -58,9 +58,9 @@ Space view.
|
||||||
|
|
||||||
## Latest Release
|
## Latest Release
|
||||||
|
|
||||||
[Community Edition: v1.76.0](https://github.com/documize/community/releases)
|
[Community Edition: v1.76.1](https://github.com/documize/community/releases)
|
||||||
|
|
||||||
[Enterprise Edition: v1.76.0](https://documize.com/downloads)
|
[Enterprise Edition: v1.76.1](https://documize.com/downloads)
|
||||||
|
|
||||||
## OS support
|
## OS support
|
||||||
|
|
||||||
|
|
|
@ -504,8 +504,11 @@ func (h *Handler) FetchSpaceData(w http.ResponseWriter, r *http.Request) {
|
||||||
fetch := category.FetchSpaceModel{}
|
fetch := category.FetchSpaceModel{}
|
||||||
|
|
||||||
// get space categories visible to user
|
// get space categories visible to user
|
||||||
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
|
var cat []category.Category
|
||||||
if err != nil && err != sql.ErrNoRows {
|
var err error
|
||||||
|
|
||||||
|
cat, err = h.Store.Category.GetBySpace(ctx, spaceID)
|
||||||
|
if err != nil {
|
||||||
h.Runtime.Log.Error("get space categories visible to user failed", err)
|
h.Runtime.Log.Error("get space categories visible to user failed", err)
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
return
|
return
|
||||||
|
@ -527,7 +530,7 @@ func (h *Handler) FetchSpaceData(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// get category membership records
|
// get category membership records
|
||||||
member, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
|
member, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil {
|
||||||
h.Runtime.Log.Error("get document category membership for space", err)
|
h.Runtime.Log.Error("get document category membership for space", err)
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -51,6 +51,8 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
org.StripSecrets()
|
||||||
|
|
||||||
response.WriteJSON(w, org)
|
response.WriteJSON(w, org)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,8 @@ type Store struct {
|
||||||
func (s Store) AddOrganization(ctx domain.RequestContext, o org.Organization) (err error) {
|
func (s Store) AddOrganization(ctx domain.RequestContext, o org.Organization) (err error) {
|
||||||
_, 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_sub, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
|
_, 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_sub, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
|
||||||
o.RefID, o.Company, o.Title, o.Message, strings.ToLower(o.Domain),
|
o.RefID, o.Company, o.Title, o.Message, strings.ToLower(o.Domain),
|
||||||
strings.ToLower(o.Email), o.AllowAnonymousAccess, o.Serial, o.MaxTags, o.Subscription, o.Created, o.Revised)
|
strings.ToLower(o.Email), o.AllowAnonymousAccess, o.Serial, o.MaxTags,
|
||||||
|
o.Subscription, o.Created, o.Revised)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "unable to execute insert for org")
|
err = errors.Wrap(err, "unable to execute insert for org")
|
||||||
|
@ -43,13 +44,14 @@ func (s Store) AddOrganization(ctx domain.RequestContext, o org.Organization) (e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrganization returns the Organization reocrod from the organization database table with the given id.
|
// GetOrganization returns the Organization record from the organization database table with the given id.
|
||||||
func (s Store) GetOrganization(ctx domain.RequestContext, id string) (org org.Organization, err error) {
|
func (s Store) GetOrganization(ctx domain.RequestContext, id string) (org org.Organization, err error) {
|
||||||
err = s.Runtime.Db.Get(&org, s.Bind(`SELECT id, c_refid AS refid,
|
err = s.Runtime.Db.Get(&org, s.Bind(`SELECT id, c_refid AS refid,
|
||||||
c_title AS title, c_message AS message, c_domain AS domain,
|
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_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_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
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_maxtags AS maxtags, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_org
|
FROM dmz_org
|
||||||
WHERE c_refid=?`),
|
WHERE c_refid=?`),
|
||||||
|
@ -80,7 +82,8 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
|
||||||
c_title AS title, c_message AS message, c_domain AS domain,
|
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_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_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
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_maxtags AS maxtags, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_org
|
FROM dmz_org
|
||||||
WHERE c_domain=? AND c_active=true`),
|
WHERE c_domain=? AND c_active=true`),
|
||||||
|
@ -95,7 +98,8 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
|
||||||
c_title AS title, c_message AS message, c_domain AS domain,
|
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_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_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
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_maxtags AS maxtags, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_org
|
FROM dmz_org
|
||||||
WHERE c_domain='' AND c_active=true`))
|
WHERE c_domain='' AND c_active=true`))
|
||||||
|
|
|
@ -120,11 +120,6 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
|
|
||||||
me := false
|
|
||||||
hasEveryoneRole := false
|
|
||||||
roleCount := 0
|
|
||||||
|
|
||||||
// Permissions can be assigned to both groups and individual users.
|
// Permissions can be assigned to both groups and individual users.
|
||||||
// Pre-fetch users with group membership to help us work out
|
// Pre-fetch users with group membership to help us work out
|
||||||
// if user belongs to a group with permissions.
|
// if user belongs to a group with permissions.
|
||||||
|
@ -136,6 +131,18 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// url is sent in 'space shared with you' invitation emails.
|
||||||
|
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
|
||||||
|
// me tracks if the user who changed permissions, also has some space permissions.
|
||||||
|
me := false
|
||||||
|
// hasEveryRole tracks if "everyone" has been give access to space.
|
||||||
|
hasEveryoneRole := false
|
||||||
|
// hasOwner tracks is at least one person or user group has been marked as space owner.
|
||||||
|
hasOwner := false
|
||||||
|
// roleCount tracks the number of permission records created for this space.
|
||||||
|
// It's used to determine if space has multiple participants, see below.
|
||||||
|
roleCount := 0
|
||||||
|
|
||||||
for _, perm := range model.Permissions {
|
for _, perm := range model.Permissions {
|
||||||
perm.OrgID = ctx.OrgID
|
perm.OrgID = ctx.OrgID
|
||||||
perm.SpaceID = id
|
perm.SpaceID = id
|
||||||
|
@ -154,6 +161,12 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
||||||
me = true
|
me = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect is we have at least one space owner permission.
|
||||||
|
// Result used below to prevent lock-outs.
|
||||||
|
if hasOwner == false && perm.SpaceOwner {
|
||||||
|
hasOwner = true
|
||||||
|
}
|
||||||
|
|
||||||
// Only persist if there is a role!
|
// Only persist if there is a role!
|
||||||
if permission.HasAnyPermission(perm) {
|
if permission.HasAnyPermission(perm) {
|
||||||
// identify publically shared spaces
|
// identify publically shared spaces
|
||||||
|
@ -209,7 +222,31 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we need to ensure permissions for space owner when shared?
|
// Catch and prevent lock-outs so we don't have
|
||||||
|
// zombie spaces that nobody can access.
|
||||||
|
if len(model.Permissions) == 0 {
|
||||||
|
// When no permissions are assigned we
|
||||||
|
// default to current user as being owner and viewer.
|
||||||
|
perm := permission.Permission{}
|
||||||
|
perm.OrgID = ctx.OrgID
|
||||||
|
perm.Who = permission.UserPermission
|
||||||
|
perm.WhoID = ctx.UserID
|
||||||
|
perm.Scope = permission.ScopeRow
|
||||||
|
perm.Location = permission.LocationSpace
|
||||||
|
perm.RefID = id
|
||||||
|
perm.Action = "" // we send allowable actions in function call...
|
||||||
|
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceOwner, permission.SpaceView)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Transaction.Rollback()
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// So we have permissions but we must check for at least one space owner.
|
||||||
|
if !hasOwner {
|
||||||
|
// So we have no space owner, make current user the owner
|
||||||
|
// if we have no permssions thus far.
|
||||||
if !me {
|
if !me {
|
||||||
perm := permission.Permission{}
|
perm := permission.Permission{}
|
||||||
perm.OrgID = ctx.OrgID
|
perm.OrgID = ctx.OrgID
|
||||||
|
@ -218,9 +255,8 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
||||||
perm.Scope = permission.ScopeRow
|
perm.Scope = permission.ScopeRow
|
||||||
perm.Location = permission.LocationSpace
|
perm.Location = permission.LocationSpace
|
||||||
perm.RefID = id
|
perm.RefID = id
|
||||||
perm.Action = "" // we send array for actions below
|
perm.Action = "" // we send allowable actions in function call...
|
||||||
|
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceOwner, permission.SpaceView)
|
||||||
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceView, permission.SpaceManage)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Transaction.Rollback()
|
ctx.Transaction.Rollback()
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
|
@ -228,6 +264,8 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mark up space type as either public, private or restricted access.
|
// Mark up space type as either public, private or restricted access.
|
||||||
if hasEveryoneRole {
|
if hasEveryoneRole {
|
||||||
|
|
|
@ -116,24 +116,35 @@ func (s Store) GetViewable(ctx domain.RequestContext) (sp []space.Space, err err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAll for admin users!
|
// AdminList returns all shared spaces and orphaned spaces that have no owner.
|
||||||
func (s Store) GetAll(ctx domain.RequestContext) (sp []space.Space, err error) {
|
func (s Store) AdminList(ctx domain.RequestContext) (sp []space.Space, err error) {
|
||||||
qry := s.Bind(`SELECT id, c_refid AS refid,
|
qry := s.Bind(`
|
||||||
|
SELECT id, c_refid AS refid,
|
||||||
c_name AS name, c_orgid AS orgid, c_userid AS userid,
|
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_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
|
||||||
c_created AS created, c_revised AS revised
|
c_created AS created, c_revised AS revised
|
||||||
FROM dmz_space
|
FROM dmz_space
|
||||||
WHERE c_orgid=?
|
WHERE c_orgid=? AND (c_type=? OR c_type=?)
|
||||||
ORDER BY c_name`)
|
UNION ALL
|
||||||
|
SELECT id, c_refid AS refid,
|
||||||
err = s.Runtime.Db.Select(&sp, qry, ctx.OrgID)
|
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_created AS created, c_revised AS revised
|
||||||
|
FROM dmz_space
|
||||||
|
WHERE c_orgid=? AND (c_type=? OR c_type=?) AND c_refid NOT IN
|
||||||
|
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_action='own')
|
||||||
|
ORDER BY name`)
|
||||||
|
|
||||||
|
err = s.Runtime.Db.Select(&sp, qry,
|
||||||
|
ctx.OrgID, space.ScopePublic, space.ScopeRestricted,
|
||||||
|
ctx.OrgID, space.ScopePublic, space.ScopeRestricted,
|
||||||
|
ctx.OrgID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
err = nil
|
err = nil
|
||||||
sp = []space.Space{}
|
sp = []space.Space{}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("failed space.GetAll org %s", ctx.OrgID))
|
err = errors.Wrap(err, fmt.Sprintf("failed space.AdminList org %s", ctx.OrgID))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -59,9 +59,9 @@ type SpaceStorer interface {
|
||||||
Get(ctx domain.RequestContext, id string) (sp space.Space, err error)
|
Get(ctx domain.RequestContext, id string) (sp space.Space, err error)
|
||||||
PublicSpaces(ctx domain.RequestContext, orgID string) (sp []space.Space, err error)
|
PublicSpaces(ctx domain.RequestContext, orgID string) (sp []space.Space, err error)
|
||||||
GetViewable(ctx domain.RequestContext) (sp []space.Space, err error)
|
GetViewable(ctx domain.RequestContext) (sp []space.Space, err error)
|
||||||
GetAll(ctx domain.RequestContext) (sp []space.Space, err error)
|
|
||||||
Update(ctx domain.RequestContext, sp space.Space) (err error)
|
Update(ctx domain.RequestContext, sp space.Space) (err error)
|
||||||
Delete(ctx domain.RequestContext, id string) (rows int64, err error)
|
Delete(ctx domain.RequestContext, id string) (rows int64, err error)
|
||||||
|
AdminList(ctx domain.RequestContext) (sp []space.Space, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CategoryStorer defines required methods for category and category membership management
|
// CategoryStorer defines required methods for category and category membership management
|
||||||
|
|
|
@ -41,8 +41,8 @@ func main() {
|
||||||
rt.Product = domain.Product{}
|
rt.Product = domain.Product{}
|
||||||
rt.Product.Major = "1"
|
rt.Product.Major = "1"
|
||||||
rt.Product.Minor = "76"
|
rt.Product.Minor = "76"
|
||||||
rt.Product.Patch = "0"
|
rt.Product.Patch = "1"
|
||||||
rt.Product.Revision = 181113144232
|
rt.Product.Revision = 181116171324
|
||||||
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
||||||
rt.Product.Edition = domain.CommunityEdition
|
rt.Product.Edition = domain.CommunityEdition
|
||||||
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)
|
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)
|
||||||
|
|
1350
embed/bindata.go
1350
embed/bindata.go
File diff suppressed because one or more lines are too long
|
@ -22,6 +22,7 @@ export default Component.extend(Notifier, Modals, {
|
||||||
subscription: null,
|
subscription: null,
|
||||||
planCloud: false,
|
planCloud: false,
|
||||||
planSelfhost: false,
|
planSelfhost: false,
|
||||||
|
comment: 'Nothing in particular -- just passing through. Please close my Documize account.',
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
@ -47,10 +48,17 @@ export default Component.extend(Notifier, Modals, {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onRequestClosure() {
|
||||||
|
this.modalOpen("#deactivation-request-modal", {"show": true}, '#close-comment');
|
||||||
|
},
|
||||||
|
|
||||||
onDeactivate() {
|
onDeactivate() {
|
||||||
this.get('global').deactivate().then(() => {
|
this.modalClose("#deactivation-request-modal");
|
||||||
|
let comment = this.get('comment');
|
||||||
|
|
||||||
|
this.get('global').deactivate(comment).then(() => {
|
||||||
this.showDone();
|
this.showDone();
|
||||||
this.modalOpen("#deactivation-modal", {"show": true});
|
this.modalOpen("#deactivation-confirmation-modal", {"show": true});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
105
gui/app/components/customize/space-admin.js
Normal file
105
gui/app/components/customize/space-admin.js
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||||
|
//
|
||||||
|
// https://documize.com
|
||||||
|
|
||||||
|
import $ from 'jquery';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { computed } from '@ember/object';
|
||||||
|
import Notifier from '../../mixins/notifier';
|
||||||
|
import Modals from '../../mixins/modal';
|
||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
|
export default Component.extend(Notifier, Modals, {
|
||||||
|
spaceSvc: service('folder'),
|
||||||
|
browserSvc: service('browser'),
|
||||||
|
documentSvc: service('document'),
|
||||||
|
spaces: null,
|
||||||
|
|
||||||
|
label: computed('model', function() {
|
||||||
|
switch (this.get('model').length) {
|
||||||
|
case 1:
|
||||||
|
return "space";
|
||||||
|
default:
|
||||||
|
return "spaces";
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
this.deleteSpace = {
|
||||||
|
id: '',
|
||||||
|
name: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
loadData() {
|
||||||
|
this.get('spaceSvc').manage().then((s) => {
|
||||||
|
this.set('spaces', s);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onShow(id) {
|
||||||
|
this.set('deleteSpace.id', id);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDelete() {
|
||||||
|
let deleteSpace = this.get('deleteSpace');
|
||||||
|
let spaceId = deleteSpace.id;
|
||||||
|
let spaceNameTyped = deleteSpace.name;
|
||||||
|
let space = this.get('spaces').findBy('id', spaceId);
|
||||||
|
let spaceName = space.get('name');
|
||||||
|
|
||||||
|
if (spaceNameTyped !== spaceName || spaceNameTyped === '' || spaceName === '') {
|
||||||
|
$('#delete-space-name').addClass('is-invalid').focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#space-delete-modal').modal('hide');
|
||||||
|
$('#space-delete-modal').modal('dispose');
|
||||||
|
|
||||||
|
this.get('spaceSvc').delete(spaceId).then(() => { /* jshint ignore:line */
|
||||||
|
this.set('deleteSpace.id', '');
|
||||||
|
this.set('deleteSpace.name', '');
|
||||||
|
this.loadData();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onExport() {
|
||||||
|
this.showWait();
|
||||||
|
|
||||||
|
let spec = {
|
||||||
|
spaceId: '',
|
||||||
|
data: _.pluck(this.get('folders'), 'id'),
|
||||||
|
filterType: 'space',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.get('documentSvc').export(spec).then((htmlExport) => {
|
||||||
|
this.get('browserSvc').downloadFile(htmlExport, 'documize.html');
|
||||||
|
this.showDone();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onOwner(spaceId) {
|
||||||
|
this.showWait();
|
||||||
|
|
||||||
|
this.get('spaceSvc').grantOwnerPermission(spaceId).then(() => { /* jshint ignore:line */
|
||||||
|
this.showDone();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -62,6 +62,15 @@ export default Component.extend(ModalMixin, TooltipMixin, Notifer, {
|
||||||
cat.set('documents', docCount);
|
cat.set('documents', docCount);
|
||||||
cat.set('users', userCount);
|
cat.set('users', userCount);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.get('categorySvc').getUserVisible(this.get('space.id')).then((cm) => {
|
||||||
|
cm.forEach((cm) => {
|
||||||
|
let cat = _.findWhere(c, {id: cm.get('id') });
|
||||||
|
if (is.not.undefined(cat)) {
|
||||||
|
cat.set('access', is.not.undefined(cat));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -34,6 +34,9 @@ export default Component.extend(Notifier, Modals, {
|
||||||
isSpaceAdmin: computed('permissions', function() {
|
isSpaceAdmin: computed('permissions', function() {
|
||||||
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
|
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
|
||||||
}),
|
}),
|
||||||
|
isNotSpaceOwner: computed('permissions', function() {
|
||||||
|
return !this.get('permissions.spaceOwner');
|
||||||
|
}),
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
|
|
@ -29,7 +29,6 @@ export default Component.extend(AuthMixin, {
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
// this.filteredDocs = [];
|
|
||||||
this.setup();
|
this.setup();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -9,85 +9,7 @@
|
||||||
//
|
//
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import { computed } from '@ember/object';
|
|
||||||
import { inject as service } from '@ember/service';
|
|
||||||
import Notifier from '../../../mixins/notifier';
|
|
||||||
import TooltipMixin from '../../../mixins/tooltip';
|
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
|
|
||||||
export default Controller.extend(TooltipMixin, Notifier, {
|
export default Controller.extend({
|
||||||
folderService: service('folder'),
|
|
||||||
browserSvc: service('browser'),
|
|
||||||
documentSvc: service('document'),
|
|
||||||
dropdown: null,
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this._super(...arguments);
|
|
||||||
this.folders = [];
|
|
||||||
this.deleteSpace = {
|
|
||||||
id: '',
|
|
||||||
name: ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
label: computed('folders', function() {
|
|
||||||
switch (this.get('folders').length) {
|
|
||||||
case 1:
|
|
||||||
return "space";
|
|
||||||
default:
|
|
||||||
return "spaces";
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
onShow(id) {
|
|
||||||
this.set('deleteSpace.id', id);
|
|
||||||
},
|
|
||||||
|
|
||||||
onDelete() {
|
|
||||||
let deleteSpace = this.get('deleteSpace');
|
|
||||||
let spaceId = deleteSpace.id;
|
|
||||||
let spaceNameTyped = deleteSpace.name;
|
|
||||||
let space = this.get('folders').findBy('id', spaceId);
|
|
||||||
let spaceName = space.get('name');
|
|
||||||
|
|
||||||
if (spaceNameTyped !== spaceName || spaceNameTyped === '' || spaceName === '') {
|
|
||||||
$('#delete-space-name').addClass('is-invalid').focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#space-delete-modal').modal('hide');
|
|
||||||
$('#space-delete-modal').modal('dispose');
|
|
||||||
|
|
||||||
this.get('folderService').delete(spaceId).then(() => { /* jshint ignore:line */
|
|
||||||
this.set('deleteSpace.id', '');
|
|
||||||
this.set('deleteSpace.name', '');
|
|
||||||
|
|
||||||
this.get('folderService').adminList().then((folders) => {
|
|
||||||
let nonPrivateFolders = folders.rejectBy('spaceType', 2);
|
|
||||||
if (is.empty(nonPrivateFolders) || is.null(folders) || is.undefined(folders)) {
|
|
||||||
nonPrivateFolders = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set('folders', nonPrivateFolders);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onExport() {
|
|
||||||
this.showWait();
|
|
||||||
|
|
||||||
let spec = {
|
|
||||||
spaceId: '',
|
|
||||||
data: _.pluck(this.get('folders'), 'id'),
|
|
||||||
filterType: 'space',
|
|
||||||
};
|
|
||||||
|
|
||||||
this.get('documentSvc').export(spec).then((htmlExport) => {
|
|
||||||
this.get('browserSvc').downloadFile(htmlExport, 'documize.html');
|
|
||||||
this.showDone();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,33 +9,17 @@
|
||||||
//
|
//
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
import { inject as service } from '@ember/service';
|
|
||||||
import Route from '@ember/routing/route';
|
|
||||||
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
||||||
|
import Route from '@ember/routing/route';
|
||||||
|
|
||||||
export default Route.extend(AuthenticatedRouteMixin, {
|
export default Route.extend(AuthenticatedRouteMixin, {
|
||||||
folderService: service('folder'),
|
|
||||||
|
|
||||||
beforeModel() {
|
beforeModel() {
|
||||||
if (!this.session.isAdmin) {
|
if (!this.session.isAdmin) {
|
||||||
this.transitionTo('auth.login');
|
this.transitionTo('auth.login');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
model() {
|
|
||||||
return this.get('folderService').adminList();
|
|
||||||
},
|
|
||||||
|
|
||||||
setupController(controller, model) {
|
|
||||||
let nonPrivateFolders = model.rejectBy('spaceType', 2);
|
|
||||||
if (is.empty(nonPrivateFolders) || is.null(model) || is.undefined(model)) {
|
|
||||||
nonPrivateFolders = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.set('folders', nonPrivateFolders);
|
|
||||||
},
|
|
||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
this.get('browser').setTitle('Spaces');
|
this.get('browser').setTitle('Manage Spaces');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,50 +1 @@
|
||||||
{{#if folders}}
|
{{customize/space-admin}}
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<div class="view-customize">
|
|
||||||
<h1 class="admin-heading">{{folders.length}} shared {{label}}</h1>
|
|
||||||
<button type="button" class="btn btn-success" onclick={{action 'onExport'}}>Export as HTML</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="view-customize">
|
|
||||||
<div class="space-list">
|
|
||||||
{{#each folders as |folder|}}
|
|
||||||
<div class="space row">
|
|
||||||
<div class="col-12 col-sm-8">
|
|
||||||
{{#link-to 'folder' folder.id folder.slug class="alt"}}{{folder.name}}{{/link-to}}
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-4 text-right">
|
|
||||||
<div id="space-delete-button-{{folder.id}}" class="button-icon-danger align-middle" data-toggle="tooltip" data-placement="top" title="Delete space" {{action "onShow" folder.id}}>
|
|
||||||
<i class="material-icons" data-toggle="modal" data-target="#space-delete-modal" data-backdrop="static">delete</i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="space-delete-modal" class="modal" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">Space Deletion</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form onsubmit={{action 'onDelete'}}>
|
|
||||||
<p>Are you sure you want to delete this space and all documents?</p>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="delete-space-name">Please type space name to confirm</label>
|
|
||||||
{{input type='text' id="delete-space-name" class="form-control mousetrap" placeholder="Space name" value=deleteSpace.name}}
|
|
||||||
<small class="form-text text-muted">This will delete all documents and templates within this space!</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 'onDelete'}}>Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<p>There are no spaces to maintain</p>
|
|
||||||
{{/if}}
|
|
|
@ -84,7 +84,7 @@ export default Controller.extend(Tooltips, Notifier, {
|
||||||
let constants = this.get('constants');
|
let constants = this.get('constants');
|
||||||
|
|
||||||
// if document approval mode is locked return
|
// if document approval mode is locked return
|
||||||
if (document.get('protection') == constants.ProtectionType.Lock) {
|
if (document.get('protection') === constants.ProtectionType.Lock) {
|
||||||
// should not really happen
|
// should not really happen
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,7 +335,7 @@ export default Service.extend({
|
||||||
} else {
|
} else {
|
||||||
let id = this.get('storageSvc').getSessionItem('anonId');
|
let id = this.get('storageSvc').getSessionItem('anonId');
|
||||||
|
|
||||||
if (is.not.null(id) && is.not.undefined(id) && id.length === 16) {
|
if (is.not.null(id) && is.not.undefined(id) && id.length >= 16) {
|
||||||
userId = id;
|
userId = id;
|
||||||
} else {
|
} else {
|
||||||
userId = stringUtil.anonUserId();
|
userId = stringUtil.anonUserId();
|
||||||
|
|
|
@ -174,8 +174,9 @@ export default BaseService.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// returns all spaces -- for use by documize admin user
|
// Returns all shared spaces and spaces without an owner.
|
||||||
adminList() {
|
// Administrator only method.
|
||||||
|
manage() {
|
||||||
return this.get('ajax').request(`space/manage`, {
|
return this.get('ajax').request(`space/manage`, {
|
||||||
method: "GET"
|
method: "GET"
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
|
@ -189,5 +190,12 @@ export default BaseService.extend({
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// Add admin as space owner.
|
||||||
|
grantOwnerPermission(folderId) {
|
||||||
|
return this.get('ajax').request(`space/manage/owner/${folderId}`, {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -236,10 +236,12 @@ export default Service.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deactivate() {
|
deactivate(comment) {
|
||||||
if(this.get('sessionService.isAdmin')) {
|
if(this.get('sessionService.isAdmin')) {
|
||||||
return this.get('ajax').request(`deactivate`, {
|
return this.get('ajax').request(`deactivate`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
contentType: 'text',
|
||||||
|
data: comment,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
|
@ -65,13 +65,11 @@
|
||||||
|
|
||||||
> .space-list {
|
> .space-list {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 3rem 0;
|
||||||
|
|
||||||
> .space {
|
> .space {
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
padding: 15px;
|
padding: 15px 0;
|
||||||
@include card();
|
|
||||||
@include ease-in();
|
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: $color-primary;
|
color: $color-primary;
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@
|
||||||
<p>Requests can take up to 24 hours to process.</p>
|
<p>Requests can take up to 24 hours to process.</p>
|
||||||
{{#link-to 'customize.backup' class="btn btn-success"}}PERFORM BACKUP{{/link-to}}
|
{{#link-to 'customize.backup' class="btn btn-success"}}PERFORM BACKUP{{/link-to}}
|
||||||
<div class="button-gap" />
|
<div class="button-gap" />
|
||||||
<button class="btn btn-danger" {{action 'onDeactivate'}}>REQUEST ACCOUNT CLOSURE</button>
|
<button class="btn btn-danger" {{action 'onRequestClosure'}}>CLOSE ACCOUNT</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -151,16 +151,37 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div id="deactivation-modal" class="modal" tabindex="-1" role="dialog">
|
<div id="deactivation-request-modal" class="modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">Request Account Closure</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form {{action "onEditComment" on="submit"}}>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="the-comment">Comment</label>
|
||||||
|
{{focus-textarea id="close-comment" class="form-control" rows="5" value=comment}}
|
||||||
|
<small class="form-text text-muted">We always welcome product feedback</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 'onDeactivate'}}>Close Account</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="deactivation-confirmation-modal" class="modal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">Deactivation Requested</div>
|
<div class="modal-header">Deactivation Requested</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>Your request has been sent and will be processed shortly.</p>
|
<p>Your request has been sent and will be processed shortly.</p>
|
||||||
<p>If you haven't already, perform a backup to download all your data.</p>
|
<p>If you haven't already, please run a backup to download all your data.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">ok</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
58
gui/app/templates/components/customize/space-admin.hbs
Normal file
58
gui/app/templates/components/customize/space-admin.hbs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="view-customize">
|
||||||
|
<h1 class="admin-heading">Manage Spaces</h1>
|
||||||
|
<h2 class="sub-heading">Delete spaces, take ownership of shared spaces and orphaned spaces</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="view-customize my-5">
|
||||||
|
{{#if spaces}}
|
||||||
|
<button type="button" class="btn btn-success" onclick={{action 'onExport'}}>Export content</button>
|
||||||
|
<div class="space-list">
|
||||||
|
{{#each spaces as |space|}}
|
||||||
|
<div class="space row">
|
||||||
|
<div class="col-12 col-sm-8">
|
||||||
|
{{#link-to 'folder' space.id space.slug class="alt"}}{{space.name}}{{/link-to}}
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4 text-right">
|
||||||
|
<div id="space-ownership-button-{{space.id}}" class="button-icon-gray align-middle" data-toggle="tooltip" data-placement="top" title="Add myself as space owner" {{action "onOwner" space.id}}>
|
||||||
|
<i class="material-icons" data-toggle="modal">person_add</i>
|
||||||
|
</div>
|
||||||
|
<div class="button-icon-gap" />
|
||||||
|
<div id="space-delete-button-{{space.id}}" class="button-icon-danger align-middle" data-toggle="tooltip" data-placement="top" title="Delete space and all content" {{action "onShow" space.id}}>
|
||||||
|
<i class="material-icons" data-toggle="modal" data-target="#space-delete-modal" data-backdrop="static">delete</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="space-delete-modal" class="modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">Space Deletion</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form onsubmit={{action 'onDelete'}}>
|
||||||
|
<p>Are you sure you want to delete this space and all documents?</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="delete-space-name">Please type space name to confirm</label>
|
||||||
|
{{input type='text' id="delete-space-name" class="form-control mousetrap" placeholder="Space name" value=deleteSpace.name}}
|
||||||
|
<small class="form-text text-muted">This will delete all documents and templates within this space!</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 'onDelete'}}>Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<p>There are no shared spaces to manage</p>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,10 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="category col-8">
|
<div class="category col-8">
|
||||||
<div class="name">{{cat.category}}</div>
|
<div class="name">{{cat.category}}</div>
|
||||||
<div class="info">{{cat.documents}} {{if (eq cat.documents 1) 'document' 'documents' }} · {{cat.users}} users/groups</div>
|
<div class="info">
|
||||||
|
{{cat.documents}} {{if (eq cat.documents 1) 'document' 'documents' }} · {{cat.users}} users/groups
|
||||||
|
{{#unless cat.access}}<span class="text-danger">(you have no view permission)</span>{{/unless}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="col-4 buttons text-right">
|
<div class="col-4 buttons text-right">
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>{{x-toggle value=permission.spaceView onToggle=(action (mut permission.spaceView))}}</td>
|
<td>{{x-toggle value=permission.spaceView onToggle=(action (mut permission.spaceView))}}</td>
|
||||||
<td>{{x-toggle value=permission.spaceManage onToggle=(action (mut permission.spaceManage))}}</td>
|
<td>{{x-toggle value=permission.spaceManage onToggle=(action (mut permission.spaceManage))}}</td>
|
||||||
<td>{{x-toggle value=permission.spaceOwner onToggle=(action (mut permission.spaceOwner))}}</td>
|
<td>{{x-toggle value=permission.spaceOwner onToggle=(action (mut permission.spaceOwner)) disabled=isNotSpaceOwner}}</td>
|
||||||
<td>{{x-toggle value=permission.documentAdd onToggle=(action (mut permission.documentAdd))}}</td>
|
<td>{{x-toggle value=permission.documentAdd onToggle=(action (mut permission.documentAdd))}}</td>
|
||||||
<td>{{x-toggle value=permission.documentEdit onToggle=(action (mut permission.documentEdit))}}</td>
|
<td>{{x-toggle value=permission.documentEdit onToggle=(action (mut permission.documentEdit))}}</td>
|
||||||
<td>{{x-toggle value=permission.documentDelete onToggle=(action (mut permission.documentDelete))}}</td>
|
<td>{{x-toggle value=permission.documentDelete onToggle=(action (mut permission.documentDelete))}}</td>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "documize",
|
"name": "documize",
|
||||||
"version": "1.76.0",
|
"version": "1.76.1",
|
||||||
"description": "The Document IDE",
|
"description": "The Document IDE",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": "",
|
"repository": "",
|
||||||
|
|
|
@ -95,6 +95,7 @@ const (
|
||||||
EventTypeWorkflowPublishRequested EventType = "requested-publication"
|
EventTypeWorkflowPublishRequested EventType = "requested-publication"
|
||||||
EventTypeDatabaseBackup EventType = "backedup-database"
|
EventTypeDatabaseBackup EventType = "backedup-database"
|
||||||
EventTypeDatabaseRestore EventType = "restored-database"
|
EventTypeDatabaseRestore EventType = "restored-database"
|
||||||
|
EventTypeAssumedSpaceOwnership EventType = "assumed-space-ownership"
|
||||||
|
|
||||||
// EventTypeVersionAdd records addition of version
|
// EventTypeVersionAdd records addition of version
|
||||||
EventTypeVersionAdd EventType = "added-version"
|
EventTypeVersionAdd EventType = "added-version"
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
|
|
||||||
package org
|
package org
|
||||||
|
|
||||||
import "github.com/documize/community/model"
|
import (
|
||||||
|
"github.com/documize/community/model"
|
||||||
|
)
|
||||||
|
|
||||||
// Organization defines a tenant that uses this app.
|
// Organization defines a tenant that uses this app.
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
|
@ -28,5 +30,10 @@ type Organization struct {
|
||||||
MaxTags int `json:"maxTags"`
|
MaxTags int `json:"maxTags"`
|
||||||
Serial string `json:"serial"`
|
Serial string `json:"serial"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Subscription string
|
Subscription string `json:"subscription"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StripSecrets removes sensitive information.
|
||||||
|
func (o *Organization) StripSecrets() {
|
||||||
|
o.Subscription = ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,7 +131,8 @@ func RegisterEndpoints(rt *env.Runtime, s *store.Store) {
|
||||||
AddPrivate(rt, "space/{spaceID}", []string{"DELETE", "OPTIONS"}, nil, space.Delete)
|
AddPrivate(rt, "space/{spaceID}", []string{"DELETE", "OPTIONS"}, nil, space.Delete)
|
||||||
AddPrivate(rt, "space/{spaceID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, space.Remove)
|
AddPrivate(rt, "space/{spaceID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, space.Remove)
|
||||||
AddPrivate(rt, "space/{spaceID}/invitation", []string{"POST", "OPTIONS"}, nil, space.Invite)
|
AddPrivate(rt, "space/{spaceID}/invitation", []string{"POST", "OPTIONS"}, nil, space.Invite)
|
||||||
AddPrivate(rt, "space/manage", []string{"GET", "OPTIONS"}, nil, space.GetAll)
|
AddPrivate(rt, "space/manage", []string{"GET", "OPTIONS"}, nil, space.Manage)
|
||||||
|
AddPrivate(rt, "space/manage/owner/{spaceID}", []string{"POST", "OPTIONS"}, nil, space.ManageOwner)
|
||||||
AddPrivate(rt, "space/{spaceID}", []string{"GET", "OPTIONS"}, nil, space.Get)
|
AddPrivate(rt, "space/{spaceID}", []string{"GET", "OPTIONS"}, nil, space.Get)
|
||||||
AddPrivate(rt, "space", []string{"GET", "OPTIONS"}, nil, space.GetViewable)
|
AddPrivate(rt, "space", []string{"GET", "OPTIONS"}, nil, space.GetViewable)
|
||||||
AddPrivate(rt, "space/{spaceID}", []string{"PUT", "OPTIONS"}, nil, space.Update)
|
AddPrivate(rt, "space/{spaceID}", []string{"PUT", "OPTIONS"}, nil, space.Update)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue