rudeshark.net/packages/client/src/pages/admin/index.vue
ThatOneCalculator a82ef942b4
refactor: 💄 relay icon
2023-07-13 17:22:35 -07:00

488 lines
11 KiB
Vue

<template>
<div ref="el" class="hiyeyicy" :class="{ wide: !narrow }">
<div v-if="!narrow || currentPage?.route.name == null" class="nav">
<MkSpacer :content-max="700" :margin-min="16">
<div class="lxpfedzu">
<div class="banner">
<img
:src="$instance.iconUrl || '/favicon.ico'"
alt=""
class="icon"
/>
</div>
<MkInfo
v-if="thereIsUnresolvedAbuseReport"
warn
class="info"
>{{ i18n.ts.thereIsUnresolvedAbuseReportWarning }}
<MkA to="/admin/abuses" class="_link">{{
i18n.ts.check
}}</MkA></MkInfo
>
<MkInfo v-if="noMaintainerInformation" warn class="info"
>{{ i18n.ts.noMaintainerInformationWarning }}
<MkA to="/admin/settings" class="_link">{{
i18n.ts.configure
}}</MkA></MkInfo
>
<MkInfo v-if="noBotProtection" warn class="info"
>{{ i18n.ts.noBotProtectionWarning }}
<MkA to="/admin/security" class="_link">{{
i18n.ts.configure
}}</MkA></MkInfo
>
<MkInfo v-if="noEmailServer" warn class="info"
>{{ i18n.ts.noEmailServerWarning }}
<MkA to="/admin/email-settings" class="_link">{{
i18n.ts.configure
}}</MkA></MkInfo
>
<MkInfo v-if="updateAvailable" warn class="info"
>{{ i18n.ts.updateAvailable }}
<a
href="https://codeberg.org/calckey/calckey/releases"
target="_bank"
class="_link"
>{{ i18n.ts.check }}</a
></MkInfo
>
<MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
</div>
</MkSpacer>
</div>
<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
<RouterView />
</div>
</div>
</template>
<script lang="ts" setup>
import {
defineAsyncComponent,
inject,
nextTick,
onMounted,
onUnmounted,
onActivated,
provide,
watch,
ref,
} from "vue";
import { i18n } from "@/i18n";
import MkSuperMenu from "@/components/MkSuperMenu.vue";
import MkInfo from "@/components/MkInfo.vue";
import { scroll } from "@/scripts/scroll";
import { instance } from "@/instance";
import { version } from "@/config";
import { $i } from "@/account";
import * as os from "@/os";
import { lookupUser } from "@/scripts/lookup-user";
import { lookupFile } from "@/scripts/lookup-file";
import { lookupInstance } from "@/scripts/lookup-instance";
import { indexPosts } from "@/scripts/index-posts";
import { defaultStore } from "@/store";
import { useRouter } from "@/router";
import {
definePageMetadata,
provideMetadataReceiver,
setPageMetadata,
} from "@/scripts/page-metadata";
const isEmpty = (x: string | null) => x == null || x === "";
const el = ref<HTMLElement | null>(null);
const router = useRouter();
const indexInfo = {
title: i18n.ts.controlPanel,
icon: "ph-gear-six ph-bold ph-lg",
hideHeader: true,
};
provide("shouldOmitHeaderTitle", false);
let INFO = $ref(indexInfo);
let childInfo = $ref(null);
let narrow = $ref(false);
let view = $ref(null);
let pageProps = $ref({});
let noMaintainerInformation =
isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
let noBotProtection =
!instance.disableRegistration &&
!instance.enableHcaptcha &&
!instance.enableRecaptcha;
let noEmailServer = !instance.enableEmail;
let thereIsUnresolvedAbuseReport = $ref(false);
let updateAvailable = $ref(false);
let currentPage = $computed(() => router.currentRef.value.child);
os.api("admin/abuse-user-reports", {
state: "unresolved",
limit: 1,
}).then((reports) => {
if (reports?.length > 0) thereIsUnresolvedAbuseReport = true;
});
if (defaultStore.state.showAdminUpdates) {
os.api("latest-version").then((res) => {
const cleanRes = parseInt(res?.tag_name.replace(/[^0-9]/g, ""));
const cleanVersion = parseInt(version.replace(/[^0-9]/g, ""));
if (cleanRes > cleanVersion) {
updateAvailable = true;
}
});
}
const NARROW_THRESHOLD = 600;
const ro = new ResizeObserver((entries, observer) => {
if (entries.length === 0) return;
narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
});
const menuDef = $computed(() => [
{
title: i18n.ts.quickAction,
items: [
{
type: "button",
icon: "ph-magnifying-glass ph-bold ph-lg",
text: i18n.ts.lookup,
action: lookup,
},
...(instance.disableRegistration
? [
{
type: "button",
icon: "ph-user-plus ph-bold ph-lg",
text: i18n.ts.invite,
action: invite,
},
]
: []),
...($i.isAdmin
? [
{
type: "button",
icon: "ph-list-magnifying-glass ph-bold ph-lg",
text: i18n.ts.indexPosts,
action: indexPosts,
},
]
: []),
],
},
{
title: i18n.ts.administration,
items: [
{
icon: "ph-gauge ph-bold ph-lg",
text: i18n.ts.dashboard,
to: "/admin/overview",
active: currentPage?.route.name === "overview",
},
{
icon: "ph-users ph-bold ph-lg",
text: i18n.ts.users,
to: "/admin/users",
active: currentPage?.route.name === "users",
},
{
icon: "ph-smiley ph-bold ph-lg",
text: i18n.ts.customEmojis,
to: "/admin/emojis",
active: currentPage?.route.name === "emojis",
},
{
icon: "ph-planet ph-bold ph-lg",
text: i18n.ts.federation,
to: "/admin/federation",
active: currentPage?.route.name === "federation",
},
{
icon: "ph-queue ph-bold ph-lg",
text: i18n.ts.jobQueue,
to: "/admin/queue",
active: currentPage?.route.name === "queue",
},
{
icon: "ph-cloud ph-bold ph-lg",
text: i18n.ts.files,
to: "/admin/files",
active: currentPage?.route.name === "files",
},
{
icon: "ph-megaphone-simple ph-bold ph-lg",
text: i18n.ts.announcements,
to: "/admin/announcements",
active: currentPage?.route.name === "announcements",
},
{
icon: "ph-money ph-bold ph-lg",
text: i18n.ts.ads,
to: "/admin/ads",
active: currentPage?.route.name === "ads",
},
{
icon: "ph-warning-circle ph-bold ph-lg",
text: i18n.ts.abuseReports,
to: "/admin/abuses",
active: currentPage?.route.name === "abuses",
},
],
},
...($i?.isAdmin
? [
{
title: i18n.ts.settings,
items: [
{
icon: "ph-gear-six ph-bold ph-lg",
text: i18n.ts.general,
to: "/admin/settings",
active: currentPage?.route.name === "settings",
},
{
icon: "ph-envelope-simple-open ph-bold ph-lg",
text: i18n.ts.emailServer,
to: "/admin/email-settings",
active:
currentPage?.route.name === "email-settings",
},
{
icon: "ph-cloud ph-bold ph-lg",
text: i18n.ts.objectStorage,
to: "/admin/object-storage",
active:
currentPage?.route.name === "object-storage",
},
{
icon: "ph-lock ph-bold ph-lg",
text: i18n.ts.security,
to: "/admin/security",
active: currentPage?.route.name === "security",
},
{
icon: "ph-arrows-merge ph-bold ph-lg",
text: i18n.ts.relays,
to: "/admin/relays",
active: currentPage?.route.name === "relays",
},
{
icon: "ph-plug ph-bold ph-lg",
text: i18n.ts.integration,
to: "/admin/integrations",
active: currentPage?.route.name === "integrations",
},
{
icon: "ph-prohibit ph-bold ph-lg",
text: i18n.ts.instanceBlocking,
to: "/admin/instance-block",
active:
currentPage?.route.name === "instance-block",
},
{
icon: "ph-hash ph-bold ph-lg",
text: i18n.ts.hiddenTags,
to: "/admin/hashtags",
active: currentPage?.route.name === "hashtags",
},
{
icon: "ph-ghost ph-bold ph-lg",
text: i18n.ts.proxyAccount,
to: "/admin/proxy-account",
active: currentPage?.route.name === "proxy-account",
},
{
icon: "ph-database ph-bold ph-lg",
text: i18n.ts.database,
to: "/admin/database",
active: currentPage?.route.name === "database",
},
{
icon: "ph-flask ph-bold ph-lg",
text: i18n.ts._experiments.title,
to: "/admin/experiments",
active: currentPage?.route.name === "experiments",
},
],
},
]
: []),
]);
watch(narrow, () => {
if (currentPage?.route.name == null && !narrow) {
router.push("/admin/overview");
}
});
onMounted(() => {
ro.observe(el.value);
narrow = el.value.offsetWidth < NARROW_THRESHOLD;
if (currentPage?.route.name == null && !narrow) {
router.push("/admin/overview");
}
});
onActivated(() => {
narrow = el.value.offsetWidth < NARROW_THRESHOLD;
if (!narrow && currentPage?.route.name == null) {
router.replace("/admin/overview");
}
});
onUnmounted(() => {
ro.disconnect();
});
watch(router.currentRef, (to) => {
if (to.route.path === "/admin" && to.child?.route.name == null && !narrow) {
router.replace("/admin/overview");
}
});
provideMetadataReceiver((info) => {
if (info == null) {
childInfo = null;
} else {
childInfo = info;
}
});
const invite = () => {
os.api("admin/invite")
.then((x) => {
os.alert({
type: "info",
text: x.code,
});
})
.catch((err) => {
os.alert({
type: "error",
text: err,
});
});
};
async function lookupNote() {
const { canceled, result: q } = await os.inputText({
title: i18n.ts.noteId,
});
if (canceled) return;
os.api(
"notes/show",
q.startsWith("http://") || q.startsWith("https://")
? { url: q.trim() }
: { noteId: q.trim() },
)
.then((note) => {
os.pageWindow(`/notes/${note.id}`);
})
.catch((err) => {
if (err.code === "NO_SUCH_NOTE") {
os.alert({
type: "error",
text: i18n.ts.notFound,
});
}
});
}
const lookup = (ev) => {
os.popupMenu(
[
{
text: i18n.ts.user,
icon: "ph-user ph-bold ph-lg",
action: () => {
lookupUser();
},
},
{
text: i18n.ts.note,
icon: "ph-pencil ph-bold ph-lg",
action: () => {
lookupNote();
},
},
{
text: i18n.ts.file,
icon: "ph-cloud ph-bold ph-lg",
action: () => {
lookupFile();
},
},
{
text: i18n.ts.instance,
icon: "ph-planet ph-bold ph-lg",
action: () => {
lookupInstance();
},
},
],
ev.currentTarget ?? ev.target,
);
};
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata(INFO);
defineExpose({
header: {
title: i18n.ts.controlPanel,
},
});
</script>
<style lang="scss" scoped>
.hiyeyicy {
&.wide {
display: flex;
margin: 0 auto;
height: 100%;
> .nav {
width: 32%;
max-width: 280px;
box-sizing: border-box;
border-right: solid 0.5px var(--divider);
overflow: auto;
height: 100%;
}
> .main {
flex: 1;
min-width: 0;
}
}
> .nav {
.lxpfedzu {
> .info {
margin: 16px 0;
}
> .banner {
margin: 16px;
> .icon {
display: block;
margin: auto;
height: 42px;
border-radius: 8px;
}
}
}
}
}
</style>