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