refactor: rename role.options -> role.policies
This commit is contained in:
		
							parent
							
								
									518b3e2f73
								
							
						
					
					
						commit
						81f11d8f86
					
				
					 53 changed files with 254 additions and 232 deletions
				
			
		|  | @ -952,6 +952,7 @@ _role: | |||
|   isPublic: "ロールを公開" | ||||
|   descriptionOfIsPublic: "ロールにアサインされたユーザーを誰でも見ることができます。また、ユーザーのプロフィールでこのロールが表示されます。" | ||||
|   options: "オプション" | ||||
|   policies: "ポリシー" | ||||
|   baseRole: "ベースロール" | ||||
|   useBaseValue: "ベースロールの値を使用" | ||||
|   chooseRoleToAssign: "アサインするロールを選択" | ||||
|  |  | |||
							
								
								
									
										13
									
								
								packages/backend/migration/1673783015567-Policies.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/backend/migration/1673783015567-Policies.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| export class Policies1673783015567 { | ||||
|     name = 'Policies1673783015567' | ||||
| 
 | ||||
|     async up(queryRunner) { | ||||
|         await queryRunner.query(`ALTER TABLE "role" RENAME COLUMN "options" TO "policies"`); | ||||
| 				await queryRunner.query(`ALTER TABLE "meta" RENAME COLUMN "defaultRoleOverride" TO "policies"`); | ||||
|     } | ||||
| 
 | ||||
|     async down(queryRunner) { | ||||
| 				await queryRunner.query(`ALTER TABLE "meta" RENAME COLUMN "policies" TO "defaultRoleOverride"`); | ||||
|         await queryRunner.query(`ALTER TABLE "role" RENAME COLUMN "policies" TO "options"`); | ||||
|     } | ||||
| } | ||||
|  | @ -479,8 +479,8 @@ export class DriveService { | |||
| 		if (user && !isLink) { | ||||
| 			const usage = await this.driveFileEntityService.calcDriveUsageOf(user); | ||||
| 
 | ||||
| 			const role = await this.roleService.getUserRoleOptions(user.id); | ||||
| 			const driveCapacity = 1024 * 1024 * role.driveCapacityMb; | ||||
| 			const policies = await this.roleService.getUserPolicies(user.id); | ||||
| 			const driveCapacity = 1024 * 1024 * policies.driveCapacityMb; | ||||
| 			this.registerLogger.debug('drive capacity override applied'); | ||||
| 			this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); | ||||
| 
 | ||||
|  |  | |||
|  | @ -226,7 +226,7 @@ export class NoteCreateService { | |||
| 		if (data.channel != null) data.localOnly = true; | ||||
| 
 | ||||
| 		if (data.visibility === 'public' && data.channel == null) { | ||||
| 			if ((await this.roleService.getUserRoleOptions(user.id)).canPublicNote === false) { | ||||
| 			if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { | ||||
| 				data.visibility = 'home'; | ||||
| 			} | ||||
| 		} | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ export class NotePiningService { | |||
| 
 | ||||
| 		const pinings = await this.userNotePiningsRepository.findBy({ userId: user.id }); | ||||
| 
 | ||||
| 		if (pinings.length >= (await this.roleService.getUserRoleOptions(user.id)).pinLimit) { | ||||
| 		if (pinings.length >= (await this.roleService.getUserPolicies(user.id)).pinLimit) { | ||||
| 			throw new IdentifiableError('15a018eb-58e5-4da1-93be-330fcc5e4e1a', 'You can not pin notes any more.'); | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; | |||
| import { StreamMessages } from '@/server/api/stream/types.js'; | ||||
| import type { OnApplicationShutdown } from '@nestjs/common'; | ||||
| 
 | ||||
| export type RoleOptions = { | ||||
| export type RolePolicies = { | ||||
| 	gtlAvailable: boolean; | ||||
| 	ltlAvailable: boolean; | ||||
| 	canPublicNote: boolean; | ||||
|  | @ -31,7 +31,7 @@ export type RoleOptions = { | |||
| 	rateLimitFactor: number; | ||||
| }; | ||||
| 
 | ||||
| export const DEFAULT_ROLE: RoleOptions = { | ||||
| export const DEFAULT_POLICIES: RolePolicies = { | ||||
| 	gtlAvailable: true, | ||||
| 	ltlAvailable: true, | ||||
| 	canPublicNote: true, | ||||
|  | @ -195,26 +195,26 @@ export class RoleService implements OnApplicationShutdown { | |||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public async getUserRoleOptions(userId: User['id'] | null): Promise<RoleOptions> { | ||||
| 	public async getUserPolicies(userId: User['id'] | null): Promise<RolePolicies> { | ||||
| 		const meta = await this.metaService.fetch(); | ||||
| 		const baseRoleOptions = { ...DEFAULT_ROLE, ...meta.defaultRoleOverride }; | ||||
| 		const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies }; | ||||
| 
 | ||||
| 		if (userId == null) return baseRoleOptions; | ||||
| 		if (userId == null) return basePolicies; | ||||
| 
 | ||||
| 		const roles = await this.getUserRoles(userId); | ||||
| 
 | ||||
| 		function calc<T extends keyof RoleOptions>(name: T, aggregate: (values: RoleOptions[T][]) => RoleOptions[T]) { | ||||
| 			if (roles.length === 0) return baseRoleOptions[name]; | ||||
| 		function calc<T extends keyof RolePolicies>(name: T, aggregate: (values: RolePolicies[T][]) => RolePolicies[T]) { | ||||
| 			if (roles.length === 0) return basePolicies[name]; | ||||
| 
 | ||||
| 			const options = roles.map(role => role.options[name] ?? { priority: 0, useDefault: true }); | ||||
| 			const policies = roles.map(role => role.policies[name] ?? { priority: 0, useDefault: true }); | ||||
| 
 | ||||
| 			const p2 = options.filter(option => option.priority === 2); | ||||
| 			if (p2.length > 0) return aggregate(p2.map(option => option.useDefault ? baseRoleOptions[name] : option.value)); | ||||
| 			const p2 = policies.filter(policy => policy.priority === 2); | ||||
| 			if (p2.length > 0) return aggregate(p2.map(policy => policy.useDefault ? basePolicies[name] : policy.value)); | ||||
| 
 | ||||
| 			const p1 = options.filter(option => option.priority === 1); | ||||
| 			if (p1.length > 0) return aggregate(p2.map(option => option.useDefault ? baseRoleOptions[name] : option.value)); | ||||
| 			const p1 = policies.filter(policy => policy.priority === 1); | ||||
| 			if (p1.length > 0) return aggregate(p2.map(policy => policy.useDefault ? basePolicies[name] : policy.value)); | ||||
| 
 | ||||
| 			return aggregate(options.map(option => option.useDefault ? baseRoleOptions[name] : option.value)); | ||||
| 			return aggregate(policies.map(policy => policy.useDefault ? basePolicies[name] : policy.value)); | ||||
| 		} | ||||
| 
 | ||||
| 		return { | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ export class UserListService { | |||
| 		const currentCount = await this.userListJoiningsRepository.countBy({ | ||||
| 			userListId: list.id, | ||||
| 		}); | ||||
| 		if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).userEachUserListsLimit) { | ||||
| 		if (currentCount > (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) { | ||||
| 			throw new Error('Too many users'); | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import type { Packed } from '@/misc/schema.js'; | |||
| import type { User } from '@/models/entities/User.js'; | ||||
| import type { Role } from '@/models/entities/Role.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { DEFAULT_ROLE } from '@/core/RoleService.js'; | ||||
| import { DEFAULT_POLICIES } from '@/core/RoleService.js'; | ||||
| import { UserEntityService } from './UserEntityService.js'; | ||||
| 
 | ||||
| @Injectable() | ||||
|  | @ -40,9 +40,9 @@ export class RoleEntityService { | |||
| 			roleId: role.id, | ||||
| 		}); | ||||
| 
 | ||||
| 		const roleOptions = { ...role.options }; | ||||
| 		for (const [k, v] of Object.entries(DEFAULT_ROLE)) { | ||||
| 			if (roleOptions[k] == null) roleOptions[k] = { | ||||
| 		const policies = { ...role.policies }; | ||||
| 		for (const [k, v] of Object.entries(DEFAULT_POLICIES)) { | ||||
| 			if (policies[k] == null) policies[k] = { | ||||
| 				useDefault: true, | ||||
| 				priority: 0, | ||||
| 				value: v, | ||||
|  | @ -62,7 +62,7 @@ export class RoleEntityService { | |||
| 			isAdministrator: role.isAdministrator, | ||||
| 			isModerator: role.isModerator, | ||||
| 			canEditMembersByModerator: role.canEditMembersByModerator, | ||||
| 			options: roleOptions, | ||||
| 			policies: policies, | ||||
| 			usersCount: assigns.length, | ||||
| 			...(opts.detail ? { | ||||
| 				users: this.userEntityService.packMany(assigns.map(x => x.userId), me), | ||||
|  |  | |||
|  | @ -423,7 +423,7 @@ export class UserEntityService implements OnModuleInit { | |||
| 				bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner, false) : null, | ||||
| 				bannerBlurhash: user.banner?.blurhash ?? null, | ||||
| 				isLocked: user.isLocked, | ||||
| 				isSilenced: this.roleService.getUserRoleOptions(user.id).then(r => !r.canPublicNote), | ||||
| 				isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote), | ||||
| 				isSuspended: user.isSuspended ?? falsy, | ||||
| 				description: profile!.description, | ||||
| 				location: profile!.location, | ||||
|  | @ -496,7 +496,7 @@ export class UserEntityService implements OnModuleInit { | |||
| 			} : {}), | ||||
| 
 | ||||
| 			...(opts.includeSecrets ? { | ||||
| 				role: this.roleService.getUserRoleOptions(user.id), | ||||
| 				policies: this.roleService.getUserPolicies(user.id), | ||||
| 				email: profile!.email, | ||||
| 				emailVerified: profile!.emailVerified, | ||||
| 				securityKeysList: profile!.twoFactorEnabled | ||||
|  |  | |||
|  | @ -458,5 +458,5 @@ export class Meta { | |||
| 	@Column('jsonb', { | ||||
| 		default: { }, | ||||
| 	}) | ||||
| 	public defaultRoleOverride: Record<string, any>; | ||||
| 	public policies: Record<string, any>; | ||||
| } | ||||
|  |  | |||
|  | @ -136,7 +136,7 @@ export class Role { | |||
| 	@Column('jsonb', { | ||||
| 		default: { }, | ||||
| 	}) | ||||
| 	public options: Record<string, { | ||||
| 	public policies: Record<string, { | ||||
| 		useDefault: boolean; | ||||
| 		priority: number; | ||||
| 		value: any; | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; | |||
| import { bindThis } from '@/decorators.js'; | ||||
| import NotesChart from '@/core/chart/charts/notes.js'; | ||||
| import UsersChart from '@/core/chart/charts/users.js'; | ||||
| import { DEFAULT_ROLE } from '@/core/RoleService.js'; | ||||
| import { DEFAULT_POLICIES } from '@/core/RoleService.js'; | ||||
| import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; | ||||
| 
 | ||||
| const nodeinfo2_1path = '/nodeinfo/2.1'; | ||||
|  | @ -74,7 +74,7 @@ export class NodeinfoServerService { | |||
| 
 | ||||
| 			const proxyAccount = meta.proxyAccountId ? await this.userEntityService.pack(meta.proxyAccountId).catch(() => null) : null; | ||||
| 
 | ||||
| 			const baseRoleOptions = { ...DEFAULT_ROLE, ...meta.defaultRoleOverride }; | ||||
| 			const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies }; | ||||
| 
 | ||||
| 			return { | ||||
| 				software: { | ||||
|  | @ -105,8 +105,8 @@ export class NodeinfoServerService { | |||
| 					repositoryUrl: meta.repositoryUrl, | ||||
| 					feedbackUrl: meta.feedbackUrl, | ||||
| 					disableRegistration: meta.disableRegistration, | ||||
| 					disableLocalTimeline: !baseRoleOptions.ltlAvailable, | ||||
| 					disableGlobalTimeline: !baseRoleOptions.gtlAvailable, | ||||
| 					disableLocalTimeline: !basePolicies.ltlAvailable, | ||||
| 					disableGlobalTimeline: !basePolicies.gtlAvailable, | ||||
| 					emailRequiredForSignup: meta.emailRequiredForSignup, | ||||
| 					enableHcaptcha: meta.enableHcaptcha, | ||||
| 					enableRecaptcha: meta.enableRecaptcha, | ||||
|  |  | |||
|  | @ -225,7 +225,7 @@ export class ApiCallService implements OnApplicationShutdown { | |||
| 			} | ||||
| 
 | ||||
| 			// TODO: 毎リクエスト計算するのもあれだしキャッシュしたい
 | ||||
| 			const factor = user ? (await this.roleService.getUserRoleOptions(user.id)).rateLimitFactor : 1; | ||||
| 			const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1; | ||||
| 
 | ||||
| 			// Rate limit
 | ||||
| 			await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor).catch(err => { | ||||
|  | @ -274,9 +274,9 @@ export class ApiCallService implements OnApplicationShutdown { | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if (ep.meta.requireRoleOption != null && !user!.isRoot) { | ||||
| 			const myRole = await this.roleService.getUserRoleOptions(user!.id); | ||||
| 			if (!myRole[ep.meta.requireRoleOption]) { | ||||
| 		if (ep.meta.requireRolePolicy != null && !user!.isRoot) { | ||||
| 			const policies = await this.roleService.getUserPolicies(user!.id); | ||||
| 			if (!policies[ep.meta.requireRolePolicy]) { | ||||
| 				throw new ApiError({ | ||||
| 					message: 'You are not assigned to a required role.', | ||||
| 					code: 'ROLE_PERMISSION_DENIED', | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ import * as ep___admin_roles_show from './endpoints/admin/roles/show.js'; | |||
| import * as ep___admin_roles_update from './endpoints/admin/roles/update.js'; | ||||
| import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js'; | ||||
| import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js'; | ||||
| import * as ep___admin_roles_updateDefaultRoleOverride from './endpoints/admin/roles/update-default-role-override.js'; | ||||
| import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js'; | ||||
| import * as ep___announcements from './endpoints/announcements.js'; | ||||
| import * as ep___antennas_create from './endpoints/antennas/create.js'; | ||||
| import * as ep___antennas_delete from './endpoints/antennas/delete.js'; | ||||
|  | @ -399,7 +399,7 @@ const $admin_roles_show: Provider = { provide: 'ep:admin/roles/show', useClass: | |||
| const $admin_roles_update: Provider = { provide: 'ep:admin/roles/update', useClass: ep___admin_roles_update.default }; | ||||
| const $admin_roles_assign: Provider = { provide: 'ep:admin/roles/assign', useClass: ep___admin_roles_assign.default }; | ||||
| const $admin_roles_unassign: Provider = { provide: 'ep:admin/roles/unassign', useClass: ep___admin_roles_unassign.default }; | ||||
| const $admin_roles_updateDefaultRoleOverride: Provider = { provide: 'ep:admin/roles/update-default-role-override', useClass: ep___admin_roles_updateDefaultRoleOverride.default }; | ||||
| const $admin_roles_updateDefaultPolicies: Provider = { provide: 'ep:admin/roles/update-default-policies', useClass: ep___admin_roles_updateDefaultPolicies.default }; | ||||
| const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default }; | ||||
| const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default }; | ||||
| const $antennas_delete: Provider = { provide: 'ep:antennas/delete', useClass: ep___antennas_delete.default }; | ||||
|  | @ -737,7 +737,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention | |||
| 		$admin_roles_update, | ||||
| 		$admin_roles_assign, | ||||
| 		$admin_roles_unassign, | ||||
| 		$admin_roles_updateDefaultRoleOverride, | ||||
| 		$admin_roles_updateDefaultPolicies, | ||||
| 		$announcements, | ||||
| 		$antennas_create, | ||||
| 		$antennas_delete, | ||||
|  | @ -1069,7 +1069,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention | |||
| 		$admin_roles_update, | ||||
| 		$admin_roles_assign, | ||||
| 		$admin_roles_unassign, | ||||
| 		$admin_roles_updateDefaultRoleOverride, | ||||
| 		$admin_roles_updateDefaultPolicies, | ||||
| 		$announcements, | ||||
| 		$antennas_create, | ||||
| 		$antennas_delete, | ||||
|  |  | |||
|  | @ -64,7 +64,7 @@ import * as ep___admin_roles_show from './endpoints/admin/roles/show.js'; | |||
| import * as ep___admin_roles_update from './endpoints/admin/roles/update.js'; | ||||
| import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js'; | ||||
| import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js'; | ||||
| import * as ep___admin_roles_updateDefaultRoleOverride from './endpoints/admin/roles/update-default-role-override.js'; | ||||
| import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js'; | ||||
| import * as ep___announcements from './endpoints/announcements.js'; | ||||
| import * as ep___antennas_create from './endpoints/antennas/create.js'; | ||||
| import * as ep___antennas_delete from './endpoints/antennas/delete.js'; | ||||
|  | @ -396,7 +396,7 @@ const eps = [ | |||
| 	['admin/roles/update', ep___admin_roles_update], | ||||
| 	['admin/roles/assign', ep___admin_roles_assign], | ||||
| 	['admin/roles/unassign', ep___admin_roles_unassign], | ||||
| 	['admin/roles/update-default-role-override', ep___admin_roles_updateDefaultRoleOverride], | ||||
| 	['admin/roles/update-default-policies', ep___admin_roles_updateDefaultPolicies], | ||||
| 	['announcements', ep___announcements], | ||||
| 	['antennas/create', ep___antennas_create], | ||||
| 	['antennas/delete', ep___antennas_delete], | ||||
|  | @ -695,7 +695,7 @@ export interface IEndpointMeta { | |||
| 	 */ | ||||
| 	readonly requireAdmin?: boolean; | ||||
| 
 | ||||
| 	readonly requireRoleOption?: string; | ||||
| 	readonly requireRolePolicy?: string; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * エンドポイントのリミテーションに関するやつ | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| } as const; | ||||
| 
 | ||||
| export const paramDef = { | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| 
 | ||||
| 	errors: { | ||||
| 		noSuchFile: { | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| 
 | ||||
| 	errors: { | ||||
| 		noSuchEmoji: { | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| } as const; | ||||
| 
 | ||||
| export const paramDef = { | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| 
 | ||||
| 	errors: { | ||||
| 		noSuchEmoji: { | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import { QueueService } from '@/core/QueueService.js'; | |||
| export const meta = { | ||||
| 	secure: true, | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| } as const; | ||||
| 
 | ||||
| export const paramDef = { | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| 
 | ||||
| 	res: { | ||||
| 		type: 'array', | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| 
 | ||||
| 	res: { | ||||
| 		type: 'array', | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| } as const; | ||||
| 
 | ||||
| export const paramDef = { | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| } as const; | ||||
| 
 | ||||
| export const paramDef = { | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| } as const; | ||||
| 
 | ||||
| export const paramDef = { | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ export const meta = { | |||
| 	tags: ['admin'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canManageCustomEmojis', | ||||
| 	requireRolePolicy: 'canManageCustomEmojis', | ||||
| 
 | ||||
| 	errors: { | ||||
| 		noSuchEmoji: { | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; | |||
| import { MetaService } from '@/core/MetaService.js'; | ||||
| import type { Config } from '@/config.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { DEFAULT_ROLE } from '@/core/RoleService.js'; | ||||
| import { DEFAULT_POLICIES } from '@/core/RoleService.js'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	tags: ['meta'], | ||||
|  | @ -440,7 +440,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				deeplIsPro: instance.deeplIsPro, | ||||
| 				enableIpLogging: instance.enableIpLogging, | ||||
| 				enableActiveEmailValidation: instance.enableActiveEmailValidation, | ||||
| 				baseRole: { ...DEFAULT_ROLE, ...instance.defaultRoleOverride }, | ||||
| 				policies: { ...DEFAULT_POLICIES, ...instance.policies }, | ||||
| 			}; | ||||
| 		}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ export const paramDef = { | |||
| 		isModerator: { type: 'boolean' }, | ||||
| 		isAdministrator: { type: 'boolean' }, | ||||
| 		canEditMembersByModerator: { type: 'boolean' }, | ||||
| 		options: { | ||||
| 		policies: { | ||||
| 			type: 'object', | ||||
| 		}, | ||||
| 	}, | ||||
|  | @ -39,7 +39,7 @@ export const paramDef = { | |||
| 		'isModerator', | ||||
| 		'isAdministrator', | ||||
| 		'canEditMembersByModerator', | ||||
| 		'options', | ||||
| 		'policies', | ||||
| 	], | ||||
| } as const; | ||||
| 
 | ||||
|  | @ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				isAdministrator: ps.isAdministrator, | ||||
| 				isModerator: ps.isModerator, | ||||
| 				canEditMembersByModerator: ps.canEditMembersByModerator, | ||||
| 				options: ps.options, | ||||
| 				policies: ps.policies, | ||||
| 			}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0])); | ||||
| 	 | ||||
| 			this.globalEventService.publishInternalEvent('roleCreated', created); | ||||
|  |  | |||
|  | @ -16,12 +16,12 @@ export const meta = { | |||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		options: { | ||||
| 		policies: { | ||||
| 			type: 'object', | ||||
| 		}, | ||||
| 	}, | ||||
| 	required: [ | ||||
| 		'options', | ||||
| 		'policies', | ||||
| 	], | ||||
| } as const; | ||||
| 
 | ||||
|  | @ -34,9 +34,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 	) { | ||||
| 		super(meta, paramDef, async (ps) => { | ||||
| 			await this.metaService.update({ | ||||
| 				defaultRoleOverride: ps.options, | ||||
| 				policies: ps.policies, | ||||
| 			}); | ||||
| 			this.globalEventService.publishInternalEvent('defaultRoleOverrideUpdated', ps.options); | ||||
| 			this.globalEventService.publishInternalEvent('policiesUpdated', ps.policies); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | @ -33,7 +33,7 @@ export const paramDef = { | |||
| 		isModerator: { type: 'boolean' }, | ||||
| 		isAdministrator: { type: 'boolean' }, | ||||
| 		canEditMembersByModerator: { type: 'boolean' }, | ||||
| 		options: { | ||||
| 		policies: { | ||||
| 			type: 'object', | ||||
| 		}, | ||||
| 	}, | ||||
|  | @ -48,7 +48,7 @@ export const paramDef = { | |||
| 		'isModerator', | ||||
| 		'isAdministrator', | ||||
| 		'canEditMembersByModerator', | ||||
| 		'options', | ||||
| 		'policies', | ||||
| 	], | ||||
| } as const; | ||||
| 
 | ||||
|  | @ -79,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				isModerator: ps.isModerator, | ||||
| 				isAdministrator: ps.isAdministrator, | ||||
| 				canEditMembersByModerator: ps.canEditMembersByModerator, | ||||
| 				options: ps.options, | ||||
| 				policies: ps.policies, | ||||
| 			}); | ||||
| 			const updated = await this.rolesRepository.findOneByOrFail({ id: ps.roleId }); | ||||
| 			this.globalEventService.publishInternalEvent('roleUpdated', updated); | ||||
|  |  | |||
|  | @ -52,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 			} | ||||
| 
 | ||||
| 			const isModerator = await this.roleService.isModerator(user); | ||||
| 			const isSilenced = !(await this.roleService.getUserRoleOptions(user.id)).canPublicNote; | ||||
| 			const isSilenced = !(await this.roleService.getUserPolicies(user.id)).canPublicNote; | ||||
| 
 | ||||
| 			const _me = await this.usersRepository.findOneByOrFail({ id: me.id }); | ||||
| 			if (!await this.roleService.isAdministrator(_me) && await this.roleService.isAdministrator(user)) { | ||||
|  | @ -94,6 +94,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				lastActiveDate: user.lastActiveDate, | ||||
| 				moderationNote: profile.moderationNote, | ||||
| 				signins, | ||||
| 				policies: await this.roleService.getUserPolicies(user.id), | ||||
| 				roles: await this.roleEntityService.packMany(roles, me, { detail: false }), | ||||
| 			}; | ||||
| 		}); | ||||
|  |  | |||
|  | @ -92,7 +92,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 			const currentAntennasCount = await this.antennasRepository.countBy({ | ||||
| 				userId: me.id, | ||||
| 			}); | ||||
| 			if (currentAntennasCount > (await this.roleService.getUserRoleOptions(me.id)).antennaLimit) { | ||||
| 			if (currentAntennasCount > (await this.roleService.getUserPolicies(me.id)).antennaLimit) { | ||||
| 				throw new ApiError(meta.errors.tooManyAntennas); | ||||
| 			} | ||||
| 
 | ||||
|  |  | |||
|  | @ -97,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 			const currentCount = await this.clipNotesRepository.countBy({ | ||||
| 				clipId: clip.id, | ||||
| 			}); | ||||
| 			if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).noteEachClipsLimit) { | ||||
| 			if (currentCount > (await this.roleService.getUserPolicies(me.id)).noteEachClipsLimit) { | ||||
| 				throw new ApiError(meta.errors.tooManyClipNotes); | ||||
| 			} | ||||
| 
 | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 			const currentCount = await this.clipsRepository.countBy({ | ||||
| 				userId: me.id, | ||||
| 			}); | ||||
| 			if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).clipLimit) { | ||||
| 			if (currentCount > (await this.roleService.getUserPolicies(me.id)).clipLimit) { | ||||
| 				throw new ApiError(meta.errors.tooManyClips); | ||||
| 			} | ||||
| 	 | ||||
|  |  | |||
|  | @ -47,10 +47,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 			// Calculate drive usage
 | ||||
| 			const usage = await this.driveFileEntityService.calcDriveUsageOf(me.id); | ||||
| 
 | ||||
| 			const myRole = await this.roleService.getUserRoleOptions(me.id); | ||||
| 			const policies = await this.roleService.getUserPolicies(me.id); | ||||
| 
 | ||||
| 			return { | ||||
| 				capacity: 1024 * 1024 * myRole.driveCapacityMb, | ||||
| 				capacity: 1024 * 1024 * policies.driveCapacityMb, | ||||
| 				usage: usage, | ||||
| 			}; | ||||
| 		}); | ||||
|  |  | |||
|  | @ -173,7 +173,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 			if (ps.mutedWords !== undefined) { | ||||
| 				// TODO: ちゃんと数える
 | ||||
| 				const length = JSON.stringify(ps.mutedWords).length; | ||||
| 				if (length > (await this.roleService.getUserRoleOptions(user.id)).wordMuteLimit) { | ||||
| 				if (length > (await this.roleService.getUserPolicies(user.id)).wordMuteLimit) { | ||||
| 					throw new ApiError(meta.errors.tooManyMutedWords); | ||||
| 				} | ||||
| 
 | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 			const currentWebhooksCount = await this.webhooksRepository.countBy({ | ||||
| 				userId: me.id, | ||||
| 			}); | ||||
| 			if (currentWebhooksCount > (await this.roleService.getUserRoleOptions(me.id)).webhookLimit) { | ||||
| 			if (currentWebhooksCount > (await this.roleService.getUserPolicies(me.id)).webhookLimit) { | ||||
| 				throw new ApiError(meta.errors.tooManyWebhooks); | ||||
| 			} | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ export const meta = { | |||
| 	tags: ['meta'], | ||||
| 
 | ||||
| 	requireCredential: true, | ||||
| 	requireRoleOption: 'canInvite', | ||||
| 	requireRolePolicy: 'canInvite', | ||||
| 
 | ||||
| 	res: { | ||||
| 		type: 'object', | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; | |||
| import { MetaService } from '@/core/MetaService.js'; | ||||
| import type { Config } from '@/config.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { DEFAULT_ROLE } from '@/core/RoleService.js'; | ||||
| import { DEFAULT_POLICIES } from '@/core/RoleService.js'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	tags: ['meta'], | ||||
|  | @ -334,7 +334,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 
 | ||||
| 				translatorAvailable: instance.deeplAuthKey != null, | ||||
| 
 | ||||
| 				baseRole: { ...DEFAULT_ROLE, ...instance.defaultRoleOverride }, | ||||
| 				policies: { ...DEFAULT_POLICIES, ...instance.policies }, | ||||
| 
 | ||||
| 				...(ps.detail ? { | ||||
| 					pinnedPages: instance.pinnedPages, | ||||
|  |  | |||
|  | @ -62,8 +62,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 		private activeUsersChart: ActiveUsersChart, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const role = await this.roleService.getUserRoleOptions(me ? me.id : null); | ||||
| 			if (!role.gtlAvailable) { | ||||
| 			const policies = await this.roleService.getUserPolicies(me ? me.id : null); | ||||
| 			if (!policies.gtlAvailable) { | ||||
| 				throw new ApiError(meta.errors.gtlDisabled); | ||||
| 			} | ||||
| 
 | ||||
|  |  | |||
|  | @ -71,8 +71,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 		private activeUsersChart: ActiveUsersChart, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const role = await this.roleService.getUserRoleOptions(me.id); | ||||
| 			if (!role.ltlAvailable) { | ||||
| 			const policies = await this.roleService.getUserPolicies(me.id); | ||||
| 			if (!policies.ltlAvailable) { | ||||
| 				throw new ApiError(meta.errors.stlDisabled); | ||||
| 			} | ||||
| 
 | ||||
|  |  | |||
|  | @ -67,8 +67,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 		private activeUsersChart: ActiveUsersChart, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const role = await this.roleService.getUserRoleOptions(me ? me.id : null); | ||||
| 			if (!role.ltlAvailable) { | ||||
| 			const policies = await this.roleService.getUserPolicies(me ? me.id : null); | ||||
| 			if (!policies.ltlAvailable) { | ||||
| 				throw new ApiError(meta.errors.ltlDisabled); | ||||
| 			} | ||||
| 
 | ||||
|  |  | |||
|  | @ -55,7 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 			const currentCount = await this.userListsRepository.countBy({ | ||||
| 				userId: me.id, | ||||
| 			}); | ||||
| 			if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).userListLimit) { | ||||
| 			if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) { | ||||
| 				throw new ApiError(meta.errors.tooManyUserLists); | ||||
| 			} | ||||
| 	 | ||||
|  |  | |||
|  | @ -29,8 +29,8 @@ class GlobalTimelineChannel extends Channel { | |||
| 
 | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 		const role = await this.roleService.getUserRoleOptions(this.user ? this.user.id : null); | ||||
| 		if (!role.gtlAvailable) return; | ||||
| 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); | ||||
| 		if (!policies.gtlAvailable) return; | ||||
| 
 | ||||
| 		// Subscribe events
 | ||||
| 		this.subscriber.on('notesStream', this.onNote); | ||||
|  |  | |||
|  | @ -30,8 +30,8 @@ class HybridTimelineChannel extends Channel { | |||
| 
 | ||||
| 	@bindThis | ||||
| 	public async init(params: any): Promise<void> { | ||||
| 		const role = await this.roleService.getUserRoleOptions(this.user ? this.user.id : null); | ||||
| 		if (!role.ltlAvailable) return; | ||||
| 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); | ||||
| 		if (!policies.ltlAvailable) return; | ||||
| 
 | ||||
| 		// Subscribe events
 | ||||
| 		this.subscriber.on('notesStream', this.onNote); | ||||
|  |  | |||
|  | @ -28,8 +28,8 @@ class LocalTimelineChannel extends Channel { | |||
| 
 | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 		const role = await this.roleService.getUserRoleOptions(this.user ? this.user.id : null); | ||||
| 		if (!role.ltlAvailable) return; | ||||
| 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); | ||||
| 		if (!policies.ltlAvailable) return; | ||||
| 
 | ||||
| 		// Subscribe events
 | ||||
| 		this.subscriber.on('notesStream', this.onNote); | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ export interface InternalStreamTypes { | |||
| 	remoteUserUpdated: Serialized<{ id: User['id']; }>; | ||||
| 	follow: Serialized<{ followerId: User['id']; followeeId: User['id']; }>; | ||||
| 	unfollow: Serialized<{ followerId: User['id']; followeeId: User['id']; }>; | ||||
| 	defaultRoleOverrideUpdated: Serialized<Role['options']>; | ||||
| 	policiesUpdated: Serialized<Role['options']>; | ||||
| 	roleCreated: Serialized<Role>; | ||||
| 	roleDeleted: Serialized<Role>; | ||||
| 	roleUpdated: Serialized<Role>; | ||||
|  |  | |||
|  | @ -36,20 +36,20 @@ | |||
| 	</MkFolder> | ||||
| 
 | ||||
| 	<FormSlot> | ||||
| 		<template #label>{{ i18n.ts._role.options }}</template> | ||||
| 		<template #label>{{ i18n.ts._role.policies }}</template> | ||||
| 		<div class="_gaps_s"> | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.rateLimitFactor }}</template> | ||||
| 				<template #suffix>{{ options.rateLimitFactor.useDefault ? i18n.ts._role.useBaseValue : `${Math.floor(options.rateLimitFactor.value * 100)}%` }} <i :class="getPriorityIcon(options.rateLimitFactor)"></i></template> | ||||
| 				<template #suffix>{{ policies.rateLimitFactor.useDefault ? i18n.ts._role.useBaseValue : `${Math.floor(policies.rateLimitFactor.value * 100)}%` }} <i :class="getPriorityIcon(policies.rateLimitFactor)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.rateLimitFactor.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.rateLimitFactor.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkRange :model-value="options.rateLimitFactor.value * 100" :min="30" :max="300" :step="10" :text-converter="(v) => `${v}%`" @update:model-value="v => options.rateLimitFactor.value = (v / 100)"> | ||||
| 					<MkRange :model-value="policies.rateLimitFactor.value * 100" :min="30" :max="300" :step="10" :text-converter="(v) => `${v}%`" @update:model-value="v => policies.rateLimitFactor.value = (v / 100)"> | ||||
| 						<template #label>{{ i18n.ts._role._options.rateLimitFactor }}</template> | ||||
| 						<template #caption>{{ i18n.ts._role._options.descriptionOfRateLimitFactor }}</template> | ||||
| 					</MkRange> | ||||
| 					<MkRange v-model="options.rateLimitFactor.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.rateLimitFactor.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -57,15 +57,15 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.gtlAvailable }}</template> | ||||
| 				<template #suffix>{{ options.gtlAvailable.useDefault ? i18n.ts._role.useBaseValue : (options.gtlAvailable.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(options.gtlAvailable)"></i></template> | ||||
| 				<template #suffix>{{ policies.gtlAvailable.useDefault ? i18n.ts._role.useBaseValue : (policies.gtlAvailable.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(policies.gtlAvailable)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.gtlAvailable.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.gtlAvailable.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkSwitch v-model="options.gtlAvailable.value" :disabled="options.gtlAvailable.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.gtlAvailable.value" :disabled="policies.gtlAvailable.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts.enable }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkRange v-model="options.gtlAvailable.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.gtlAvailable.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -73,15 +73,15 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.ltlAvailable }}</template> | ||||
| 				<template #suffix>{{ options.ltlAvailable.useDefault ? i18n.ts._role.useBaseValue : (options.ltlAvailable.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(options.ltlAvailable)"></i></template> | ||||
| 				<template #suffix>{{ policies.ltlAvailable.useDefault ? i18n.ts._role.useBaseValue : (policies.ltlAvailable.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(policies.ltlAvailable)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.ltlAvailable.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.ltlAvailable.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkSwitch v-model="options.ltlAvailable.value" :disabled="options.ltlAvailable.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.ltlAvailable.value" :disabled="policies.ltlAvailable.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts.enable }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkRange v-model="options.ltlAvailable.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.ltlAvailable.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -89,15 +89,15 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.canPublicNote }}</template> | ||||
| 				<template #suffix>{{ options.canPublicNote.useDefault ? i18n.ts._role.useBaseValue : (options.canPublicNote.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(options.canPublicNote)"></i></template> | ||||
| 				<template #suffix>{{ policies.canPublicNote.useDefault ? i18n.ts._role.useBaseValue : (policies.canPublicNote.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(policies.canPublicNote)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.canPublicNote.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.canPublicNote.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkSwitch v-model="options.canPublicNote.value" :disabled="options.canPublicNote.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.canPublicNote.value" :disabled="policies.canPublicNote.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts.enable }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkRange v-model="options.canPublicNote.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.canPublicNote.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -105,15 +105,15 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.canInvite }}</template> | ||||
| 				<template #suffix>{{ options.canInvite.useDefault ? i18n.ts._role.useBaseValue : (options.canInvite.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(options.canInvite)"></i></template> | ||||
| 				<template #suffix>{{ policies.canInvite.useDefault ? i18n.ts._role.useBaseValue : (policies.canInvite.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(policies.canInvite)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.canInvite.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.canInvite.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkSwitch v-model="options.canInvite.value" :disabled="options.canInvite.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.canInvite.value" :disabled="policies.canInvite.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts.enable }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkRange v-model="options.canInvite.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.canInvite.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -121,15 +121,15 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.canManageCustomEmojis }}</template> | ||||
| 				<template #suffix>{{ options.canManageCustomEmojis.useDefault ? i18n.ts._role.useBaseValue : (options.canManageCustomEmojis.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(options.canManageCustomEmojis)"></i></template> | ||||
| 				<template #suffix>{{ policies.canManageCustomEmojis.useDefault ? i18n.ts._role.useBaseValue : (policies.canManageCustomEmojis.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(policies.canManageCustomEmojis)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.canManageCustomEmojis.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.canManageCustomEmojis.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkSwitch v-model="options.canManageCustomEmojis.value" :disabled="options.canManageCustomEmojis.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.canManageCustomEmojis.value" :disabled="policies.canManageCustomEmojis.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts.enable }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkRange v-model="options.canManageCustomEmojis.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.canManageCustomEmojis.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -137,15 +137,15 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.driveCapacity }}</template> | ||||
| 				<template #suffix>{{ options.driveCapacityMb.useDefault ? i18n.ts._role.useBaseValue : (options.driveCapacityMb.value + 'MB') }} <i :class="getPriorityIcon(options.driveCapacityMb)"></i></template> | ||||
| 				<template #suffix>{{ policies.driveCapacityMb.useDefault ? i18n.ts._role.useBaseValue : (policies.driveCapacityMb.value + 'MB') }} <i :class="getPriorityIcon(policies.driveCapacityMb)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.driveCapacityMb.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.driveCapacityMb.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkInput v-model="options.driveCapacityMb.value" :disabled="options.driveCapacityMb.useDefault" type="number" :readonly="readonly"> | ||||
| 					<MkInput v-model="policies.driveCapacityMb.value" :disabled="policies.driveCapacityMb.useDefault" type="number" :readonly="readonly"> | ||||
| 						<template #suffix>MB</template> | ||||
| 					</MkInput> | ||||
| 					<MkRange v-model="options.driveCapacityMb.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.driveCapacityMb.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -153,14 +153,14 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.pinMax }}</template> | ||||
| 				<template #suffix>{{ options.pinLimit.useDefault ? i18n.ts._role.useBaseValue : (options.pinLimit.value) }} <i :class="getPriorityIcon(options.pinLimit)"></i></template> | ||||
| 				<template #suffix>{{ policies.pinLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.pinLimit.value) }} <i :class="getPriorityIcon(policies.pinLimit)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.pinLimit.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.pinLimit.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkInput v-model="options.pinLimit.value" :disabled="options.pinLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					<MkInput v-model="policies.pinLimit.value" :disabled="policies.pinLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					</MkInput> | ||||
| 					<MkRange v-model="options.pinLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.pinLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -168,14 +168,14 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.antennaMax }}</template> | ||||
| 				<template #suffix>{{ options.antennaLimit.useDefault ? i18n.ts._role.useBaseValue : (options.antennaLimit.value) }} <i :class="getPriorityIcon(options.antennaLimit)"></i></template> | ||||
| 				<template #suffix>{{ policies.antennaLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.antennaLimit.value) }} <i :class="getPriorityIcon(policies.antennaLimit)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.antennaLimit.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.antennaLimit.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkInput v-model="options.antennaLimit.value" :disabled="options.antennaLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					<MkInput v-model="policies.antennaLimit.value" :disabled="policies.antennaLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					</MkInput> | ||||
| 					<MkRange v-model="options.antennaLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.antennaLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -183,15 +183,15 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.wordMuteMax }}</template> | ||||
| 				<template #suffix>{{ options.wordMuteLimit.useDefault ? i18n.ts._role.useBaseValue : (options.wordMuteLimit.value) }} <i :class="getPriorityIcon(options.wordMuteLimit)"></i></template> | ||||
| 				<template #suffix>{{ policies.wordMuteLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.wordMuteLimit.value) }} <i :class="getPriorityIcon(policies.wordMuteLimit)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.wordMuteLimit.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.wordMuteLimit.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkInput v-model="options.wordMuteLimit.value" :disabled="options.wordMuteLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					<MkInput v-model="policies.wordMuteLimit.value" :disabled="policies.wordMuteLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 						<template #suffix>chars</template> | ||||
| 					</MkInput> | ||||
| 					<MkRange v-model="options.wordMuteLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.wordMuteLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -199,14 +199,14 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.webhookMax }}</template> | ||||
| 				<template #suffix>{{ options.webhookLimit.useDefault ? i18n.ts._role.useBaseValue : (options.webhookLimit.value) }} <i :class="getPriorityIcon(options.webhookLimit)"></i></template> | ||||
| 				<template #suffix>{{ policies.webhookLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.webhookLimit.value) }} <i :class="getPriorityIcon(policies.webhookLimit)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.webhookLimit.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.webhookLimit.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkInput v-model="options.webhookLimit.value" :disabled="options.webhookLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					<MkInput v-model="policies.webhookLimit.value" :disabled="policies.webhookLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					</MkInput> | ||||
| 					<MkRange v-model="options.webhookLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.webhookLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -214,14 +214,14 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.clipMax }}</template> | ||||
| 				<template #suffix>{{ options.clipLimit.useDefault ? i18n.ts._role.useBaseValue : (options.clipLimit.value) }} <i :class="getPriorityIcon(options.clipLimit)"></i></template> | ||||
| 				<template #suffix>{{ policies.clipLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.clipLimit.value) }} <i :class="getPriorityIcon(policies.clipLimit)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.clipLimit.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.clipLimit.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkInput v-model="options.clipLimit.value" :disabled="options.clipLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					<MkInput v-model="policies.clipLimit.value" :disabled="policies.clipLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					</MkInput> | ||||
| 					<MkRange v-model="options.clipLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.clipLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -229,14 +229,14 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.noteEachClipsMax }}</template> | ||||
| 				<template #suffix>{{ options.noteEachClipsLimit.useDefault ? i18n.ts._role.useBaseValue : (options.noteEachClipsLimit.value) }} <i :class="getPriorityIcon(options.noteEachClipsLimit)"></i></template> | ||||
| 				<template #suffix>{{ policies.noteEachClipsLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.noteEachClipsLimit.value) }} <i :class="getPriorityIcon(policies.noteEachClipsLimit)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.noteEachClipsLimit.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.noteEachClipsLimit.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkInput v-model="options.noteEachClipsLimit.value" :disabled="options.noteEachClipsLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					<MkInput v-model="policies.noteEachClipsLimit.value" :disabled="policies.noteEachClipsLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					</MkInput> | ||||
| 					<MkRange v-model="options.noteEachClipsLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.noteEachClipsLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -244,14 +244,14 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.userListMax }}</template> | ||||
| 				<template #suffix>{{ options.userListLimit.useDefault ? i18n.ts._role.useBaseValue : (options.userListLimit.value) }} <i :class="getPriorityIcon(options.userListLimit)"></i></template> | ||||
| 				<template #suffix>{{ policies.userListLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.userListLimit.value) }} <i :class="getPriorityIcon(policies.userListLimit)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.userListLimit.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.userListLimit.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkInput v-model="options.userListLimit.value" :disabled="options.userListLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					<MkInput v-model="policies.userListLimit.value" :disabled="policies.userListLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					</MkInput> | ||||
| 					<MkRange v-model="options.userListLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.userListLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -259,14 +259,14 @@ | |||
| 
 | ||||
| 			<MkFolder> | ||||
| 				<template #label>{{ i18n.ts._role._options.userEachUserListsMax }}</template> | ||||
| 				<template #suffix>{{ options.userEachUserListsLimit.useDefault ? i18n.ts._role.useBaseValue : (options.userEachUserListsLimit.value) }} <i :class="getPriorityIcon(options.userEachUserListsLimit)"></i></template> | ||||
| 				<template #suffix>{{ policies.userEachUserListsLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.userEachUserListsLimit.value) }} <i :class="getPriorityIcon(policies.userEachUserListsLimit)"></i></template> | ||||
| 				<div class="_gaps"> | ||||
| 					<MkSwitch v-model="options.userEachUserListsLimit.useDefault" :readonly="readonly"> | ||||
| 					<MkSwitch v-model="policies.userEachUserListsLimit.useDefault" :readonly="readonly"> | ||||
| 						<template #label>{{ i18n.ts._role.useBaseValue }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<MkInput v-model="options.userEachUserListsLimit.value" :disabled="options.userEachUserListsLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					<MkInput v-model="policies.userEachUserListsLimit.value" :disabled="policies.userEachUserListsLimit.useDefault" type="number" :readonly="readonly"> | ||||
| 					</MkInput> | ||||
| 					<MkRange v-model="options.userEachUserListsLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 					<MkRange v-model="policies.userEachUserListsLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> | ||||
| 						<template #label>{{ i18n.ts._role.priority }}</template> | ||||
| 					</MkRange> | ||||
| 				</div> | ||||
|  | @ -306,7 +306,7 @@ import * as os from '@/os'; | |||
| import { i18n } from '@/i18n'; | ||||
| import { instance } from '@/instance'; | ||||
| 
 | ||||
| const ROLE_OPTIONS = [ | ||||
| const ROLE_POLICIES = [ | ||||
| 	'gtlAvailable', | ||||
| 	'ltlAvailable', | ||||
| 	'canPublicNote', | ||||
|  | @ -345,13 +345,13 @@ let condFormula = $ref(role?.condFormula ?? { id: uuid(), type: 'isRemote' }); | |||
| let isPublic = $ref(role?.isPublic ?? false); | ||||
| let canEditMembersByModerator = $ref(role?.canEditMembersByModerator ?? false); | ||||
| 
 | ||||
| const options = reactive<Record<typeof ROLE_OPTIONS[number], { useDefault: boolean; priority: number; value: any; }>>({}); | ||||
| for (const ROLE_OPTION of ROLE_OPTIONS) { | ||||
| 	const _options = role?.options ?? {}; | ||||
| 	options[ROLE_OPTION] = { | ||||
| 		useDefault: _options[ROLE_OPTION]?.useDefault ?? true, | ||||
| 		priority: _options[ROLE_OPTION]?.priority ?? 0, | ||||
| 		value: _options[ROLE_OPTION]?.value ?? instance.baseRole[ROLE_OPTION], | ||||
| const policies = reactive<Record<typeof ROLE_POLICIES[number], { useDefault: boolean; priority: number; value: any; }>>({}); | ||||
| for (const ROLE_POLICY of ROLE_POLICIES) { | ||||
| 	const _policies = role?.policies ?? {}; | ||||
| 	policies[ROLE_POLICY] = { | ||||
| 		useDefault: _policies[ROLE_POLICY]?.useDefault ?? true, | ||||
| 		priority: _policies[ROLE_POLICY]?.priority ?? 0, | ||||
| 		value: _policies[ROLE_POLICY]?.value ?? instance.policies[ROLE_POLICY], | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
|  | @ -381,7 +381,7 @@ async function save() { | |||
| 			isModerator: rolePermission === 'moderator', | ||||
| 			isPublic, | ||||
| 			canEditMembersByModerator, | ||||
| 			options, | ||||
| 			policies, | ||||
| 		}); | ||||
| 		emit('updated'); | ||||
| 	} else { | ||||
|  | @ -395,7 +395,7 @@ async function save() { | |||
| 			isModerator: rolePermission === 'moderator', | ||||
| 			isPublic, | ||||
| 			canEditMembersByModerator, | ||||
| 			options, | ||||
| 			policies, | ||||
| 		}); | ||||
| 		emit('created', created); | ||||
| 	} | ||||
|  |  | |||
|  | @ -10,114 +10,114 @@ | |||
| 					<div class="_gaps"> | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.rateLimitFactor }}</template> | ||||
| 							<template #suffix>{{ Math.floor(options_rateLimitFactor * 100) }}%</template> | ||||
| 							<MkRange :model-value="options_rateLimitFactor * 100" :min="30" :max="300" :step="10" :text-converter="(v) => `${v}%`" @update:model-value="v => options_rateLimitFactor = (v / 100)"> | ||||
| 							<template #suffix>{{ Math.floor(policies.rateLimitFactor * 100) }}%</template> | ||||
| 							<MkRange :model-value="policies.rateLimitFactor * 100" :min="30" :max="300" :step="10" :text-converter="(v) => `${v}%`" @update:model-value="v => policies.rateLimitFactor = (v / 100)"> | ||||
| 								<template #caption>{{ i18n.ts._role._options.descriptionOfRateLimitFactor }}</template> | ||||
| 							</MkRange> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.gtlAvailable }}</template> | ||||
| 							<template #suffix>{{ options_gtlAvailable ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="options_gtlAvailable"> | ||||
| 							<template #suffix>{{ policies.gtlAvailable ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="policies.gtlAvailable"> | ||||
| 								<template #label>{{ i18n.ts.enable }}</template> | ||||
| 							</MkSwitch> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.ltlAvailable }}</template> | ||||
| 							<template #suffix>{{ options_ltlAvailable ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="options_ltlAvailable"> | ||||
| 							<template #suffix>{{ policies.ltlAvailable ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="policies.ltlAvailable"> | ||||
| 								<template #label>{{ i18n.ts.enable }}</template> | ||||
| 							</MkSwitch> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.canPublicNote }}</template> | ||||
| 							<template #suffix>{{ options_canPublicNote ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="options_canPublicNote"> | ||||
| 							<template #suffix>{{ policies.canPublicNote ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="policies.canPublicNote"> | ||||
| 								<template #label>{{ i18n.ts.enable }}</template> | ||||
| 							</MkSwitch> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.canInvite }}</template> | ||||
| 							<template #suffix>{{ options_canInvite ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="options_canInvite"> | ||||
| 							<template #suffix>{{ policies.canInvite ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="policies.canInvite"> | ||||
| 								<template #label>{{ i18n.ts.enable }}</template> | ||||
| 							</MkSwitch> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.canManageCustomEmojis }}</template> | ||||
| 							<template #suffix>{{ options_canManageCustomEmojis ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="options_canManageCustomEmojis"> | ||||
| 							<template #suffix>{{ policies.canManageCustomEmojis ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="policies.canManageCustomEmojis"> | ||||
| 								<template #label>{{ i18n.ts.enable }}</template> | ||||
| 							</MkSwitch> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.driveCapacity }}</template> | ||||
| 							<template #suffix>{{ options_driveCapacityMb }}MB</template> | ||||
| 							<MkInput v-model="options_driveCapacityMb" type="number"> | ||||
| 							<template #suffix>{{ policies.driveCapacityMb }}MB</template> | ||||
| 							<MkInput v-model="policies.driveCapacityMb" type="number"> | ||||
| 								<template #suffix>MB</template> | ||||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.pinMax }}</template> | ||||
| 							<template #suffix>{{ options_pinLimit }}</template> | ||||
| 							<MkInput v-model="options_pinLimit" type="number"> | ||||
| 							<template #suffix>{{ policies.pinLimit }}</template> | ||||
| 							<MkInput v-model="policies.pinLimit" type="number"> | ||||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.antennaMax }}</template> | ||||
| 							<template #suffix>{{ options_antennaLimit }}</template> | ||||
| 							<MkInput v-model="options_antennaLimit" type="number"> | ||||
| 							<template #suffix>{{ policies.antennaLimit }}</template> | ||||
| 							<MkInput v-model="policies.antennaLimit" type="number"> | ||||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.wordMuteMax }}</template> | ||||
| 							<template #suffix>{{ options_wordMuteLimit }}</template> | ||||
| 							<MkInput v-model="options_wordMuteLimit" type="number"> | ||||
| 							<template #suffix>{{ policies.wordMuteLimit }}</template> | ||||
| 							<MkInput v-model="policies.wordMuteLimit" type="number"> | ||||
| 								<template #suffix>chars</template> | ||||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.webhookMax }}</template> | ||||
| 							<template #suffix>{{ options_webhookLimit }}</template> | ||||
| 							<MkInput v-model="options_webhookLimit" type="number"> | ||||
| 							<template #suffix>{{ policies.webhookLimit }}</template> | ||||
| 							<MkInput v-model="policies.webhookLimit" type="number"> | ||||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.clipMax }}</template> | ||||
| 							<template #suffix>{{ options_clipLimit }}</template> | ||||
| 							<MkInput v-model="options_clipLimit" type="number"> | ||||
| 							<template #suffix>{{ policies.clipLimit }}</template> | ||||
| 							<MkInput v-model="policies.clipLimit" type="number"> | ||||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.noteEachClipsMax }}</template> | ||||
| 							<template #suffix>{{ options_noteEachClipsLimit }}</template> | ||||
| 							<MkInput v-model="options_noteEachClipsLimit" type="number"> | ||||
| 							<template #suffix>{{ policies.noteEachClipsLimit }}</template> | ||||
| 							<MkInput v-model="policies.noteEachClipsLimit" type="number"> | ||||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.userListMax }}</template> | ||||
| 							<template #suffix>{{ options_userListLimit }}</template> | ||||
| 							<MkInput v-model="options_userListLimit" type="number"> | ||||
| 							<template #suffix>{{ policies.userListLimit }}</template> | ||||
| 							<MkInput v-model="policies.userListLimit" type="number"> | ||||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder> | ||||
| 							<template #label>{{ i18n.ts._role._options.userEachUserListsMax }}</template> | ||||
| 							<template #suffix>{{ options_userEachUserListsLimit }}</template> | ||||
| 							<MkInput v-model="options_userEachUserListsLimit" type="number"> | ||||
| 							<template #suffix>{{ policies.userEachUserListsLimit }}</template> | ||||
| 							<MkInput v-model="policies.userEachUserListsLimit" type="number"> | ||||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
|  | @ -134,7 +134,7 @@ | |||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { computed } from 'vue'; | ||||
| import { computed, reactive } from 'vue'; | ||||
| import XHeader from './_header_.vue'; | ||||
| import MkInput from '@/components/MkInput.vue'; | ||||
| import MkSelect from '@/components/MkSelect.vue'; | ||||
|  | @ -150,45 +150,36 @@ import { definePageMetadata } from '@/scripts/page-metadata'; | |||
| import { instance } from '@/instance'; | ||||
| import { useRouter } from '@/router'; | ||||
| 
 | ||||
| const ROLE_POLICIES = [ | ||||
| 	'gtlAvailable', | ||||
| 	'ltlAvailable', | ||||
| 	'canPublicNote', | ||||
| 	'canInvite', | ||||
| 	'canManageCustomEmojis', | ||||
| 	'driveCapacityMb', | ||||
| 	'pinLimit', | ||||
| 	'antennaLimit', | ||||
| 	'wordMuteLimit', | ||||
| 	'webhookLimit', | ||||
| 	'clipLimit', | ||||
| 	'noteEachClipsLimit', | ||||
| 	'userListLimit', | ||||
| 	'userEachUserListsLimit', | ||||
| 	'rateLimitFactor', | ||||
| ] as const; | ||||
| 
 | ||||
| const router = useRouter(); | ||||
| 
 | ||||
| const roles = await os.api('admin/roles/list'); | ||||
| 
 | ||||
| let options_gtlAvailable = $ref(instance.baseRole.gtlAvailable); | ||||
| let options_ltlAvailable = $ref(instance.baseRole.ltlAvailable); | ||||
| let options_canPublicNote = $ref(instance.baseRole.canPublicNote); | ||||
| let options_canInvite = $ref(instance.baseRole.canInvite); | ||||
| let options_canManageCustomEmojis = $ref(instance.baseRole.canManageCustomEmojis); | ||||
| let options_driveCapacityMb = $ref(instance.baseRole.driveCapacityMb); | ||||
| let options_pinLimit = $ref(instance.baseRole.pinLimit); | ||||
| let options_antennaLimit = $ref(instance.baseRole.antennaLimit); | ||||
| let options_wordMuteLimit = $ref(instance.baseRole.wordMuteLimit); | ||||
| let options_webhookLimit = $ref(instance.baseRole.webhookLimit); | ||||
| let options_clipLimit = $ref(instance.baseRole.clipLimit); | ||||
| let options_noteEachClipsLimit = $ref(instance.baseRole.noteEachClipsLimit); | ||||
| let options_userListLimit = $ref(instance.baseRole.userListLimit); | ||||
| let options_userEachUserListsLimit = $ref(instance.baseRole.userEachUserListsLimit); | ||||
| let options_rateLimitFactor = $ref(instance.baseRole.rateLimitFactor); | ||||
| const policies = reactive<Record<typeof ROLE_POLICIES[number], any>>({}); | ||||
| for (const ROLE_POLICY of ROLE_POLICIES) { | ||||
| 	policies[ROLE_POLICY] = instance.policies[ROLE_POLICY]; | ||||
| } | ||||
| 
 | ||||
| async function updateBaseRole() { | ||||
| 	await os.apiWithDialog('admin/roles/update-default-role-override', { | ||||
| 		options: { | ||||
| 			gtlAvailable: options_gtlAvailable, | ||||
| 			ltlAvailable: options_ltlAvailable, | ||||
| 			canPublicNote: options_canPublicNote, | ||||
| 			canInvite: options_canInvite, | ||||
| 			canManageCustomEmojis: options_canManageCustomEmojis, | ||||
| 			driveCapacityMb: options_driveCapacityMb, | ||||
| 			pinLimit: options_pinLimit, | ||||
| 			antennaLimit: options_antennaLimit, | ||||
| 			wordMuteLimit: options_wordMuteLimit, | ||||
| 			webhookLimit: options_webhookLimit, | ||||
| 			clipLimit: options_clipLimit, | ||||
| 			noteEachClipsLimit: options_noteEachClipsLimit, | ||||
| 			userListLimit: options_userListLimit, | ||||
| 			userEachUserListsLimit: options_userEachUserListsLimit, | ||||
| 			rateLimitFactor: options_rateLimitFactor, | ||||
| 		}, | ||||
| 	await os.apiWithDialog('admin/roles/update-default-policies', { | ||||
| 		policies, | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,8 +35,8 @@ import { definePageMetadata } from '@/scripts/page-metadata'; | |||
| 
 | ||||
| const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue')); | ||||
| 
 | ||||
| const isLocalTimelineAvailable = ($i == null && instance.baseRole.ltlAvailable) || ($i != null && $i.role.ltlAvailable); | ||||
| const isGlobalTimelineAvailable = ($i == null && instance.baseRole.gtlAvailable) || ($i != null && $i.role.gtlAvailable); | ||||
| const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable); | ||||
| const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable); | ||||
| const keymap = { | ||||
| 	't': focus, | ||||
| }; | ||||
|  |  | |||
|  | @ -86,16 +86,28 @@ | |||
| 					</div> | ||||
| 				</FormSection> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div v-else-if="tab === 'moderation'" class="_gaps_m"> | ||||
| 				<MkSwitch v-model="suspended" @update:model-value="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch> | ||||
| 
 | ||||
| 				<div> | ||||
| 					<MkButton v-if="user.host == null && iAmModerator" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton> | ||||
| 					<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<MkFolder> | ||||
| 					<template #icon><i class="ti ti-license"></i></template> | ||||
| 					<template #label>{{ i18n.ts._role.policies }}</template> | ||||
| 					<div class="_gaps"> | ||||
| 						<div v-for="policy in Object.keys(info.policies)" :key="policy"> | ||||
| 							{{ policy }} ... {{ info.policies[policy] }} | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</MkFolder> | ||||
| 
 | ||||
| 				<MkFolder> | ||||
| 					<template #icon><i class="ti ti-badges"></i></template> | ||||
| 					<template #label>{{ i18n.ts.roles }}</template> | ||||
| 
 | ||||
| 					<div class="_gaps"> | ||||
| 						<MkButton v-if="user.host == null && iAmModerator" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton> | ||||
| 
 | ||||
|  | @ -105,6 +117,7 @@ | |||
| 						</div> | ||||
| 					</div> | ||||
| 				</MkFolder> | ||||
| 
 | ||||
| 				<MkFolder> | ||||
| 					<template #icon><i class="ti ti-password"></i></template> | ||||
| 					<template #label>IP</template> | ||||
|  | @ -117,16 +130,18 @@ | |||
| 						</div> | ||||
| 					</template> | ||||
| 				</MkFolder> | ||||
| 
 | ||||
| 				<MkFolder> | ||||
| 					<template #icon><i class="ti ti-cloud"></i></template> | ||||
| 					<template #label>{{ i18n.ts.files }}</template> | ||||
| 
 | ||||
| 					<MkFileListForAdmin :pagination="filesPagination" view-mode="grid"/> | ||||
| 				</MkFolder> | ||||
| 
 | ||||
| 				<MkTextarea v-model="moderationNote" manual-save> | ||||
| 					<template #label>Moderation note</template> | ||||
| 				</MkTextarea> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div v-else-if="tab === 'chart'" class="_gaps_m"> | ||||
| 				<div class="cmhjzshm"> | ||||
| 					<div class="selects"> | ||||
|  | @ -142,6 +157,7 @@ | |||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div v-else-if="tab === 'raw'" class="_gaps_m"> | ||||
| 				<MkObjectView v-if="info && $i.isAdmin" tall :value="info"> | ||||
| 				</MkObjectView> | ||||
|  |  | |||
|  | @ -47,7 +47,7 @@ export function openInstanceMenu(ev: MouseEvent) { | |||
| 			to: '/clicker', | ||||
| 			text: '🍪👈', | ||||
| 			icon: 'ti ti-cookie', | ||||
| 		}, ($i && ($i.isAdmin || $i.role.canInvite) && instance.disableRegistration) ? { | ||||
| 		}, ($i && ($i.isAdmin || $i.policies.canInvite) && instance.disableRegistration) ? { | ||||
| 			text: i18n.ts.invite, | ||||
| 			icon: 'ti ti-user-plus', | ||||
| 			action: () => { | ||||
|  | @ -63,7 +63,7 @@ export function openInstanceMenu(ev: MouseEvent) { | |||
| 					}); | ||||
| 				}); | ||||
| 			}, | ||||
| 		} : undefined, ($i && ($i.isAdmin || $i.role.canManageCustomEmojis)) ? { | ||||
| 		} : undefined, ($i && ($i.isAdmin || $i.policies.canManageCustomEmojis)) ? { | ||||
| 			type: 'link', | ||||
| 			to: '/custom-emojis-manager', | ||||
| 			text: i18n.ts.manageCustomEmojis, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue