mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 11:42:30 +03:00
Owners (#2183)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
25afe12cc2
commit
efc3583376
@ -5,9 +5,10 @@
|
||||
Core:
|
||||
|
||||
- Allow to leave workspace
|
||||
- Allow to kick employee
|
||||
- Allow to kick employee (Only for owner)
|
||||
- Browser notifications
|
||||
- Allow to create employee
|
||||
- Owner role for employee
|
||||
|
||||
HR:
|
||||
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
dropWorkspace,
|
||||
getAccount,
|
||||
getWorkspace,
|
||||
setRole,
|
||||
listAccounts,
|
||||
listWorkspaces,
|
||||
upgradeWorkspace
|
||||
@ -114,6 +115,14 @@ program
|
||||
})
|
||||
})
|
||||
|
||||
program
|
||||
.command('set-user-role <email> <workspace> <role>')
|
||||
.description('set user role')
|
||||
.action(async (email: string, workspace: string, role: number, cmd) => {
|
||||
console.log(`set user ${email} role for ${workspace}...`)
|
||||
await setRole(email, workspace, role)
|
||||
})
|
||||
|
||||
program
|
||||
.command('upgrade-workspace <name>')
|
||||
.description('upgrade workspace')
|
||||
|
@ -71,14 +71,15 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
/** Disable Automation UI
|
||||
builder.createDoc(
|
||||
setting.class.SettingsCategory,
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'automation',
|
||||
label: automation.string.Automation,
|
||||
icon: automation.icon.Automation, // TODO: update icon
|
||||
component: plugin.component.AutomationSettingsElement,
|
||||
order: 3600
|
||||
order: 3600,
|
||||
secured: false
|
||||
},
|
||||
plugin.ids.Automation
|
||||
)
|
||||
|
@ -420,7 +420,8 @@ export function createModel (builder: Builder): void {
|
||||
context: {
|
||||
mode: ['context'],
|
||||
group: 'other'
|
||||
}
|
||||
},
|
||||
secured: true
|
||||
},
|
||||
contact.action.KickEmployee
|
||||
)
|
||||
|
@ -1,6 +1,5 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
@ -14,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { TxOperations } from '@anticrm/core'
|
||||
import { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import { AccountRole, DOMAIN_TX, TxCreateDoc, TxOperations } from '@anticrm/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
|
||||
import core from '@anticrm/model-core'
|
||||
import contact, { DOMAIN_CONTACT } from './index'
|
||||
@ -66,11 +66,40 @@ async function setActiveEmployee (client: MigrationClient): Promise<void> {
|
||||
active: true
|
||||
}
|
||||
)
|
||||
await setActiveEmployeeTx(client)
|
||||
}
|
||||
|
||||
async function setActiveEmployeeTx (client: MigrationClient): Promise<void> {
|
||||
await client.update<TxCreateDoc<Employee>>(
|
||||
DOMAIN_TX,
|
||||
{
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: contact.class.Employee,
|
||||
'attributes.active': { $exists: false }
|
||||
},
|
||||
{
|
||||
'attributes.active': true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function setRole (client: MigrationClient): Promise<void> {
|
||||
await client.update<TxCreateDoc<EmployeeAccount>>(
|
||||
DOMAIN_TX,
|
||||
{
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: contact.class.Employee
|
||||
},
|
||||
{
|
||||
'attributes.role': AccountRole.User
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const contactOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await setActiveEmployee(client)
|
||||
await setRole(client)
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Account, Arr, Domain, DOMAIN_MODEL, IndexKind, Ref, Space } from '@anticrm/core'
|
||||
import { Account, AccountRole, Arr, Domain, DOMAIN_MODEL, IndexKind, Ref, Space } from '@anticrm/core'
|
||||
import { Index, Model, Prop, TypeBoolean, TypeString, UX } from '@anticrm/model'
|
||||
import core from './component'
|
||||
import { TDoc } from './core'
|
||||
@ -45,4 +45,5 @@ export class TSpace extends TDoc implements Space {
|
||||
@Model(core.class.Account, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TAccount extends TDoc implements Account {
|
||||
email!: string
|
||||
role!: AccountRole
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { TxOperations } from '@anticrm/core'
|
||||
import core, { AccountRole, TxOperations } from '@anticrm/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
|
||||
import contact, { EmployeeAccount } from '@anticrm/contact'
|
||||
import recruit from '@anticrm/model-recruit'
|
||||
@ -61,7 +61,8 @@ export const demoOperation: MigrateOperation = {
|
||||
await tx.createDoc<EmployeeAccount>(contact.class.EmployeeAccount, core.space.Model, {
|
||||
email: 'rosamund@hc.engineering',
|
||||
employee,
|
||||
name: 'Chen,Rosamund'
|
||||
name: 'Chen,Rosamund',
|
||||
role: AccountRole.Owner
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,16 @@ export class TSettingsCategory extends TDoc implements SettingsCategory {
|
||||
label!: IntlString
|
||||
icon!: Asset
|
||||
component!: AnyComponent
|
||||
secured!: boolean
|
||||
}
|
||||
|
||||
@Model(setting.class.WorkspaceSettingCategory, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TWorkspaceSettingCategory extends TDoc implements SettingsCategory {
|
||||
name!: string
|
||||
label!: IntlString
|
||||
icon!: Asset
|
||||
component!: AnyComponent
|
||||
secured!: boolean
|
||||
}
|
||||
|
||||
@Model(setting.class.IntegrationType, core.class.Doc, DOMAIN_MODEL)
|
||||
@ -56,7 +66,7 @@ export class TIntegrationType extends TDoc implements IntegrationType {
|
||||
export class TEditable extends TClass implements Editable {}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TIntegration, TIntegrationType, TSettingsCategory, TEditable)
|
||||
builder.createModel(TIntegration, TIntegrationType, TSettingsCategory, TWorkspaceSettingCategory, TEditable)
|
||||
|
||||
builder.createDoc(
|
||||
setting.class.SettingsCategory,
|
||||
@ -66,7 +76,8 @@ export function createModel (builder: Builder): void {
|
||||
label: setting.string.EditProfile,
|
||||
icon: setting.icon.EditProfile,
|
||||
component: setting.component.Profile,
|
||||
order: 0
|
||||
order: 0,
|
||||
secured: false
|
||||
},
|
||||
setting.ids.Profile
|
||||
)
|
||||
@ -79,7 +90,8 @@ export function createModel (builder: Builder): void {
|
||||
label: setting.string.ChangePassword,
|
||||
icon: setting.icon.Password,
|
||||
component: setting.component.Password,
|
||||
order: 1000
|
||||
order: 1000,
|
||||
secured: false
|
||||
},
|
||||
setting.ids.Password
|
||||
)
|
||||
@ -88,10 +100,11 @@ export function createModel (builder: Builder): void {
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'setting',
|
||||
label: setting.string.Setting,
|
||||
label: setting.string.WorkspaceSetting,
|
||||
icon: setting.icon.Setting,
|
||||
component: setting.component.Setting,
|
||||
order: 2000
|
||||
component: setting.component.WorkspaceSettings,
|
||||
order: 2000,
|
||||
secured: false
|
||||
},
|
||||
setting.ids.Setting
|
||||
)
|
||||
@ -103,43 +116,60 @@ export function createModel (builder: Builder): void {
|
||||
label: setting.string.Integrations,
|
||||
icon: setting.icon.Integrations,
|
||||
component: setting.component.Integrations,
|
||||
order: 3000
|
||||
order: 3000,
|
||||
secured: false
|
||||
},
|
||||
setting.ids.Integrations
|
||||
)
|
||||
builder.createDoc(
|
||||
setting.class.SettingsCategory,
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'owners',
|
||||
label: setting.string.Owners,
|
||||
icon: setting.icon.Password,
|
||||
component: setting.component.Owners,
|
||||
order: 1000,
|
||||
secured: true
|
||||
},
|
||||
setting.ids.Owners
|
||||
)
|
||||
builder.createDoc(
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'statuses',
|
||||
label: setting.string.ManageStatuses,
|
||||
icon: task.icon.ManageStatuses,
|
||||
component: setting.component.ManageStatuses,
|
||||
order: 4000
|
||||
order: 4000,
|
||||
secured: false
|
||||
},
|
||||
setting.ids.ManageStatuses
|
||||
)
|
||||
builder.createDoc(
|
||||
setting.class.SettingsCategory,
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'classes',
|
||||
label: setting.string.ClassSetting,
|
||||
icon: setting.icon.Setting,
|
||||
component: setting.component.ClassSetting,
|
||||
order: 4500
|
||||
order: 4500,
|
||||
secured: false
|
||||
},
|
||||
setting.ids.ClassSetting
|
||||
)
|
||||
builder.createDoc(
|
||||
setting.class.SettingsCategory,
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'enums',
|
||||
label: setting.string.Enums,
|
||||
icon: setting.icon.Setting,
|
||||
component: setting.component.EnumSetting,
|
||||
order: 4600
|
||||
order: 4600,
|
||||
secured: false
|
||||
},
|
||||
setting.ids.EnumSetting
|
||||
)
|
||||
@ -151,7 +181,8 @@ export function createModel (builder: Builder): void {
|
||||
label: setting.string.Support,
|
||||
icon: setting.icon.Support,
|
||||
component: setting.component.Support,
|
||||
order: 5000
|
||||
order: 5000,
|
||||
secured: false
|
||||
},
|
||||
setting.ids.Support
|
||||
)
|
||||
@ -163,7 +194,8 @@ export function createModel (builder: Builder): void {
|
||||
label: setting.string.Privacy,
|
||||
icon: setting.icon.Privacy,
|
||||
component: setting.component.Privacy,
|
||||
order: 6000
|
||||
order: 6000,
|
||||
secured: false
|
||||
},
|
||||
setting.ids.Privacy
|
||||
)
|
||||
@ -175,7 +207,8 @@ export function createModel (builder: Builder): void {
|
||||
label: setting.string.Terms,
|
||||
icon: setting.icon.Terms,
|
||||
component: setting.component.Terms,
|
||||
order: 10000
|
||||
order: 10000,
|
||||
secured: false
|
||||
},
|
||||
setting.ids.Terms
|
||||
)
|
||||
|
@ -36,6 +36,7 @@ export default mergeIds(settingId, setting, {
|
||||
NumberTypeEditor: '' as AnyComponent,
|
||||
DateTypeEditor: '' as AnyComponent,
|
||||
RefEditor: '' as AnyComponent,
|
||||
EnumTypeEditor: '' as AnyComponent
|
||||
EnumTypeEditor: '' as AnyComponent,
|
||||
Owners: '' as AnyComponent
|
||||
}
|
||||
})
|
||||
|
@ -39,14 +39,15 @@ export function createModel (builder: Builder): void {
|
||||
builder.createModel(TMessageTemplate)
|
||||
|
||||
builder.createDoc(
|
||||
setting.class.SettingsCategory,
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'message-templates',
|
||||
label: templates.string.Templates,
|
||||
icon: templates.icon.Templates,
|
||||
component: templates.component.Templates,
|
||||
order: 3500
|
||||
order: 3500,
|
||||
secured: false
|
||||
},
|
||||
templates.ids.Templates
|
||||
)
|
||||
|
@ -202,6 +202,7 @@ export class TAction extends TDoc implements Action {
|
||||
description!: IntlString
|
||||
category!: Ref<ActionCategory>
|
||||
context!: ViewContext
|
||||
secured?: boolean
|
||||
}
|
||||
|
||||
@Model(view.class.ActionCategory, core.class.Doc, DOMAIN_MODEL)
|
||||
|
@ -236,7 +236,7 @@ describe('memdb', () => {
|
||||
members: [],
|
||||
archived: false
|
||||
})
|
||||
const account = await model.createDoc(core.class.Account, core.space.Model, { email: 'email' })
|
||||
const account = await model.createDoc(core.class.Account, core.space.Model, { email: 'email', role: 0 })
|
||||
await model.updateDoc(core.class.Space, core.space.Model, space, { $push: { members: account } })
|
||||
const txSpace = await model.findAll(core.class.Space, { _id: space })
|
||||
expect(txSpace[0].members).toEqual(expect.arrayContaining([account]))
|
||||
|
@ -279,6 +279,16 @@ export interface Space extends Doc {
|
||||
*/
|
||||
export interface Account extends Doc {
|
||||
email: string
|
||||
role: AccountRole
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export enum AccountRole {
|
||||
User,
|
||||
Maintainer,
|
||||
Owner
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,8 +194,8 @@ export function genMinModel (): TxCUD<Doc>[] {
|
||||
const u1 = 'User1' as Ref<Account>
|
||||
const u2 = 'User2' as Ref<Account>
|
||||
txes.push(
|
||||
createDoc(core.class.Account, { email: 'user1@site.com' }, u1),
|
||||
createDoc(core.class.Account, { email: 'user2@site.com' }, u2),
|
||||
createDoc(core.class.Account, { email: 'user1@site.com', role: 0 }, u1),
|
||||
createDoc(core.class.Account, { email: 'user2@site.com', role: 0 }, u2),
|
||||
createDoc(core.class.Space, {
|
||||
name: 'Sp1',
|
||||
description: '',
|
||||
|
@ -100,7 +100,8 @@ describe('query', () => {
|
||||
})
|
||||
|
||||
await factory.createDoc(core.class.Account, core.space.Model, {
|
||||
email: 'user1@site.com'
|
||||
email: 'user1@site.com',
|
||||
role: 0
|
||||
})
|
||||
await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
|
||||
private: true,
|
||||
|
@ -16,7 +16,6 @@
|
||||
import { IntlString, Asset } from '@anticrm/platform'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { AnySvelteComponent, TooltipAlignment, ButtonKind, ButtonSize, DropdownIntlItem } from '../types'
|
||||
import ui from '../plugin'
|
||||
import { showPopup } from '../popups'
|
||||
import Button from './Button.svelte'
|
||||
import DropdownLabelsPopupIntl from './DropdownLabelsPopupIntl.svelte'
|
||||
@ -24,10 +23,9 @@
|
||||
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let label: IntlString
|
||||
export let placeholder: IntlString | undefined = ui.string.SearchDots
|
||||
export let items: DropdownIntlItem[]
|
||||
export let selected: DropdownIntlItem['id'] | undefined = undefined
|
||||
|
||||
export let disabled: boolean = false
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
export let size: ButtonSize = 'small'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
@ -52,12 +50,13 @@
|
||||
width={width ?? 'min-content'}
|
||||
{size}
|
||||
{kind}
|
||||
{disabled}
|
||||
{justify}
|
||||
showTooltip={{ label, direction: labelDirection }}
|
||||
on:click={() => {
|
||||
if (!opened) {
|
||||
opened = true
|
||||
showPopup(DropdownLabelsPopupIntl, { placeholder, items, selected }, container, (result) => {
|
||||
showPopup(DropdownLabelsPopupIntl, { items, selected }, container, (result) => {
|
||||
if (result) {
|
||||
selected = result
|
||||
dispatch('selected', result)
|
||||
@ -68,11 +67,7 @@
|
||||
}}
|
||||
>
|
||||
<span slot="content" class="overflow-label disabled">
|
||||
{#if selectedItem}
|
||||
<Label label={selectedItem.label} />
|
||||
{:else}
|
||||
<Label label={label ?? ui.string.NotSelected} />
|
||||
{/if}
|
||||
<Label label={selectedItem ? selectedItem.label : label} />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -13,26 +13,16 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { translate } from '@anticrm/platform'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import plugin from '../plugin'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { DropdownIntlItem } from '../types'
|
||||
import CheckBox from './CheckBox.svelte'
|
||||
import Label from './Label.svelte'
|
||||
|
||||
export let placeholder: IntlString = plugin.string.SearchDots
|
||||
export let items: DropdownIntlItem[]
|
||||
export let selected: DropdownIntlItem['id'] | undefined = undefined
|
||||
|
||||
let search: string = ''
|
||||
let phTraslate: string = ''
|
||||
$: translate(placeholder, {}).then((res) => {
|
||||
phTraslate = res
|
||||
})
|
||||
const dispatch = createEventDispatcher()
|
||||
const btns: HTMLButtonElement[] = []
|
||||
let searchInput: HTMLInputElement
|
||||
|
||||
const keyDown = (ev: KeyboardEvent, n: number): void => {
|
||||
if (ev.key === 'ArrowDown') {
|
||||
@ -41,28 +31,14 @@
|
||||
} else if (ev.key === 'ArrowUp') {
|
||||
if (n === 0) btns[btns.length - 1].focus()
|
||||
else btns[n - 1].focus()
|
||||
} else searchInput.focus()
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (searchInput) searchInput.focus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="selectPopup">
|
||||
<div class="header">
|
||||
<input
|
||||
bind:this={searchInput}
|
||||
type="text"
|
||||
bind:value={search}
|
||||
placeholder={phTraslate}
|
||||
on:input={(ev) => {}}
|
||||
on:change
|
||||
/>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="box">
|
||||
{#each items.filter((x) => x.label.toLowerCase().includes(search.toLowerCase())) as item, i}
|
||||
{#each items as item, i}
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<button
|
||||
class="menu-item flex-between"
|
||||
|
@ -51,7 +51,6 @@
|
||||
<div class="simpleFilterButton">
|
||||
<DropdownLabelsIntl
|
||||
items={dateFileBrowserFilters}
|
||||
placeholder={attachment.string.FileBrowserFilterDate}
|
||||
label={attachment.string.FileBrowserFilterDate}
|
||||
bind:selected={selectedDateId}
|
||||
/>
|
||||
@ -59,7 +58,6 @@
|
||||
<div class="simpleFilterButton">
|
||||
<DropdownLabelsIntl
|
||||
items={fileTypeFileBrowserFilters}
|
||||
placeholder={attachment.string.FileBrowserFilterFileType}
|
||||
label={attachment.string.FileBrowserFilterFileType}
|
||||
bind:selected={selectedFileTypeId}
|
||||
/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import attachment from '@anticrm/attachment'
|
||||
import { Channel, combineName, Employee, findPerson, Person } from '@anticrm/contact'
|
||||
import core, { AttachedData, Data, generateId, Ref } from '@anticrm/core'
|
||||
import core, { AccountRole, AttachedData, Data, generateId, Ref } from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { Card, EditableAvatar, getClient } from '@anticrm/presentation'
|
||||
import { EditBox, IconInfo, Label, createFocusManager, FocusHandler } from '@anticrm/ui'
|
||||
@ -69,7 +69,8 @@
|
||||
await client.createDoc(contact.class.EmployeeAccount, core.space.Model, {
|
||||
email: email.trim(),
|
||||
name,
|
||||
employee: id
|
||||
employee: id,
|
||||
role: AccountRole.User
|
||||
})
|
||||
|
||||
for (const channel of channels) {
|
||||
|
@ -58,6 +58,7 @@ export {
|
||||
OrganizationSelector,
|
||||
ChannelsDropdown,
|
||||
EmployeePresenter,
|
||||
PersonPresenter,
|
||||
EmployeeBrowser,
|
||||
MemberPresenter,
|
||||
EmployeeEditor
|
||||
|
@ -45,6 +45,14 @@
|
||||
"Enums": "Enums",
|
||||
"NewValue": "New value",
|
||||
"Leave": "Leave workspace",
|
||||
"LeaveDescr": "Are you sure you want to leave the workspace? This action cannot be undone."
|
||||
"LeaveDescr": "Are you sure you want to leave the workspace? This action cannot be undone.",
|
||||
"Owners": "Owners",
|
||||
"WorkspaceSetting": "Workspace",
|
||||
"Select": "Select",
|
||||
"AddOwner": "Add owner",
|
||||
"User": "User",
|
||||
"Maintainer": "Maintainer",
|
||||
"Owner": "Owner",
|
||||
"Role": "Role"
|
||||
}
|
||||
}
|
@ -45,6 +45,14 @@
|
||||
"Enums": "Справочники",
|
||||
"NewValue": "Новое значение",
|
||||
"Leave": "Покинуть рабочее пространство",
|
||||
"LeaveDescr": "Вы действительно хотите покинуть рабочее пространство? Отменить это действие невозможно"
|
||||
"LeaveDescr": "Вы действительно хотите покинуть рабочее пространство? Отменить это действие невозможно",
|
||||
"Owners": "Владельцы",
|
||||
"WorkspaceSetting": "Рабочее пространство",
|
||||
"Select": "Выбрать",
|
||||
"AddOwner": "Добавить владельца",
|
||||
"User": "Пользователь",
|
||||
"Maintainer": "Maintainer",
|
||||
"Owner": "Владелец",
|
||||
"Role": "Роль"
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@
|
||||
export let icon: Asset | undefined = undefined
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let selected: boolean = false
|
||||
export let expandable = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
@ -28,6 +29,7 @@
|
||||
<div
|
||||
class="antiNav-element"
|
||||
class:selected
|
||||
class:expandable
|
||||
on:click|stopPropagation={() => {
|
||||
dispatch('click')
|
||||
}}
|
||||
@ -38,6 +40,21 @@
|
||||
{/if}
|
||||
</div>
|
||||
<span class="an-element__label title">
|
||||
{#if label}<Label {label} />{:else}{label}{/if}
|
||||
{#if label}<Label {label} />{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.expandable {
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '▶';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0.5rem;
|
||||
font-size: 0.375rem;
|
||||
color: var(--dark-color);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -77,7 +77,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ac-column">
|
||||
<div class="ac-column max">
|
||||
{#if selected !== undefined}
|
||||
<EnumValues value={selected} />
|
||||
{/if}
|
||||
|
91
plugins/setting-resources/src/components/Owners.svelte
Normal file
91
plugins/setting-resources/src/components/Owners.svelte
Normal file
@ -0,0 +1,91 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import { PersonPresenter } from '@anticrm/contact-resources'
|
||||
import { AccountRole, getCurrentAccount, Ref, SortingOrder } from '@anticrm/core'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { DropdownIntlItem, DropdownLabelsIntl, Icon, Label } from '@anticrm/ui'
|
||||
import setting from '../plugin'
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const query = createQuery()
|
||||
const employeeQuery = createQuery()
|
||||
|
||||
const currentRole = getCurrentAccount().role
|
||||
|
||||
const items: DropdownIntlItem[] = [
|
||||
{ id: AccountRole.User.toString(), label: setting.string.User },
|
||||
{ id: AccountRole.Maintainer.toString(), label: setting.string.Maintainer },
|
||||
{ id: AccountRole.Owner.toString(), label: setting.string.Owner }
|
||||
]
|
||||
|
||||
let accounts: EmployeeAccount[] = []
|
||||
$: owners = accounts.filter((p) => p.role === AccountRole.Owner)
|
||||
let employees: Map<Ref<Employee>, Employee> = new Map<Ref<Employee>, Employee>()
|
||||
|
||||
query.query(
|
||||
contact.class.EmployeeAccount,
|
||||
{},
|
||||
(res) => {
|
||||
accounts = res
|
||||
},
|
||||
{
|
||||
sort: { name: SortingOrder.Descending }
|
||||
}
|
||||
)
|
||||
|
||||
employeeQuery.query(contact.class.Employee, {}, (res) => {
|
||||
employees = new Map(
|
||||
res.map((p) => {
|
||||
return [p._id, p]
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
async function change (account: EmployeeAccount, value: AccountRole): Promise<void> {
|
||||
await client.update(account, {
|
||||
role: value
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiComponent">
|
||||
<div class="ac-header short divide">
|
||||
<div class="ac-header__icon"><Icon icon={setting.icon.Password} size={'medium'} /></div>
|
||||
<div class="ac-header__title"><Label label={setting.string.Owners} /></div>
|
||||
</div>
|
||||
<div class="ac-body columns">
|
||||
<div class="ac-column max">
|
||||
{#each accounts as account (account._id)}
|
||||
<div class="flex-between">
|
||||
<PersonPresenter value={employees.get(account.employee)} isInteractive={false} />
|
||||
<DropdownLabelsIntl
|
||||
label={setting.string.Role}
|
||||
disabled={account.role > currentRole || (account.role === AccountRole.Owner && owners.length === 1)}
|
||||
kind={'transparent'}
|
||||
size={'medium'}
|
||||
{items}
|
||||
selected={account.role.toString()}
|
||||
on:selected={(e) => {
|
||||
change(account, Number(e.detail))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,25 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import setting from '@anticrm/setting'
|
||||
import { Icon, Label } from '@anticrm/ui'
|
||||
</script>
|
||||
|
||||
<div class="antiComponent">
|
||||
<div class="ac-header short divide">
|
||||
<div class="ac-header__icon"><Icon icon={setting.icon.Setting} size={'medium'} /></div>
|
||||
<div class="ac-header__title"><Label label={setting.string.Setting} /></div>
|
||||
</div>
|
||||
</div>
|
@ -1,5 +1,19 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import setting, { SettingsCategory } from '@anticrm/setting'
|
||||
import {
|
||||
Component,
|
||||
@ -13,17 +27,25 @@
|
||||
import { onDestroy } from 'svelte'
|
||||
import CategoryElement from './CategoryElement.svelte'
|
||||
import login from '@anticrm/login'
|
||||
|
||||
const client = getClient()
|
||||
import { AccountRole, getCurrentAccount } from '@anticrm/core'
|
||||
import { EmployeeAccount } from '@anticrm/contact'
|
||||
|
||||
let category: SettingsCategory | undefined
|
||||
let categoryId: string = ''
|
||||
|
||||
let categories: SettingsCategory[] = []
|
||||
client.findAll(setting.class.SettingsCategory, {}, { sort: { order: 1 } }).then((s) => {
|
||||
categories = s
|
||||
category = findCategory(categoryId)
|
||||
})
|
||||
const account = getCurrentAccount() as EmployeeAccount
|
||||
|
||||
const settingsQuery = createQuery()
|
||||
settingsQuery.query(
|
||||
setting.class.SettingsCategory,
|
||||
{},
|
||||
(res) => {
|
||||
categories = account.role > AccountRole.User ? res : res.filter((p) => p.secured === false)
|
||||
category = findCategory(categoryId)
|
||||
},
|
||||
{ sort: { order: 1 } }
|
||||
)
|
||||
|
||||
onDestroy(
|
||||
location.subscribe(async (loc) => {
|
||||
@ -68,6 +90,7 @@
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
expandable={category._id === setting.ids.Setting}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
}}
|
||||
|
@ -0,0 +1,74 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { EmployeeAccount } from '@anticrm/contact'
|
||||
import { AccountRole, getCurrentAccount } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import setting, { SettingsCategory } from '@anticrm/setting'
|
||||
import { Component, Label } from '@anticrm/ui'
|
||||
import CategoryElement from './CategoryElement.svelte'
|
||||
|
||||
let category: SettingsCategory | undefined
|
||||
let categoryId: string = ''
|
||||
|
||||
let categories: SettingsCategory[] = []
|
||||
const account = getCurrentAccount() as EmployeeAccount
|
||||
|
||||
const settingsQuery = createQuery()
|
||||
settingsQuery.query(
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
{},
|
||||
(res) => {
|
||||
categories = account.role > AccountRole.User ? res : res.filter((p) => p.secured === false)
|
||||
category = findCategory(categoryId)
|
||||
},
|
||||
{ sort: { order: 1 } }
|
||||
)
|
||||
|
||||
function findCategory (name: string): SettingsCategory | undefined {
|
||||
return categories.find((x) => x.name === name)
|
||||
}
|
||||
|
||||
function selectCategory (value: SettingsCategory) {
|
||||
categoryId = value.name
|
||||
category = value
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full">
|
||||
<div class="antiPanel-navigator filled indent">
|
||||
<div class="antiNav-header">
|
||||
<span class="fs-title overflow-label">
|
||||
<Label label={setting.string.WorkspaceSetting} />
|
||||
</span>
|
||||
</div>
|
||||
{#each categories as category}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
on:click={() => {
|
||||
selectCategory(category)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="antiPanel-component border-left filled">
|
||||
{#if category}
|
||||
<Component is={category.component} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
@ -16,7 +16,7 @@
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import Profile from './components/Profile.svelte'
|
||||
import Password from './components/Password.svelte'
|
||||
import Setting from './components/Setting.svelte'
|
||||
import WorkspaceSettings from './components/WorkspaceSettings.svelte'
|
||||
import Integrations from './components/Integrations.svelte'
|
||||
import ManageStatuses from './components/statuses/ManageStatuses.svelte'
|
||||
import Support from './components/Support.svelte'
|
||||
@ -34,6 +34,7 @@ import RefEditor from './components/typeEditors/RefEditor.svelte'
|
||||
import EnumTypeEditor from './components/typeEditors/EnumTypeEditor.svelte'
|
||||
import EditEnum from './components/EditEnum.svelte'
|
||||
import EnumSetting from './components/EnumSetting.svelte'
|
||||
import Owners from './components/Owners.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
activity: {
|
||||
@ -44,7 +45,7 @@ export default async (): Promise<Resources> => ({
|
||||
Settings,
|
||||
Profile,
|
||||
Password,
|
||||
Setting,
|
||||
WorkspaceSettings,
|
||||
Integrations,
|
||||
Support,
|
||||
Privacy,
|
||||
@ -58,6 +59,7 @@ export default async (): Promise<Resources> => ({
|
||||
DateTypeEditor,
|
||||
EnumTypeEditor,
|
||||
EditEnum,
|
||||
EnumSetting
|
||||
EnumSetting,
|
||||
Owners
|
||||
}
|
||||
})
|
||||
|
@ -41,6 +41,12 @@ export default mergeIds(settingId, setting, {
|
||||
Enums: '' as IntlString,
|
||||
NewValue: '' as IntlString,
|
||||
Leave: '' as IntlString,
|
||||
LeaveDescr: '' as IntlString
|
||||
LeaveDescr: '' as IntlString,
|
||||
Select: '' as IntlString,
|
||||
AddOwner: '' as IntlString,
|
||||
User: '' as IntlString,
|
||||
Maintainer: '' as IntlString,
|
||||
Owner: '' as IntlString,
|
||||
Role: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -60,6 +60,7 @@ export interface SettingsCategory extends Doc {
|
||||
|
||||
// If defined, will sort using order.
|
||||
order?: number
|
||||
secured: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,13 +79,15 @@ export default plugin(settingId, {
|
||||
Support: '' as Ref<Doc>,
|
||||
Privacy: '' as Ref<Doc>,
|
||||
Terms: '' as Ref<Doc>,
|
||||
ClassSetting: '' as Ref<Doc>
|
||||
ClassSetting: '' as Ref<Doc>,
|
||||
Owners: '' as Ref<Doc>
|
||||
},
|
||||
mixin: {
|
||||
Editable: '' as Ref<Mixin<Editable>>
|
||||
},
|
||||
class: {
|
||||
SettingsCategory: '' as Ref<Class<SettingsCategory>>,
|
||||
WorkspaceSettingCategory: '' as Ref<Class<SettingsCategory>>,
|
||||
Integration: '' as Ref<Class<Integration>>,
|
||||
IntegrationType: '' as Ref<Class<IntegrationType>>
|
||||
},
|
||||
@ -92,7 +95,7 @@ export default plugin(settingId, {
|
||||
Settings: '' as AnyComponent,
|
||||
Profile: '' as AnyComponent,
|
||||
Password: '' as AnyComponent,
|
||||
Setting: '' as AnyComponent,
|
||||
WorkspaceSettings: '' as AnyComponent,
|
||||
Integrations: '' as AnyComponent,
|
||||
ManageStatuses: '' as AnyComponent,
|
||||
Support: '' as AnyComponent,
|
||||
@ -103,6 +106,7 @@ export default plugin(settingId, {
|
||||
string: {
|
||||
Settings: '' as IntlString,
|
||||
Setting: '' as IntlString,
|
||||
WorkspaceSetting: '' as IntlString,
|
||||
Integrations: '' as IntlString,
|
||||
ManageStatuses: '' as IntlString,
|
||||
Support: '' as IntlString,
|
||||
@ -127,7 +131,8 @@ export default plugin(settingId, {
|
||||
InviteWorkspace: '' as IntlString,
|
||||
SelectWorkspace: '' as IntlString,
|
||||
Reconnect: '' as IntlString,
|
||||
ClassSetting: '' as IntlString
|
||||
ClassSetting: '' as IntlString,
|
||||
Owners: '' as IntlString
|
||||
},
|
||||
icon: {
|
||||
EditProfile: '' as Asset,
|
||||
|
@ -14,8 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Doc, WithLookup } from '@anticrm/core'
|
||||
import core, { Class, Client, matchQuery, Ref } from '@anticrm/core'
|
||||
import core, { AccountRole, Doc, getCurrentAccount, WithLookup, Class, Client, matchQuery, Ref } from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import type { Action, ViewAction, ViewActionInput, ViewContextType } from '@anticrm/view'
|
||||
import view from './plugin'
|
||||
@ -118,6 +117,7 @@ export function filterActions (
|
||||
): Array<WithLookup<Action>> {
|
||||
let result: Array<WithLookup<Action>> = []
|
||||
const hierarchy = client.getHierarchy()
|
||||
const role = getCurrentAccount().role
|
||||
const clazz = hierarchy.getClass(doc._class)
|
||||
const ignoreActions = hierarchy.as(clazz, view.mixin.IgnoreActions)
|
||||
const ignore = ignoreActions?.actions ?? []
|
||||
@ -126,6 +126,9 @@ export function filterActions (
|
||||
if (ignore.includes(action._id)) {
|
||||
continue
|
||||
}
|
||||
if (role < AccountRole.Maintainer && action.secured === true) {
|
||||
continue
|
||||
}
|
||||
if (action.override !== undefined) {
|
||||
overrideRemove.push(...action.override)
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
export let value: any
|
||||
export let withoutUndefined: boolean = false
|
||||
export let onChange: (value: any) => void
|
||||
|
||||
export let disabled: boolean = false
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
export let size: ButtonSize = 'small'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
@ -36,8 +36,9 @@
|
||||
{size}
|
||||
{justify}
|
||||
{width}
|
||||
{disabled}
|
||||
on:click={(ev) => {
|
||||
if (!shown) {
|
||||
if (!shown && !disabled) {
|
||||
showPopup(BooleanEditorPopup, { value, withoutUndefined }, eventToHTMLElement(ev), (res) => {
|
||||
if (res !== undefined) {
|
||||
if (res === 1) value = true
|
||||
|
@ -262,6 +262,9 @@ export interface Action<T extends Doc = Doc, P = Record<string, any>> extends Do
|
||||
// A list of actions replaced by this one.
|
||||
// For example it could be global action and action for focus class, second one fill override first one.
|
||||
override?: Ref<Action>[]
|
||||
|
||||
// Avaible only for workspace owners
|
||||
secured?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,10 +14,10 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
|
||||
import { getCurrentAccount } from '@anticrm/core'
|
||||
import { AccountRole, getCurrentAccount } from '@anticrm/core'
|
||||
import login from '@anticrm/login'
|
||||
import { Avatar, createQuery, getClient } from '@anticrm/presentation'
|
||||
import setting, { SettingsCategory, settingId } from '@anticrm/setting'
|
||||
import { Avatar, createQuery } from '@anticrm/presentation'
|
||||
import setting, { settingId, SettingsCategory } from '@anticrm/setting'
|
||||
import {
|
||||
closePanel,
|
||||
closePopup,
|
||||
@ -26,17 +26,20 @@
|
||||
Label,
|
||||
navigate,
|
||||
setMetadataLocalStorage,
|
||||
showPopup,
|
||||
Submenu,
|
||||
locationToUrl
|
||||
showPopup
|
||||
} from '@anticrm/ui'
|
||||
import type { Action } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
const client = getClient()
|
||||
async function getItems (): Promise<SettingsCategory[]> {
|
||||
return await client.findAll(setting.class.SettingsCategory, {}, { sort: { order: 1 } })
|
||||
}
|
||||
let items: SettingsCategory[] = []
|
||||
|
||||
const settingsQuery = createQuery()
|
||||
settingsQuery.query(
|
||||
setting.class.SettingsCategory,
|
||||
{},
|
||||
(res) => {
|
||||
items = account.role > AccountRole.User ? res : res.filter((p) => p.secured === false)
|
||||
},
|
||||
{ sort: { order: 1 } }
|
||||
)
|
||||
|
||||
const account = getCurrentAccount() as EmployeeAccount
|
||||
let employee: Employee | undefined
|
||||
@ -80,84 +83,61 @@
|
||||
}
|
||||
|
||||
function filterItems (items: SettingsCategory[]): SettingsCategory[] {
|
||||
return items?.filter((p) => p.name !== 'profile' && p.name !== 'password')
|
||||
return items.filter((p) => p._id !== setting.ids.Profile && p._id !== setting.ids.Password)
|
||||
}
|
||||
|
||||
function editProfile (items: SettingsCategory[] | undefined): void {
|
||||
const profile = items?.find((p) => p.name === 'profile')
|
||||
function editProfile (items: SettingsCategory[]): void {
|
||||
const profile = items.find((p) => p._id === setting.ids.Profile)
|
||||
if (profile === undefined) return
|
||||
selectCategory(profile)
|
||||
}
|
||||
|
||||
function getURLCategory (sp: SettingsCategory): string {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = settingId
|
||||
loc.path[2] = sp.name
|
||||
loc.path.length = 3
|
||||
return locationToUrl(loc)
|
||||
}
|
||||
|
||||
const getSubmenu = (items: SettingsCategory[]): Action[] => {
|
||||
const actions: Action[] = filterItems(items).map((i) => {
|
||||
return {
|
||||
icon: i.icon,
|
||||
label: i.label,
|
||||
action: async () => selectCategory(i),
|
||||
link: getURLCategory(i),
|
||||
inline: true
|
||||
}
|
||||
})
|
||||
return actions
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="selectPopup autoHeight">
|
||||
<div class="scroll">
|
||||
<div class="box">
|
||||
{#await getItems() then items}
|
||||
<div
|
||||
class="menu-item high flex-row-center"
|
||||
on:click={() => {
|
||||
editProfile(items)
|
||||
}}
|
||||
>
|
||||
{#if employee}
|
||||
<Avatar avatar={employee.avatar} size={'medium'} />
|
||||
{/if}
|
||||
<div class="ml-2 flex-col">
|
||||
{#if account}
|
||||
<div class="overflow-label fs-bold caption-color">{formatName(account.name)}</div>
|
||||
<div class="overflow-label text-sm content-dark-color">{account.email}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if items}
|
||||
<Submenu
|
||||
icon={view.icon.Setting}
|
||||
label={setting.string.Settings}
|
||||
props={{ actions: getSubmenu(items) }}
|
||||
withHover
|
||||
/>
|
||||
<div
|
||||
class="menu-item high flex-row-center"
|
||||
on:click={() => {
|
||||
editProfile(items)
|
||||
}}
|
||||
>
|
||||
{#if employee}
|
||||
<Avatar avatar={employee.avatar} size={'medium'} />
|
||||
{/if}
|
||||
<button class="menu-item" on:click={selectWorkspace}>
|
||||
<div class="icon mr-3">
|
||||
<Icon icon={setting.icon.SelectWorkspace} size={'small'} />
|
||||
<div class="ml-2 flex-col">
|
||||
{#if account}
|
||||
<div class="overflow-label fs-bold caption-color">{formatName(account.name)}</div>
|
||||
<div class="overflow-label text-sm content-dark-color">{account.email}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#each filterItems(items) as item}
|
||||
<button class="menu-item" on:click={() => selectCategory(item)}>
|
||||
<div class="mr-2">
|
||||
<Icon icon={item.icon} size={'small'} />
|
||||
</div>
|
||||
<Label label={setting.string.SelectWorkspace} />
|
||||
<Label label={item.label} />
|
||||
</button>
|
||||
<button class="menu-item" on:click={inviteWorkspace}>
|
||||
<div class="icon mr-3">
|
||||
<Icon icon={login.icon.InviteWorkspace} size={'small'} />
|
||||
</div>
|
||||
<Label label={setting.string.InviteWorkspace} />
|
||||
</button>
|
||||
<button class="menu-item" on:click={signOut}>
|
||||
<div class="icon mr-3">
|
||||
<Icon icon={setting.icon.Signout} size={'small'} />
|
||||
</div>
|
||||
<Label label={setting.string.Signout} />
|
||||
</button>
|
||||
{/await}
|
||||
{/each}
|
||||
<button class="menu-item" on:click={selectWorkspace}>
|
||||
<div class="icon mr-3">
|
||||
<Icon icon={setting.icon.SelectWorkspace} size={'small'} />
|
||||
</div>
|
||||
<Label label={setting.string.SelectWorkspace} />
|
||||
</button>
|
||||
<button class="menu-item" on:click={inviteWorkspace}>
|
||||
<div class="icon mr-3">
|
||||
<Icon icon={login.icon.InviteWorkspace} size={'small'} />
|
||||
</div>
|
||||
<Label label={setting.string.InviteWorkspace} />
|
||||
</button>
|
||||
<button class="menu-item" on:click={signOut}>
|
||||
<div class="icon mr-3">
|
||||
<Icon icon={setting.icon.Signout} size={'small'} />
|
||||
</div>
|
||||
<Label label={setting.string.Signout} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import calendar from '@anticrm/calendar'
|
||||
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import core, { Class, Client, Doc, getCurrentAccount, Ref, Space } from '@anticrm/core'
|
||||
import core, { Class, Client, Doc, getCurrentAccount, Ref, setCurrentAccount, Space } from '@anticrm/core'
|
||||
import notification, { NotificationStatus } from '@anticrm/notification'
|
||||
import { NotificationClientImpl, BrowserNotificatator } from '@anticrm/notification-resources'
|
||||
import { getMetadata, getResource, IntlString } from '@anticrm/platform'
|
||||
@ -93,7 +93,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
const account = getCurrentAccount() as EmployeeAccount
|
||||
let account = getCurrentAccount() as EmployeeAccount
|
||||
const accountQ = createQuery()
|
||||
accountQ.query(
|
||||
contact.class.EmployeeAccount,
|
||||
{
|
||||
_id: account._id
|
||||
},
|
||||
(res) => {
|
||||
account = res[0]
|
||||
setCurrentAccount(account)
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
let employee: Employee | undefined
|
||||
const employeeQ = createQuery()
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import contact, { combineName, Employee } from '@anticrm/contact'
|
||||
import core, { Ref, TxOperations } from '@anticrm/core'
|
||||
import core, { AccountRole, Ref, TxOperations } from '@anticrm/core'
|
||||
import platform, {
|
||||
getMetadata,
|
||||
PlatformError,
|
||||
@ -380,6 +380,7 @@ export async function createUserWorkspace (db: Db, token: string, workspace: str
|
||||
const { email } = decodeToken(token)
|
||||
await createWorkspace(db, workspace, '')
|
||||
await assignWorkspace(db, email, workspace)
|
||||
await setRole(email, workspace, AccountRole.Owner)
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
@ -445,6 +446,26 @@ async function getWorkspaceAndAccount (
|
||||
return { accountId, workspaceId }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function setRole (email: string, workspace: string, role: AccountRole): Promise<void> {
|
||||
const connection = await connect(getTransactor(), workspace, email)
|
||||
try {
|
||||
const ops = new TxOperations(connection, core.account.System)
|
||||
|
||||
const existingAccount = await ops.findOne(contact.class.EmployeeAccount, { email })
|
||||
|
||||
if (existingAccount !== undefined) {
|
||||
await ops.update(existingAccount, {
|
||||
role
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
await connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -485,7 +506,8 @@ async function createEmployeeAccount (account: Account, workspace: string): Prom
|
||||
await ops.createDoc(contact.class.EmployeeAccount, core.space.Model, {
|
||||
email: account.email,
|
||||
employee,
|
||||
name
|
||||
name,
|
||||
role: 0
|
||||
})
|
||||
} else {
|
||||
const employee = await ops.findOne(contact.class.Employee, { _id: existingAccount.employee })
|
||||
|
@ -174,8 +174,8 @@ export function genMinModel (): TxCUD<Doc>[] {
|
||||
const u1 = 'User1' as Ref<Account>
|
||||
const u2 = 'User2' as Ref<Account>
|
||||
txes.push(
|
||||
createDoc(core.class.Account, { email: 'user1@site.com' }, u1),
|
||||
createDoc(core.class.Account, { email: 'user2@site.com' }, u2),
|
||||
createDoc(core.class.Account, { email: 'user1@site.com', role: 0 }, u1),
|
||||
createDoc(core.class.Account, { email: 'user2@site.com', role: 0 }, u2),
|
||||
createDoc(core.class.Space, {
|
||||
name: 'Sp1',
|
||||
description: '',
|
||||
|
@ -174,8 +174,8 @@ export function genMinModel (): TxCUD<Doc>[] {
|
||||
const u1 = 'User1' as Ref<Account>
|
||||
const u2 = 'User2' as Ref<Account>
|
||||
txes.push(
|
||||
createDoc(core.class.Account, { email: 'user1@site.com' }, u1),
|
||||
createDoc(core.class.Account, { email: 'user2@site.com' }, u2),
|
||||
createDoc(core.class.Account, { email: 'user1@site.com', role: 0 }, u1),
|
||||
createDoc(core.class.Account, { email: 'user2@site.com', role: 0 }, u2),
|
||||
createDoc(core.class.Space, {
|
||||
name: 'Sp1',
|
||||
description: '',
|
||||
|
@ -14,9 +14,7 @@ test.describe('contact tests', () => {
|
||||
await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp`)
|
||||
// Click #profile-button
|
||||
await page.click('#profile-button')
|
||||
await page.click('.antiPopup-submenu >> text=Settings')
|
||||
// Click button:has-text("Setting")
|
||||
await page.click('button:has-text("Setting")')
|
||||
await page.click('text=Workspace')
|
||||
await expect(page).toHaveURL(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/setting/setting`)
|
||||
// Click text=Edit profile
|
||||
await page.click('text=Edit profile')
|
||||
@ -43,12 +41,8 @@ test.describe('contact tests', () => {
|
||||
await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp`)
|
||||
// Click #profile-button
|
||||
await page.click('#profile-button')
|
||||
await page.click('.antiPopup-submenu >> text=Settings')
|
||||
// Click button:has-text("Templates")
|
||||
await page.click('button:has-text("Templates")')
|
||||
await expect(page).toHaveURL(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/setting/message-templates`)
|
||||
// Go to http://localhost:8083/workbench%3Acomponent%3AWorkbenchApp/setting/message-templates
|
||||
await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/setting/message-templates`)
|
||||
await page.click('text=Workspace')
|
||||
await page.click('text=Templates')
|
||||
// Click .flex-center.icon-button
|
||||
await page.click('#create-template >> .flex-center.icon-button')
|
||||
// Click [placeholder="New\ template"]
|
||||
@ -76,10 +70,9 @@ test.describe('contact tests', () => {
|
||||
await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp`)
|
||||
// Click #profile-button
|
||||
await page.click('#profile-button')
|
||||
await page.click('.antiPopup-submenu >> text=Settings')
|
||||
await page.click('text=Workspace')
|
||||
// Click button:has-text("Manage Statuses")
|
||||
await page.click('button:has-text("Manage Statuses")')
|
||||
await expect(page).toHaveURL(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/setting/statuses`)
|
||||
await page.click('text="Manage Statuses"')
|
||||
// Click text=Vacancies
|
||||
await page.click('text=Vacancies')
|
||||
// Click #create-template div
|
||||
|
@ -50,16 +50,4 @@ test.describe('workbench tests', () => {
|
||||
// Click text=John Appleseed
|
||||
await expect(page.locator('text=John Appleseed')).toBeVisible()
|
||||
})
|
||||
test('submenu', async ({ page }) => {
|
||||
// await page.goto('http://localhost:8080/workbench%3Acomponent%3AWorkbenchApp');
|
||||
// Click #profile-button
|
||||
await page.click('#profile-button')
|
||||
// Click text=Settings
|
||||
await page.click('.antiPopup-submenu >> text=Settings')
|
||||
// Click button:has-text("Terms")
|
||||
await page.click('button:has-text("Terms")')
|
||||
await expect(page).toHaveURL(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/setting/terms`)
|
||||
// Click .ac-header
|
||||
await expect(page.locator('.ac-header >> text=Terms')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user