parent
							
								
									74910f8d70
								
							
						
					
					
						commit
						c5c40a73b7
					
				
					 9 changed files with 296 additions and 4 deletions
				
			
		|  | @ -938,7 +938,12 @@ _role: | ||||||
|   name: "ロール名" |   name: "ロール名" | ||||||
|   description: "ロールの説明" |   description: "ロールの説明" | ||||||
|   permission: "ロールの権限" |   permission: "ロールの権限" | ||||||
|   descriptionOfType: "モデレーターは基本的なモデレーションに関する操作を行えます。\n管理者はインスタンスの全ての設定を変更できます。" |   descriptionOfPermission: "<b>モデレーター</b>は基本的なモデレーションに関する操作を行えます。\n<b>管理者</b>はインスタンスの全ての設定を変更できます。" | ||||||
|  |   assignTarget: "アサインターゲット" | ||||||
|  |   descriptionOfAssignTarget: "<b>マニュアル</b>は誰がこのロールに含まれるかを手動で管理します。\n<b>コンディショナル</b>は条件を設定し、それに合致するユーザーが自動で含まれるようになります。" | ||||||
|  |   manual: "マニュアル" | ||||||
|  |   conditional: "コンディショナル" | ||||||
|  |   condition: "条件" | ||||||
|   isPublic: "ロールを公開" |   isPublic: "ロールを公開" | ||||||
|   descriptionOfIsPublic: "ロールにアサインされたユーザーを誰でも見ることができます。また、ユーザーのプロフィールでこのロールが表示されます。" |   descriptionOfIsPublic: "ロールにアサインされたユーザーを誰でも見ることができます。また、ユーザーのプロフィールでこのロールが表示されます。" | ||||||
|   options: "オプション" |   options: "オプション" | ||||||
|  | @ -953,6 +958,14 @@ _role: | ||||||
|     canPublicNote: "パブリック投稿の許可" |     canPublicNote: "パブリック投稿の許可" | ||||||
|     driveCapacity: "ドライブ容量" |     driveCapacity: "ドライブ容量" | ||||||
|     antennaMax: "アンテナの作成可能数" |     antennaMax: "アンテナの作成可能数" | ||||||
|  |   _condition: | ||||||
|  |     isLocal: "ローカルユーザー" | ||||||
|  |     isRemote: "リモートユーザー" | ||||||
|  |     createdLessThan: "アカウント作成から~以内" | ||||||
|  |     createdMoreThan: "アカウント作成から~経過" | ||||||
|  |     and: "~かつ~" | ||||||
|  |     or: "~または~" | ||||||
|  |     not: "~ではない" | ||||||
| 
 | 
 | ||||||
| _sensitiveMediaDetection: | _sensitiveMediaDetection: | ||||||
|   description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" |   description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								packages/backend/migration/1673570377815-RoleConditional.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								packages/backend/migration/1673570377815-RoleConditional.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | export class RoleConditional1673570377815 { | ||||||
|  |     name = 'RoleConditional1673570377815' | ||||||
|  | 
 | ||||||
