1 parent
a4615c7b73
commit
77258eee9d
6 files changed
+265
-7
No files matched your search
@@ -30,15 +30,23 @@ const messages = defineMessages({
|
||||
error: { id: 'app.skin-site.home.error', defaultMessage: 'Could not load players.' },
|
||||
signIn: { id: 'app.skin-site.home.sign-in', defaultMessage: 'Sign in' },
|
||||
settings: { id: 'app.skin-site.home.settings', defaultMessage: 'Account' },
|
||||
admin: { id: 'app.skin-site.home.admin', defaultMessage: 'Admin' },
|
||||
myPlayers: { id: 'app.skin-site.home.my-players', defaultMessage: 'My players' },
|
||||
})
|
||||
|
||||
const emit = defineEmits<{ (e: 'sign-in' | 'open-settings' | 'open-players'): void }>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'sign-in' | 'open-settings' | 'open-players' | 'open-admin'): void
|
||||
}>()
|
||||
|
||||
const luck = ref<number | null>(null)
|
||||
const luckLoading = ref(false)
|
||||
|
||||
const signedIn = computed(() => skinSiteStatus.value === 'signed-in' && !!skinSiteUser.value)
|
||||
|
||||
const isAdmin = computed(() => {
|
||||
const permissions = skinSiteUser.value?.permissions ?? []
|
||||
return permissions.includes('WEB_ADMIN') || permissions.includes('SERVER_ADMIN')
|
||||
})
|
||||
const players = computed(() => skinSitePlayers.value)
|
||||
const playersLoading = computed(
|
||||
() => skinSitePlayersStatus.value === 'checking' || skinSitePlayersStatus.value === 'idle',
|
||||
@@ -98,6 +106,14 @@ watch(signedIn, loadLuck)
|
||||
>
|
||||
{{ formatMessage(messages.settings) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="isAdmin"
|
||||
type="button"
|
||||
class="shrink-0 rounded-full bg-brand px-3 py-1 text-xs font-semibold text-contrast hover:brightness-110"
|
||||
@click="emit('open-admin')"
|
||||
>
|
||||
{{ formatMessage(messages.admin) }}
|
||||
</button>
|
||||
<div
|
||||
class="ml-auto flex shrink-0 items-center gap-1 rounded-full bg-button-bg px-3 py-1 text-sm"
|
||||
:title="formatMessage(messages.luck)"
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
<script setup lang="ts">
|
||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { skinSiteUser } from '@/composables/skin-site-session'
|
||||
|
||||
const emit = defineEmits<{ (e: 'back'): void }>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'app.skin-site.admin.title', defaultMessage: 'Admin console' },
|
||||
back: { id: 'app.skin-site.admin.back', defaultMessage: 'Back to home' },
|
||||
noAccess: { id: 'app.skin-site.admin.no-access', defaultMessage: 'You do not have access.' },
|
||||
noAccessHint: {
|
||||
id: 'app.skin-site.admin.no-access-hint',
|
||||
defaultMessage: 'Sign in with an administrator account to use this console.',
|
||||
},
|
||||
groupSecurity: { id: 'app.skin-site.admin.group.security', defaultMessage: 'Security' },
|
||||
groupUsers: { id: 'app.skin-site.admin.group.users', defaultMessage: 'Users' },
|
||||
groupCommunity: { id: 'app.skin-site.admin.group.community', defaultMessage: 'Community' },
|
||||
groupResources: { id: 'app.skin-site.admin.group.resources', defaultMessage: 'Resources' },
|
||||
itemServers: { id: 'app.skin-site.admin.item.servers', defaultMessage: 'Servers' },
|
||||
itemSummary: { id: 'app.skin-site.admin.item.summary', defaultMessage: 'Summary' },
|
||||
itemAccessAudit: { id: 'app.skin-site.admin.item.access-audit', defaultMessage: 'Access audit' },
|
||||
itemUsers: { id: 'app.skin-site.admin.item.users', defaultMessage: 'Users' },
|
||||
itemPlayers: { id: 'app.skin-site.admin.item.players', defaultMessage: 'Players' },
|
||||
itemReview: { id: 'app.skin-site.admin.item.review', defaultMessage: 'Player review' },
|
||||
itemBroadcast: { id: 'app.skin-site.admin.item.broadcast', defaultMessage: 'Broadcast' },
|
||||
itemChat: { id: 'app.skin-site.admin.item.chat', defaultMessage: 'Chat rooms' },
|
||||
itemWiki: { id: 'app.skin-site.admin.item.wiki', defaultMessage: 'Wiki' },
|
||||
itemDonations: { id: 'app.skin-site.admin.item.donations', defaultMessage: 'Donations' },
|
||||
itemMods: { id: 'app.skin-site.admin.item.mods', defaultMessage: 'Mods' },
|
||||
itemLauncher: { id: 'app.skin-site.admin.item.launcher', defaultMessage: 'Launcher' },
|
||||
itemDownloads: { id: 'app.skin-site.admin.item.downloads', defaultMessage: 'Downloads' },
|
||||
})
|
||||
|
||||
type AdminKey =
|
||||
| 'servers'
|
||||
| 'summary'
|
||||
| 'access-audit'
|
||||
| 'users'
|
||||
| 'players'
|
||||
| 'review'
|
||||
| 'broadcast'
|
||||
| 'chat'
|
||||
| 'wiki'
|
||||
| 'donations'
|
||||
| 'mods'
|
||||
| 'launcher'
|
||||
| 'downloads'
|
||||
|
||||
interface NavItem {
|
||||
key: AdminKey
|
||||
label: string
|
||||
}
|
||||
interface NavGroup {
|
||||
id: string
|
||||
label: string
|
||||
items: NavItem[]
|
||||
}
|
||||
|
||||
const navGroups = computed<NavGroup[]>(() => [
|
||||
{
|
||||
id: 'security',
|
||||
label: formatMessage(messages.groupSecurity),
|
||||
items: [
|
||||
{ key: 'servers', label: formatMessage(messages.itemServers) },
|
||||
{ key: 'summary', label: formatMessage(messages.itemSummary) },
|
||||
{ key: 'access-audit', label: formatMessage(messages.itemAccessAudit) },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'users',
|
||||
label: formatMessage(messages.groupUsers),
|
||||
items: [
|
||||
{ key: 'users', label: formatMessage(messages.itemUsers) },
|
||||
{ key: 'players', label: formatMessage(messages.itemPlayers) },
|
||||
{ key: 'review', label: formatMessage(messages.itemReview) },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'community',
|
||||
label: formatMessage(messages.groupCommunity),
|
||||
items: [
|
||||
{ key: 'broadcast', label: formatMessage(messages.itemBroadcast) },
|
||||
{ key: 'chat', label: formatMessage(messages.itemChat) },
|
||||
{ key: 'wiki', label: formatMessage(messages.itemWiki) },
|
||||
{ key: 'donations', label: formatMessage(messages.itemDonations) },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'resources',
|
||||
label: formatMessage(messages.groupResources),
|
||||
items: [
|
||||
{ key: 'mods', label: formatMessage(messages.itemMods) },
|
||||
{ key: 'launcher', label: formatMessage(messages.itemLauncher) },
|
||||
{ key: 'downloads', label: formatMessage(messages.itemDownloads) },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const hasAdminAccess = computed(() => {
|
||||
const permissions = skinSiteUser.value?.permissions ?? []
|
||||
return permissions.includes('WEB_ADMIN') || permissions.includes('SERVER_ADMIN')
|
||||
})
|
||||
|
||||
const current = ref<AdminKey>('summary')
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex h-full min-h-0 flex-col gap-4">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-fit items-center gap-1 rounded-full bg-button-bg px-3 py-1 text-xs font-semibold text-contrast hover:brightness-110"
|
||||
@click="emit('back')"
|
||||
>
|
||||
← {{ formatMessage(messages.back) }}
|
||||
</button>
|
||||
|
||||
<div
|
||||
v-if="!hasAdminAccess"
|
||||
class="flex flex-col gap-1 rounded-xl border border-solid border-surface-5 bg-bg-raised p-6"
|
||||
>
|
||||
<h2 class="m-0 text-lg font-bold text-contrast">{{ formatMessage(messages.noAccess) }}</h2>
|
||||
<p class="m-0 text-sm text-secondary">{{ formatMessage(messages.noAccessHint) }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex min-h-0 flex-1 gap-4">
|
||||
<nav class="flex w-48 shrink-0 flex-col gap-3 overflow-y-auto">
|
||||
<div v-for="group in navGroups" :key="group.id" class="flex flex-col gap-1">
|
||||
<span class="px-2 text-[10px] font-bold tracking-wide text-secondary uppercase">
|
||||
{{ group.label }}
|
||||
</span>
|
||||
<button
|
||||
v-for="item in group.items"
|
||||
:key="item.key"
|
||||
type="button"
|
||||
class="rounded-lg px-2 py-1.5 text-left text-xs font-semibold transition-colors"
|
||||
:class="
|
||||
current === item.key
|
||||
? 'bg-brand text-contrast'
|
||||
: 'text-secondary hover:bg-button-bg hover:text-contrast'
|
||||
"
|
||||
@click="current = item.key"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section
|
||||
class="min-w-0 flex-1 overflow-y-auto rounded-xl border border-solid border-surface-5 bg-bg-raised p-4"
|
||||
>
|
||||
<h1 class="m-0 mb-3 text-xl font-bold text-contrast">
|
||||
{{ formatMessage(messages.title) }} · {{ current }}
|
||||
</h1>
|
||||
<!-- Page content is filled in one admin screen at a time. -->
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -4,6 +4,7 @@ import { defineMessages, injectNotificationManager, useVIntl } from '@modrinth/u
|
||||
import { ref } from 'vue'
|
||||
|
||||
import SkinSiteAccountCard from './SkinSiteAccountCard.vue'
|
||||
import SkinSiteAdminView from './SkinSiteAdminView.vue'
|
||||
import SkinSiteAnnouncements from './SkinSiteAnnouncements.vue'
|
||||
import SkinSiteBugCreateModal from './SkinSiteBugCreateModal.vue'
|
||||
import SkinSiteBugDetailView from './SkinSiteBugDetailView.vue'
|
||||
@@ -35,7 +36,15 @@ const YGGDRASIL_URL = 'https://skin.starlight.cool/yggdrasil'
|
||||
const loginModal = ref<InstanceType<typeof SkinSiteLoginModal>>()
|
||||
const profileModal = ref<InstanceType<typeof SkinSiteProfileModal>>()
|
||||
const view = ref<
|
||||
'home' | 'players' | 'downloads' | 'map' | 'review' | 'bugs' | 'bug-detail' | 'bug-notifications'
|
||||
| 'home'
|
||||
| 'players'
|
||||
| 'downloads'
|
||||
| 'map'
|
||||
| 'review'
|
||||
| 'bugs'
|
||||
| 'bug-detail'
|
||||
| 'bug-notifications'
|
||||
| 'admin'
|
||||
>('home')
|
||||
const selectedBugId = ref<number | null>(null)
|
||||
|
||||
@@ -88,6 +97,7 @@ async function copyAddress() {
|
||||
@back="view = 'bugs'"
|
||||
@open="openBug"
|
||||
/>
|
||||
<SkinSiteAdminView v-else-if="view === 'admin'" @back="view = 'home'" />
|
||||
<SkinSiteBugDetailView
|
||||
v-else-if="view === 'bug-detail' && selectedBugId !== null"
|
||||
:issue-id="selectedBugId"
|
||||
@@ -104,6 +114,7 @@ async function copyAddress() {
|
||||
@sign-in="loginModal?.show()"
|
||||
@open-settings="profileModal?.show()"
|
||||
@open-players="view = 'players'"
|
||||
@open-admin="view = 'admin'"
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
|
||||
@@ -19,7 +19,7 @@ import { fetchImageAsDataUrl, renderSkinHead } from '@/helpers/skin-site-render'
|
||||
|
||||
export const SKIN_SITE_ORIGIN = 'https://skin.starlight.cool'
|
||||
|
||||
export type SkinSiteUser = { uuid: string; username: string }
|
||||
export type SkinSiteUser = { uuid: string; username: string; permissions: string[] }
|
||||
export type SkinSiteStatus = 'checking' | 'signed-out' | 'signed-in' | 'error'
|
||||
export type SkinSitePlayer = {
|
||||
uuid: string
|
||||
@@ -46,7 +46,11 @@ export const skinSitePlayersStatus = readonly(playersStatus)
|
||||
export const selectedSkinSitePlayerId = readonly(selectedPlayerId)
|
||||
|
||||
function toUser(raw: ApiSkinSiteUser): SkinSiteUser {
|
||||
return { uuid: raw.uuid, username: raw.username }
|
||||
return {
|
||||
uuid: raw.uuid,
|
||||
username: raw.username,
|
||||
permissions: Array.isArray(raw.permissions) ? raw.permissions : [],
|
||||
}
|
||||
}
|
||||
|
||||
function clearSessionState() {
|
||||
@@ -111,7 +115,7 @@ export async function signInSkinSite(payload: {
|
||||
status.value = 'signed-in'
|
||||
void requestSkinSitePlayers().catch(() => {})
|
||||
} else {
|
||||
user.value = { uuid: '', username: result.payload.username }
|
||||
user.value = { uuid: '', username: result.payload.username, permissions: [] }
|
||||
status.value = 'signed-in'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11767,5 +11767,38 @@
|
||||
"app.skin-site.home.subtitle": {"message": "The wardrobe for StarLight Server players."},
|
||||
"app.skin-site.home.address": {"message": "Skin site address"},
|
||||
"app.skin-site.home.copied": {"message": "Address copied."},
|
||||
"app.skin-site.home.copy": {"message": "Copy"}
|
||||
"app.skin-site.home.copy": {"message": "Copy"},
|
||||
"app.skin-site.admin.title": {"message": "Admin console"},
|
||||
"app.skin-site.admin.back": {"message": "Back to home"},
|
||||
"app.skin-site.admin.no-access": {"message": "You do not have access."},
|
||||
"app.skin-site.admin.no-access-hint": {"message": "Sign in with an administrator account to use this console."},
|
||||
"app.skin-site.admin.group.security": {"message": "Security"},
|
||||
"app.skin-site.admin.group.users": {"message": "Users"},
|
||||
"app.skin-site.admin.group.community": {"message": "Community"},
|
||||
"app.skin-site.admin.group.resources": {"message": "Resources"},
|
||||
"app.skin-site.admin.item.servers": {"message": "Servers"},
|
||||
"app.skin-site.admin.item.summary": {"message": "Summary"},
|
||||
"app.skin-site.admin.item.access-audit": {"message": "Access audit"},
|
||||
"app.skin-site.admin.item.users": {"message": "Users"},
|
||||
"app.skin-site.admin.item.players": {"message": "Players"},
|
||||
"app.skin-site.admin.item.review": {"message": "Player review"},
|
||||
"app.skin-site.admin.item.broadcast": {"message": "Broadcast"},
|
||||
"app.skin-site.admin.item.chat": {"message": "Chat rooms"},
|
||||
"app.skin-site.admin.item.wiki": {"message": "Wiki"},
|
||||
"app.skin-site.admin.item.donations": {"message": "Donations"},
|
||||
"app.skin-site.admin.item.mods": {"message": "Mods"},
|
||||
"app.skin-site.admin.item.launcher": {"message": "Launcher"},
|
||||
"app.skin-site.admin.item.downloads": {"message": "Downloads"},
|
||||
"app.skin-site.home.admin": {"message": "Admin"},
|
||||
"app.skin-site.home.settings": {"message": "Account"},
|
||||
"app.skin-site.home.sign-in": {"message": "Sign in"},
|
||||
"app.skin-site.home.players": {"message": "Players"},
|
||||
"app.skin-site.home.my-players": {"message": "My players"},
|
||||
"app.skin-site.home.no-players": {"message": "No players linked yet."},
|
||||
"app.skin-site.home.loading": {"message": "Loading…"},
|
||||
"app.skin-site.home.error": {"message": "Could not load."},
|
||||
"app.skin-site.home.luck": {"message": "Luck"},
|
||||
"app.skin-site.home.luck-unavailable": {"message": "Unavailable"},
|
||||
"app.skin-site.home.signed-out-title": {"message": "Not signed in"},
|
||||
"app.skin-site.home.signed-out-body": {"message": "Sign in to manage your account and textures."}
|
||||
}
|
||||
@@ -11598,5 +11598,38 @@
|
||||
"app.skin-site.home.subtitle": {"message": "星光服务器玩家的衣柜。"},
|
||||
"app.skin-site.home.address": {"message": "皮肤站地址"},
|
||||
"app.skin-site.home.copied": {"message": "地址已复制。"},
|
||||
"app.skin-site.home.copy": {"message": "复制"}
|
||||
"app.skin-site.home.copy": {"message": "复制"},
|
||||
"app.skin-site.admin.title": {"message": "管理控制台"},
|
||||
"app.skin-site.admin.back": {"message": "返回主页"},
|
||||
"app.skin-site.admin.no-access": {"message": "你没有访问权限。"},
|
||||
"app.skin-site.admin.no-access-hint": {"message": "请使用管理员账号登录后使用本控制台。"},
|
||||
"app.skin-site.admin.group.security": {"message": "系统安全"},
|
||||
"app.skin-site.admin.group.users": {"message": "用户管理"},
|
||||
"app.skin-site.admin.group.community": {"message": "社区管理"},
|
||||
"app.skin-site.admin.group.resources": {"message": "资源分发"},
|
||||
"app.skin-site.admin.item.servers": {"message": "服务器管理"},
|
||||
"app.skin-site.admin.item.summary": {"message": "状态概览"},
|
||||
"app.skin-site.admin.item.access-audit": {"message": "登录与认证审计"},
|
||||
"app.skin-site.admin.item.users": {"message": "用户信息"},
|
||||
"app.skin-site.admin.item.players": {"message": "玩家信息"},
|
||||
"app.skin-site.admin.item.review": {"message": "玩家审核"},
|
||||
"app.skin-site.admin.item.broadcast": {"message": "公告操作"},
|
||||
"app.skin-site.admin.item.chat": {"message": "聊天室管理"},
|
||||
"app.skin-site.admin.item.wiki": {"message": "维基管理"},
|
||||
"app.skin-site.admin.item.donations": {"message": "捐赠管理"},
|
||||
"app.skin-site.admin.item.mods": {"message": "Mod 管理"},
|
||||
"app.skin-site.admin.item.launcher": {"message": "启动器管理"},
|
||||
"app.skin-site.admin.item.downloads": {"message": "下载管理"},
|
||||
"app.skin-site.home.admin": {"message": "管理"},
|
||||
"app.skin-site.home.settings": {"message": "账号"},
|
||||
"app.skin-site.home.sign-in": {"message": "登录"},
|
||||
"app.skin-site.home.players": {"message": "角色"},
|
||||
"app.skin-site.home.my-players": {"message": "我的角色"},
|
||||
"app.skin-site.home.no-players": {"message": "尚未绑定角色。"},
|
||||
"app.skin-site.home.loading": {"message": "加载中…"},
|
||||
"app.skin-site.home.error": {"message": "加载失败。"},
|
||||
"app.skin-site.home.luck": {"message": "幸运值"},
|
||||
"app.skin-site.home.luck-unavailable": {"message": "不可用"},
|
||||
"app.skin-site.home.signed-out-title": {"message": "未登录"},
|
||||
"app.skin-site.home.signed-out-body": {"message": "登录后可管理你的账号与皮肤。"}
|
||||
}
|
||||
Reference in new issue
Block a user