|  |     async up(queryRunner) { | ||||||
|  |         await queryRunner.query(`CREATE TYPE "public"."role_target_enum" AS ENUM('manual', 'conditional')`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "role" ADD "target" "public"."role_target_enum" NOT NULL DEFAULT 'manual'`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "role" ADD "condFormula" jsonb NOT NULL DEFAULT '{}'`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async down(queryRunner) { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "condFormula"`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "target"`); | ||||||
|  |         await queryRunner.query(`DROP TYPE "public"."role_target_enum"`); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -7,6 +7,9 @@ import type { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/mode | ||||||
| import { DI } from '@/di-symbols.js'; | import { DI } from '@/di-symbols.js'; | ||||||
| import { bindThis } from '@/decorators.js'; | import { bindThis } from '@/decorators.js'; | ||||||
| import { MetaService } from '@/core/MetaService.js'; | import { MetaService } from '@/core/MetaService.js'; | ||||||
|  | import { UserCacheService } from '@/core/UserCacheService.js'; | ||||||
|  | import { RoleCondFormulaValue } from '@/models/entities/Role.js'; | ||||||
|  | import { UserEntityService } from '@/core/entities/UserEntityService.js'; | ||||||
| import type { OnApplicationShutdown } from '@nestjs/common'; | import type { OnApplicationShutdown } from '@nestjs/common'; | ||||||
| 
 | 
 | ||||||
| export type RoleOptions = { | export type RoleOptions = { | ||||||
|  | @ -44,6 +47,8 @@ export class RoleService implements OnApplicationShutdown { | ||||||
| 		private roleAssignmentsRepository: RoleAssignmentsRepository, | 		private roleAssignmentsRepository: RoleAssignmentsRepository, | ||||||
| 
 | 
 | ||||||
| 		private metaService: MetaService, | 		private metaService: MetaService, | ||||||
|  | 		private userCacheService: UserCacheService, | ||||||
|  | 		private userEntityService: UserEntityService, | ||||||
| 	) { | 	) { | ||||||
| 		//this.onMessage = this.onMessage.bind(this);
 | 		//this.onMessage = this.onMessage.bind(this);
 | ||||||
| 
 | 
 | ||||||
|  | @ -111,12 +116,49 @@ export class RoleService implements OnApplicationShutdown { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@bindThis | ||||||
|  | 	private evalCond(user: User, value: RoleCondFormulaValue): boolean { | ||||||
|  | 		try { | ||||||
|  | 			switch (value.type) { | ||||||
|  | 				case 'and': { | ||||||
|  | 					return value.values.every(v => this.evalCond(user, v)); | ||||||
|  | 				} | ||||||
|  | 				case 'or': { | ||||||
|  | 					return value.values.some(v => this.evalCond(user, v)); | ||||||
|  | 				} | ||||||
|  | 				case 'not': { | ||||||
|  | 					return !this.evalCond(user, value.value); | ||||||
|  | 				} | ||||||
|  | 				case 'isLocal': { | ||||||
|  | 					return this.userEntityService.isLocalUser(user); | ||||||
|  | 				} | ||||||
|  | 				case 'isRemote': { | ||||||
|  | 					return this.userEntityService.isRemoteUser(user); | ||||||
|  | 				} | ||||||
|  | 				case 'createdLessThan': { | ||||||
|  | 					return user.createdAt.getTime() > (Date.now() - (value.sec * 1000)); | ||||||
|  | 				} | ||||||
|  | 				case 'createdMoreThan': { | ||||||
|  | 					return user.createdAt.getTime() < (Date.now() - (value.sec * 1000)); | ||||||
|  | 				} | ||||||
|  | 				default: | ||||||
|  | 					return false; | ||||||
|  | 			} | ||||||
|  | 		} catch (err) { | ||||||
|  | 			// TODO: log error
 | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@bindThis | 	@bindThis | ||||||
| 	public async getUserRoles(userId: User['id']) { | 	public async getUserRoles(userId: User['id']) { | ||||||
| 		const assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); | 		const assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); | ||||||
| 		const assignedRoleIds = assigns.map(x => x.roleId); | 		const assignedRoleIds = assigns.map(x => x.roleId); | ||||||
| 		const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({})); | 		const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({})); | ||||||
| 		return roles.filter(r => assignedRoleIds.includes(r.id)); | 		const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id)); | ||||||
|  | 		const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null; | ||||||
|  | 		const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula)); | ||||||
|  | 		return [...assignedRoles, ...matchedCondRoles]; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@bindThis | 	@bindThis | ||||||
|  |  | ||||||
|  | @ -55,6 +55,8 @@ export class RoleEntityService { | ||||||
| 			name: role.name, | 			name: role.name, | ||||||
| 			description: role.description, | 			description: role.description, | ||||||
| 			color: role.color, | 			color: role.color, | ||||||
|  | 			target: role.target, | ||||||
|  | 			condFormula: role.condFormula, | ||||||
| 			isPublic: role.isPublic, | 			isPublic: role.isPublic, | ||||||
| 			isAdministrator: role.isAdministrator, | 			isAdministrator: role.isAdministrator, | ||||||
| 			isModerator: role.isModerator, | 			isModerator: role.isModerator, | ||||||
|  |  | ||||||
|  | @ -1,6 +1,48 @@ | ||||||
| import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; | import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; | ||||||
| import { id } from '../id.js'; | import { id } from '../id.js'; | ||||||
| 
 | 
 | ||||||
|  | type CondFormulaValueAnd = { | ||||||
|  | 	type: 'and'; | ||||||
|  | 	values: RoleCondFormulaValue[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type CondFormulaValueOr = { | ||||||
|  | 	type: 'or'; | ||||||
|  | 	values: RoleCondFormulaValue[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type CondFormulaValueNot = { | ||||||
|  | 	type: 'not'; | ||||||
|  | 	value: RoleCondFormulaValue; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type CondFormulaValueIsLocal = { | ||||||
|  | 	type: 'isLocal'; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type CondFormulaValueIsRemote = { | ||||||
|  | 	type: 'isRemote'; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type CondFormulaValueCreatedLessThan = { | ||||||
|  | 	type: 'createdLessThan'; | ||||||
|  | 	sec: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type CondFormulaValueCreatedMoreThan = { | ||||||
|  | 	type: 'createdMoreThan'; | ||||||
|  | 	sec: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type RoleCondFormulaValue = | ||||||
|  | 	CondFormulaValueAnd | | ||||||
|  | 	CondFormulaValueOr | | ||||||
|  | 	CondFormulaValueNot | | ||||||
|  | 	CondFormulaValueIsLocal | | ||||||
|  | 	CondFormulaValueIsRemote | | ||||||
|  | 	CondFormulaValueCreatedLessThan | | ||||||
|  | 	CondFormulaValueCreatedMoreThan; | ||||||
|  | 
 | ||||||
| @Entity() | @Entity() | ||||||
| export class Role { | export class Role { | ||||||
| 	@PrimaryColumn(id()) | 	@PrimaryColumn(id()) | ||||||
|  | @ -36,6 +78,17 @@ export class Role { | ||||||
| 	}) | 	}) | ||||||
| 	public color: string | null; | 	public color: string | null; | ||||||
| 
 | 
 | ||||||
|  | 	@Column('enum', { | ||||||
|  | 		enum: ['manual', 'conditional'], | ||||||
|  | 		default: 'manual', | ||||||
|  | 	}) | ||||||
|  | 	public target: 'manual' | 'conditional'; | ||||||
|  | 
 | ||||||
|  | 	@Column('jsonb', { | ||||||
|  | 		default: { }, | ||||||
|  | 	}) | ||||||
|  | 	public condFormula: RoleCondFormulaValue; | ||||||
|  | 
 | ||||||
| 	@Column('boolean', { | 	@Column('boolean', { | ||||||
| 		default: false, | 		default: false, | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | @ -19,6 +19,8 @@ export const paramDef = { | ||||||
| 		name: { type: 'string' }, | 		name: { type: 'string' }, | ||||||
| 		description: { type: 'string' }, | 		description: { type: 'string' }, | ||||||
| 		color: { type: 'string', nullable: true }, | 		color: { type: 'string', nullable: true }, | ||||||
|  | 		target: { type: 'string' }, | ||||||
|  | 		condFormula: { type: 'object' }, | ||||||
| 		isPublic: { type: 'boolean' }, | 		isPublic: { type: 'boolean' }, | ||||||
| 		isModerator: { type: 'boolean' }, | 		isModerator: { type: 'boolean' }, | ||||||
| 		isAdministrator: { type: 'boolean' }, | 		isAdministrator: { type: 'boolean' }, | ||||||
|  | @ -31,6 +33,8 @@ export const paramDef = { | ||||||
| 		'name', | 		'name', | ||||||
| 		'description', | 		'description', | ||||||
| 		'color', | 		'color', | ||||||
|  | 		'target', | ||||||
|  | 		'condFormula', | ||||||
| 		'isPublic', | 		'isPublic', | ||||||
| 		'isModerator', | 		'isModerator', | ||||||
| 		'isAdministrator', | 		'isAdministrator', | ||||||
|  | @ -60,6 +64,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||||
| 				name: ps.name, | 				name: ps.name, | ||||||
| 				description: ps.description, | 				description: ps.description, | ||||||
| 				color: ps.color, | 				color: ps.color, | ||||||
|  | 				target: ps.target, | ||||||
|  | 				condFormula: ps.condFormula, | ||||||
| 				isPublic: ps.isPublic, | 				isPublic: ps.isPublic, | ||||||
| 				isAdministrator: ps.isAdministrator, | 				isAdministrator: ps.isAdministrator, | ||||||
| 				isModerator: ps.isModerator, | 				isModerator: ps.isModerator, | ||||||
|  |  | ||||||
|  | @ -27,6 +27,8 @@ export const paramDef = { | ||||||
| 		name: { type: 'string' }, | 		name: { type: 'string' }, | ||||||
| 		description: { type: 'string' }, | 		description: { type: 'string' }, | ||||||
| 		color: { type: 'string', nullable: true }, | 		color: { type: 'string', nullable: true }, | ||||||
|  | 		target: { type: 'string' }, | ||||||
|  | 		condFormula: { type: 'object' }, | ||||||
| 		isPublic: { type: 'boolean' }, | 		isPublic: { type: 'boolean' }, | ||||||
| 		isModerator: { type: 'boolean' }, | 		isModerator: { type: 'boolean' }, | ||||||
| 		isAdministrator: { type: 'boolean' }, | 		isAdministrator: { type: 'boolean' }, | ||||||
|  | @ -40,6 +42,8 @@ export const paramDef = { | ||||||
| 		'name', | 		'name', | ||||||
| 		'description', | 		'description', | ||||||
| 		'color', | 		'color', | ||||||
|  | 		'target', | ||||||
|  | 		'condFormula', | ||||||
| 		'isPublic', | 		'isPublic', | ||||||
| 		'isModerator', | 		'isModerator', | ||||||
| 		'isAdministrator', | 		'isAdministrator', | ||||||
|  | @ -69,6 +73,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | ||||||
| 				name: ps.name, | 				name: ps.name, | ||||||
| 				description: ps.description, | 				description: ps.description, | ||||||
| 				color: ps.color, | 				color: ps.color, | ||||||
|  | 				target: ps.target, | ||||||
|  | 				condFormula: ps.condFormula, | ||||||
| 				isPublic: ps.isPublic, | 				isPublic: ps.isPublic, | ||||||
| 				isModerator: ps.isModerator, | 				isModerator: ps.isModerator, | ||||||
| 				isAdministrator: ps.isAdministrator, | 				isAdministrator: ps.isAdministrator, | ||||||
|  |  | ||||||
							
								
								
									
										129
									
								
								packages/frontend/src/pages/admin/RolesEditorFormula.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								packages/frontend/src/pages/admin/RolesEditorFormula.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,129 @@ | ||||||
|  | <template> | ||||||
|  | <div :class="$style.root" class="_gaps"> | ||||||
|  | 	<div :class="$style.header"> | ||||||
|  | 		<MkSelect v-model="type" :class="$style.typeSelect"> | ||||||
|  | 			<option value="isLocal">{{ i18n.ts._role._condition.isLocal }}</option> | ||||||
|  | 			<option value="isRemote">{{ i18n.ts._role._condition.isRemote }}</option> | ||||||
|  | 			<option value="createdLessThan">{{ i18n.ts._role._condition.createdLessThan }}</option> | ||||||
|  | 			<option value="createdMoreThan">{{ i18n.ts._role._condition.createdMoreThan }}</option> | ||||||
|  | 			<option value="and">{{ i18n.ts._role._condition.and }}</option> | ||||||
|  | 			<option value="or">{{ i18n.ts._role._condition.or }}</option> | ||||||
|  | 			<option value="not">{{ i18n.ts._role._condition.not }}</option> | ||||||
|  | 		</MkSelect> | ||||||
|  | 		<button v-if="draggable" class="drag-handle _button" :class="$style.dragHandle"> | ||||||
|  | 			<i class="ti ti-menu-2"></i> | ||||||
|  | 		</button> | ||||||
|  | 	</div> | ||||||
|  | 
 | ||||||
|  | 	<div v-if="type === 'and' || type === 'or'" :class="$style.values" class="_gaps"> | ||||||
|  | 		<Sortable v-model="v.values" tag="div" class="_gaps" item-key="id" handle=".drag-handle" :group="{ name: 'roleFormula' }" :animation="150" :swap-threshold="0.5"> | ||||||
|  | 			<template #item="{element}"> | ||||||
|  | 				<div :class="$style.item"> | ||||||
|  | 					<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 --> | ||||||
|  | 					<RolesEditorFormula :model-value="element" draggable @update:model-value="updated => valuesItemUpdated(updated)"/> | ||||||
|  | 				</div> | ||||||
|  | 			</template> | ||||||
|  | 		</Sortable> | ||||||
|  | 		<MkButton rounded style="margin: 0 auto;" @click="addValue"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> | ||||||
|  | 	</div> | ||||||
|  | 
 | ||||||
|  | 	<div v-else-if="type === 'not'" :class="$style.item"> | ||||||
|  | 		<RolesEditorFormula v-model="v.value"/> | ||||||
|  | 	</div> | ||||||
|  | 
 | ||||||
|  | 	<MkInput v-else-if="type === 'createdLessThan' || type === 'createdMoreThan'" v-model="v.sec" type="number"> | ||||||
|  | 		<template #suffix>sec</template> | ||||||
|  | 	</MkInput> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { computed, defineAsyncComponent, ref, watch } from 'vue'; | ||||||
|  | import { v4 as uuid } from 'uuid'; | ||||||
|  | import MkInput from '@/components/MkInput.vue'; | ||||||
|  | import MkSelect from '@/components/MkSelect.vue'; | ||||||
|  | import MkTextarea from '@/components/MkTextarea.vue'; | ||||||
|  | import MkFolder from '@/components/MkFolder.vue'; | ||||||
|  | import MkSwitch from '@/components/MkSwitch.vue'; | ||||||
|  | import MkButton from '@/components/MkButton.vue'; | ||||||
|  | import FormSlot from '@/components/form/slot.vue'; | ||||||
|  | import * as os from '@/os'; | ||||||
|  | import { i18n } from '@/i18n'; | ||||||
|  | import { deepClone } from '@/scripts/clone'; | ||||||
|  | 
 | ||||||
|  | const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits<{ | ||||||
|  | 	(ev: 'update:modelValue', value: any): void; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | const props = defineProps<{ | ||||||
|  | 	modelValue: any; | ||||||
|  | 	draggable?: boolean; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | const v = ref(deepClone(props.modelValue)); | ||||||
|  | 
 | ||||||
|  | watch(() => props.modelValue, () => { | ||||||
|  | 	if (JSON.stringify(props.modelValue) === JSON.stringify(v.value)) return; | ||||||
|  | 	v.value = deepClone(props.modelValue); | ||||||
|  | }, { deep: true }); | ||||||
|  | 
 | ||||||
|  | watch(v, () => { | ||||||
|  | 	emit('update:modelValue', v.value); | ||||||
|  | }, { deep: true }); | ||||||
|  | 
 | ||||||
|  | const type = computed({ | ||||||
|  | 	get: () => v.value.type, | ||||||
|  | 	set: (t) => { | ||||||
|  | 		if (t === 'and') v.value.values = []; | ||||||
|  | 		if (t === 'or') v.value.values = []; | ||||||
|  | 		if (t === 'not') v.value.value = { id: uuid(), type: 'isRemote' }; | ||||||
|  | 		if (t === 'createdLessThan') v.value.sec = 86400; | ||||||
|  | 		if (t === 'createdMoreThan') v.value.sec = 86400; | ||||||
|  | 		v.value.type = t; | ||||||
|  | 	}, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function addValue() { | ||||||
|  | 	v.value.values.push({ id: uuid(), type: 'isRemote' }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function valuesItemUpdated(item) { | ||||||
|  | 	const i = v.value.values.findIndex(_item => _item.id === item.id); | ||||||
|  | 	v.value.values[i] = item; | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" module> | ||||||
|  | .root { | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .header { | ||||||
|  | 	display: flex; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .typeSelect { | ||||||
|  | 	flex: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dragHandle { | ||||||
|  | 	cursor: move; | ||||||
|  | 	margin-left: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .item { | ||||||
|  | 	border: solid 2px var(--divider); | ||||||
|  | 	border-radius: var(--radius); | ||||||
|  | 	padding: 12px; | ||||||
|  | 
 | ||||||
|  | 	&:hover { | ||||||
|  | 		border-color: var(--accent); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .values { | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -15,12 +15,26 @@ | ||||||
| 
 | 
 | ||||||
| 	<MkSelect v-model="rolePermission" :readonly="readonly"> | 	<MkSelect v-model="rolePermission" :readonly="readonly"> | ||||||
| 		<template #label>{{ i18n.ts._role.permission }}</template> | 		<template #label>{{ i18n.ts._role.permission }}</template> | ||||||
| 		<template #caption><div v-html="i18n.ts._role.descriptionOfType.replaceAll('\n', '<br>')"></div></template> | 		<template #caption><div v-html="i18n.ts._role.descriptionOfPermission.replaceAll('\n', '<br>')"></div></template> | ||||||
| 		<option value="normal">{{ i18n.ts.normalUser }}</option> | 		<option value="normal">{{ i18n.ts.normalUser }}</option> | ||||||
| 		<option value="moderator">{{ i18n.ts.moderator }}</option> | 		<option value="moderator">{{ i18n.ts.moderator }}</option> | ||||||
| 		<option value="administrator">{{ i18n.ts.administrator }}</option> | 		<option value="administrator">{{ i18n.ts.administrator }}</option> | ||||||
| 	</MkSelect> | 	</MkSelect> | ||||||
| 
 | 
 | ||||||
|  | 	<MkSelect v-model="target" :readonly="readonly"> | ||||||
|  | 		<template #label>{{ i18n.ts._role.assignTarget }}</template> | ||||||
|  | 		<template #caption><div v-html="i18n.ts._role.descriptionOfAssignTarget.replaceAll('\n', '<br>')"></div></template> | ||||||
|  | 		<option value="manual">{{ i18n.ts._role.manual }}</option> | ||||||
|  | 		<option value="conditional">{{ i18n.ts._role.conditional }}</option> | ||||||
|  | 	</MkSelect> | ||||||
|  | 
 | ||||||
|  | 	<MkFolder v-if="target === 'conditional'" default-open> | ||||||
|  | 		<template #label>{{ i18n.ts._role.condition }}</template> | ||||||
|  | 		<div class="_gaps"> | ||||||
|  | 			<RolesEditorFormula v-model="condFormula"/> | ||||||
|  | 		</div> | ||||||
|  | 	</MkFolder> | ||||||
|  | 
 | ||||||
| 	<FormSlot> | 	<FormSlot> | ||||||
| 		<template #label>{{ i18n.ts._role.options }}</template> | 		<template #label>{{ i18n.ts._role.options }}</template> | ||||||
| 		<div class="_gaps_s"> | 		<div class="_gaps_s"> | ||||||
|  | @ -107,7 +121,9 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { computed } from 'vue'; | import { computed, watch } from 'vue'; | ||||||
|  | import { v4 as uuid } from 'uuid'; | ||||||
|  | import RolesEditorFormula from './RolesEditorFormula.vue'; | ||||||
| import MkInput from '@/components/MkInput.vue'; | import MkInput from '@/components/MkInput.vue'; | ||||||
| import MkSelect from '@/components/MkSelect.vue'; | import MkSelect from '@/components/MkSelect.vue'; | ||||||
| import MkTextarea from '@/components/MkTextarea.vue'; | import MkTextarea from '@/components/MkTextarea.vue'; | ||||||
|  | @ -134,6 +150,8 @@ let name = $ref(role?.name ?? 'New Role'); | ||||||
| let description = $ref(role?.description ?? ''); | let description = $ref(role?.description ?? ''); | ||||||
| let rolePermission = $ref(role?.isAdministrator ? 'administrator' : role?.isModerator ? 'moderator' : 'normal'); | let rolePermission = $ref(role?.isAdministrator ? 'administrator' : role?.isModerator ? 'moderator' : 'normal'); | ||||||
| let color = $ref(role?.color ?? null); | let color = $ref(role?.color ?? null); | ||||||
|  | let target = $ref(role?.target ?? 'manual'); | ||||||
|  | let condFormula = $ref(role?.condFormula ?? { id: uuid(), type: 'isRemote' }); | ||||||
| let isPublic = $ref(role?.isPublic ?? false); | let isPublic = $ref(role?.isPublic ?? false); | ||||||
| let canEditMembersByModerator = $ref(role?.canEditMembersByModerator ?? false); | let canEditMembersByModerator = $ref(role?.canEditMembersByModerator ?? false); | ||||||
| let options_gtlAvailable_useDefault = $ref(role?.options?.gtlAvailable?.useDefault ?? true); | let options_gtlAvailable_useDefault = $ref(role?.options?.gtlAvailable?.useDefault ?? true); | ||||||
|  | @ -147,6 +165,10 @@ let options_driveCapacityMb_value = $ref(role?.options?.driveCapacityMb?.value ? | ||||||
| let options_antennaLimit_useDefault = $ref(role?.options?.antennaLimit?.useDefault ?? true); | let options_antennaLimit_useDefault = $ref(role?.options?.antennaLimit?.useDefault ?? true); | ||||||
| let options_antennaLimit_value = $ref(role?.options?.antennaLimit?.value ?? 0); | let options_antennaLimit_value = $ref(role?.options?.antennaLimit?.value ?? 0); | ||||||
| 
 | 
 | ||||||
|  | watch($$(condFormula), () => { | ||||||
|  | 	console.log(condFormula); | ||||||
|  | }, { deep: true }); | ||||||
|  | 
 | ||||||
| function getOptions() { | function getOptions() { | ||||||
| 	return { | 	return { | ||||||
| 		gtlAvailable: { useDefault: options_gtlAvailable_useDefault, value: options_gtlAvailable_value }, | 		gtlAvailable: { useDefault: options_gtlAvailable_useDefault, value: options_gtlAvailable_value }, | ||||||
|  | @ -165,6 +187,8 @@ async function save() { | ||||||
| 			name, | 			name, | ||||||
| 			description, | 			description, | ||||||
| 			color: color === '' ? null : color, | 			color: color === '' ? null : color, | ||||||
|  | 			target, | ||||||
|  | 			condFormula, | ||||||
| 			isAdministrator: rolePermission === 'administrator', | 			isAdministrator: rolePermission === 'administrator', | ||||||
| 			isModerator: rolePermission === 'moderator', | 			isModerator: rolePermission === 'moderator', | ||||||
| 			isPublic, | 			isPublic, | ||||||
|  | @ -177,6 +201,8 @@ async function save() { | ||||||
| 			name, | 			name, | ||||||
| 			description, | 			description, | ||||||
| 			color: color === '' ? null : color, | 			color: color === '' ? null : color, | ||||||
|  | 			target, | ||||||
|  | 			condFormula, | ||||||
| 			isAdministrator: rolePermission === 'administrator', | 			isAdministrator: rolePermission === 'administrator', | ||||||
| 			isModerator: rolePermission === 'moderator', | 			isModerator: rolePermission === 'moderator', | ||||||
| 			isPublic, | 			isPublic, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue