mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-14 14:07:38 -07:00
[Client] Admin page improved
This commit is contained in:
parent
d97dd83e18
commit
dd14a43f6d
20 changed files with 529 additions and 533 deletions
|
@ -1069,14 +1069,16 @@ desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
|
||||||
desktop/views/pages/admin/admin.vue:
|
admin/views/index.vue:
|
||||||
dashboard: "ダッシュボード"
|
dashboard: "ダッシュボード"
|
||||||
|
instance: "インスタンス"
|
||||||
|
emoji: "カスタム絵文字"
|
||||||
users: "ユーザー"
|
users: "ユーザー"
|
||||||
update: "更新"
|
update: "更新"
|
||||||
announcements: "お知らせ"
|
announcements: "お知らせ"
|
||||||
hashtags: "ハッシュタグ"
|
hashtags: "ハッシュタグ"
|
||||||
|
|
||||||
desktop/views/pages/admin/admin.dashboard.vue:
|
admin/views/dashboard.vue:
|
||||||
dashboard: "ダッシュボード"
|
dashboard: "ダッシュボード"
|
||||||
all-users: "全てのユーザー"
|
all-users: "全てのユーザー"
|
||||||
original-users: "このインスタンスのユーザー"
|
original-users: "このインスタンスのユーザー"
|
||||||
|
@ -1087,30 +1089,34 @@ desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
disableRegistration: "Disable new user registration"
|
disableRegistration: "Disable new user registration"
|
||||||
disableLocalTimeline: "Disable the local timeline"
|
disableLocalTimeline: "Disable the local timeline"
|
||||||
|
|
||||||
desktop/views/pages/admin/admin.suspend-user.vue:
|
admin/views/users.vue:
|
||||||
suspend-user: "ユーザーの凍結"
|
suspend-user: "ユーザーの凍結"
|
||||||
suspend: "凍結"
|
suspend: "凍結"
|
||||||
suspended: "凍結しました"
|
suspended: "凍結しました"
|
||||||
|
|
||||||
desktop/views/pages/admin/admin.unsuspend-user.vue:
|
|
||||||
unsuspend-user: "ユーザーの凍結の解除"
|
unsuspend-user: "ユーザーの凍結の解除"
|
||||||
unsuspend: "凍結の解除"
|
unsuspend: "凍結の解除"
|
||||||
unsuspended: "凍結を解除しました"
|
unsuspended: "凍結を解除しました"
|
||||||
|
|
||||||
desktop/views/pages/admin/admin.verify-user.vue:
|
|
||||||
verify-user: "ユーザーの公式アカウント設定"
|
verify-user: "ユーザーの公式アカウント設定"
|
||||||
verify: "公式アカウントにする"
|
verify: "公式アカウントにする"
|
||||||
verified: "公式アカウントにしました"
|
verified: "公式アカウントにしました"
|
||||||
|
|
||||||
desktop/views/pages/admin/admin.unverify-user.vue:
|
|
||||||
unverify-user: "ユーザーの公式アカウント解除"
|
unverify-user: "ユーザーの公式アカウント解除"
|
||||||
unverify: "公式アカウントを解除する"
|
unverify: "公式アカウントを解除する"
|
||||||
unverified: "公式アカウントを解除しました"
|
unverified: "公式アカウントを解除しました"
|
||||||
|
|
||||||
desktop/views/pages/admin/admin.announcements.vue:
|
admin/views/emoji.vue:
|
||||||
|
add-emoji:
|
||||||
|
title: "絵文字の登録"
|
||||||
|
name: "絵文字名"
|
||||||
|
name-desc: "a~z 0~9 _ の文字が使えます。"
|
||||||
|
aliases: "エイリアス"
|
||||||
|
aliases-desc: "スペースで区切って複数設定できます。"
|
||||||
|
url: "絵文字画像URL"
|
||||||
|
add: "追加"
|
||||||
|
|
||||||
|
admin/views/announcements.vue:
|
||||||
announcements: "お知らせ"
|
announcements: "お知らせ"
|
||||||
|
|
||||||
desktop/views/pages/admin/admin.hashtags.vue:
|
admin/views/hashtags.vue:
|
||||||
hided-tags: "Hidden Tags"
|
hided-tags: "Hidden Tags"
|
||||||
|
|
||||||
desktop/views/pages/deck/deck.tl-column.vue:
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
|
|
27
src/client/app/admin/script.ts
Normal file
27
src/client/app/admin/script.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* Admin
|
||||||
|
*/
|
||||||
|
|
||||||
|
import VueRouter from 'vue-router';
|
||||||
|
|
||||||
|
// Style
|
||||||
|
import './style.styl';
|
||||||
|
|
||||||
|
import init from '../init';
|
||||||
|
import Index from './views/index.vue';
|
||||||
|
|
||||||
|
init(launch => {
|
||||||
|
document.title = 'Admin';
|
||||||
|
|
||||||
|
// Init router
|
||||||
|
const router = new VueRouter({
|
||||||
|
mode: 'history',
|
||||||
|
base: '/admin/',
|
||||||
|
routes: [
|
||||||
|
{ path: '/', component: Index },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Launch the app
|
||||||
|
launch(router);
|
||||||
|
});
|
6
src/client/app/admin/style.styl
Normal file
6
src/client/app/admin/style.styl
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
@import "../app"
|
||||||
|
@import "../reset"
|
||||||
|
|
||||||
|
html
|
||||||
|
height 100%
|
||||||
|
background #EBEBEB
|
|
@ -1,8 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="qldxjjsrseehkusjuoooapmsprvfrxyl mk-admin-card">
|
<div>
|
||||||
<header>%i18n:@announcements%</header>
|
<ui-card>
|
||||||
<textarea v-model="broadcasts" placeholder='[ { "title": "Title1", "text": "Text1" }, { "title": "Title2", "text": "Text2" } ]'></textarea>
|
<div slot="title">%i18n:@announcements%</div>
|
||||||
<button class="ui" @click="save">%i18n:@save%</button>
|
<section>
|
||||||
|
<textarea class="qldxjjsrseehkusjuoooapmsprvfrxyl" v-model="broadcasts" placeholder='[ { "title": "Title1", "text": "Text1" }, { "title": "Title2", "text": "Text2" } ]'></textarea>
|
||||||
|
<ui-button @click="save">%i18n:@save%</ui-button>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -45,8 +49,7 @@ export default Vue.extend({
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.qldxjjsrseehkusjuoooapmsprvfrxyl
|
.qldxjjsrseehkusjuoooapmsprvfrxyl
|
||||||
textarea
|
width 100%
|
||||||
width 100%
|
min-height 300px
|
||||||
min-height 300px
|
|
||||||
|
|
||||||
</style>
|
</style>
|
117
src/client/app/admin/views/dashboard.vue
Normal file
117
src/client/app/admin/views/dashboard.vue
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<div class="obdskegsannmntldydackcpzezagxqfy">
|
||||||
|
<div v-if="stats" class="stats">
|
||||||
|
<div>
|
||||||
|
<div>%fa:user%</div>
|
||||||
|
<div>
|
||||||
|
<span>%i18n:@original-users%</span>
|
||||||
|
<b>{{ stats.originalUsersCount | number }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>%fa:pencil-alt%</div>
|
||||||
|
<div>
|
||||||
|
<span>%i18n:@original-notes%</span>
|
||||||
|
<b>{{ stats.originalNotesCount | number }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>%fa:user%</div>
|
||||||
|
<div>
|
||||||
|
<span>%i18n:@all-users%</span>
|
||||||
|
<b>{{ stats.usersCount | number }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>%fa:pencil-alt%</div>
|
||||||
|
<div>
|
||||||
|
<span>%i18n:@all-notes%</span>
|
||||||
|
<b>{{ stats.notesCount | number }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cpu-memory">
|
||||||
|
<x-cpu-memory :connection="connection"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import XCpuMemory from "./cpu-memory.vue";
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
XCpuMemory
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
stats: null,
|
||||||
|
connection: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.connection = (this as any).os.stream.useSharedConnection('serverStats');
|
||||||
|
|
||||||
|
(this as any).os.getMeta().then(meta => {
|
||||||
|
this.disableRegistration = meta.disableRegistration;
|
||||||
|
this.disableLocalTimeline = meta.disableLocalTimeline;
|
||||||
|
this.bannerUrl = meta.bannerUrl;
|
||||||
|
});
|
||||||
|
|
||||||
|
(this as any).api('stats').then(stats => {
|
||||||
|
this.stats = stats;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.connection.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.obdskegsannmntldydackcpzezagxqfy
|
||||||
|
> .stats
|
||||||
|
display flex
|
||||||
|
justify-content space-between
|
||||||
|
margin-bottom 16px
|
||||||
|
|
||||||
|
> div
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
flex 1
|
||||||
|
max-width 300px
|
||||||
|
margin-right 16px
|
||||||
|
text-align center
|
||||||
|
color var(--text)
|
||||||
|
box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
|
||||||
|
background var(--face)
|
||||||
|
border-radius 8px
|
||||||
|
|
||||||
|
&:last-child
|
||||||
|
margin-right 0
|
||||||
|
|
||||||
|
> div:first-child
|
||||||
|
padding 16px 24px
|
||||||
|
font-size 28px
|
||||||
|
|
||||||
|
> div:last-child
|
||||||
|
flex 1
|
||||||
|
padding 16px 32px 16px 0
|
||||||
|
text-align right
|
||||||
|
|
||||||
|
> span
|
||||||
|
opacity 0.7
|
||||||
|
|
||||||
|
> b
|
||||||
|
display block
|
||||||
|
|
||||||
|
> .cpu-memory
|
||||||
|
margin-bottom 16px
|
||||||
|
padding 32px
|
||||||
|
box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
|
||||||
|
background var(--face)
|
||||||
|
border-radius 8px
|
||||||
|
|
||||||
|
</style>
|
48
src/client/app/admin/views/emoji.vue
Normal file
48
src/client/app/admin/views/emoji.vue
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%fa:plus% %i18n:@add-emoji.title%</div>
|
||||||
|
<section class="fit-top">
|
||||||
|
<ui-input v-model="name">
|
||||||
|
<span>%i18n:@add-emoji.name%</span>
|
||||||
|
<span slot="text">%i18n:@add-emoji.name-desc%</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-input v-model="aliases">
|
||||||
|
<span>%i18n:@add-emoji.aliases%</span>
|
||||||
|
<span slot="text">%i18n:@add-emoji.aliases-desc%</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-input v-model="url">
|
||||||
|
<span>%i18n:@add-emoji.url%</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-button @click="add">%i18n:@add-emoji.add%</ui-button>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
url: '',
|
||||||
|
aliases: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add() {
|
||||||
|
(this as any).api('admin/add-emoji', {
|
||||||
|
name: this.name,
|
||||||
|
url: this.url,
|
||||||
|
aliases: this.aliases.split(' ')
|
||||||
|
}).then(() => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Added` });
|
||||||
|
}).catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed ${e}` });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,8 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="jdnqwkzlnxcfftthoybjxrebyolvoucw mk-admin-card">
|
<div>
|
||||||
<header>%i18n:@hided-tags%</header>
|
<ui-card>
|
||||||
<textarea v-model="hidedTags"></textarea>
|
<div slot="title">%i18n:@hided-tags%</div>
|
||||||
<button class="ui" @click="save">%i18n:@save%</button>
|
<section>
|
||||||
|
<textarea class="jdnqwkzlnxcfftthoybjxrebyolvoucw" v-model="hidedTags"></textarea>
|
||||||
|
<ui-button @click="save">%i18n:@save%</ui-button>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -35,11 +39,8 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
|
||||||
|
|
||||||
.jdnqwkzlnxcfftthoybjxrebyolvoucw
|
.jdnqwkzlnxcfftthoybjxrebyolvoucw
|
||||||
textarea
|
width 100%
|
||||||
width 100%
|
min-height 300px
|
||||||
min-height 300px
|
|
||||||
|
|
||||||
</style>
|
</style>
|
101
src/client/app/admin/views/index.vue
Normal file
101
src/client/app/admin/views/index.vue
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<template>
|
||||||
|
<div class="mk-admin">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:home .fw%%i18n:@dashboard%</li>
|
||||||
|
<li @click="nav('instance')" :class="{ active: page == 'instance' }">%fa:cog .fw%%i18n:@instance%</li>
|
||||||
|
<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
|
||||||
|
<li @click="nav('emoji')" :class="{ active: page == 'emoji' }">%fa:grin R .fw%%i18n:@emoji%</li>
|
||||||
|
<li @click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li>
|
||||||
|
<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }">%fa:hashtag .fw%%i18n:@hashtags%</li>
|
||||||
|
|
||||||
|
<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:common.drive%</li> -->
|
||||||
|
<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<div v-show="page == 'dashboard'"><x-dashboard/></div>
|
||||||
|
<div v-show="page == 'instance'"><x-instance/></div>
|
||||||
|
<div v-if="page == 'users'"><x-users/></div>
|
||||||
|
<div v-show="page == 'emoji'"><x-emoji/></div>
|
||||||
|
<div v-show="page == 'announcements'"><x-announcements/></div>
|
||||||
|
<div v-show="page == 'hashtags'"><x-hashtags/></div>
|
||||||
|
<div v-if="page == 'drive'"></div>
|
||||||
|
<div v-if="page == 'update'"></div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import XDashboard from "./dashboard.vue";
|
||||||
|
import XInstance from "./instance.vue";
|
||||||
|
import XEmoji from "./emoji.vue";
|
||||||
|
import XAnnouncements from "./announcements.vue";
|
||||||
|
import XHashtags from "./hashtags.vue";
|
||||||
|
import XUsers from "./users.vue";
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
XDashboard,
|
||||||
|
XInstance,
|
||||||
|
XEmoji,
|
||||||
|
XAnnouncements,
|
||||||
|
XHashtags,
|
||||||
|
XUsers
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: 'dashboard'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
nav(page: string) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus">
|
||||||
|
.mk-admin
|
||||||
|
display flex
|
||||||
|
height 100%
|
||||||
|
|
||||||
|
> nav
|
||||||
|
position fixed
|
||||||
|
z-index 10000
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
width 250px
|
||||||
|
height 100vh
|
||||||
|
padding 16px 0 0 0
|
||||||
|
overflow auto
|
||||||
|
background #333
|
||||||
|
color #fff
|
||||||
|
|
||||||
|
> ul
|
||||||
|
margin 0
|
||||||
|
padding 0
|
||||||
|
list-style none
|
||||||
|
|
||||||
|
> li
|
||||||
|
display block
|
||||||
|
padding 10px 16px
|
||||||
|
margin 0
|
||||||
|
cursor pointer
|
||||||
|
user-select none
|
||||||
|
transition margin-left 0.2s ease
|
||||||
|
|
||||||
|
> [data-fa]
|
||||||
|
margin-right 4px
|
||||||
|
|
||||||
|
&.active
|
||||||
|
margin-left 8px
|
||||||
|
color var(--primary) !important
|
||||||
|
|
||||||
|
> main
|
||||||
|
width 100%
|
||||||
|
padding 32px 32px 32px calc(32px + 250px)
|
||||||
|
|
||||||
|
</style>
|
62
src/client/app/admin/views/instance.vue
Normal file
62
src/client/app/admin/views/instance.vue
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%i18n:@banner-url%</div>
|
||||||
|
<section class="fit-top">
|
||||||
|
<ui-input v-model="bannerUrl"/>
|
||||||
|
<ui-button @click="updateMeta">%i18n:@save%</ui-button>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%i18n:@disable-registration%</div>
|
||||||
|
<section>
|
||||||
|
<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
|
||||||
|
<button class="ui" @click="invite">%i18n:@invite%</button>
|
||||||
|
<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%i18n:@disable-local-timeline%</div>
|
||||||
|
<section>
|
||||||
|
<input type="checkbox" v-model="disableLocalTimeline" @change="updateMeta">
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
disableRegistration: false,
|
||||||
|
disableLocalTimeline: false,
|
||||||
|
bannerUrl: null,
|
||||||
|
inviteCode: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
invite() {
|
||||||
|
(this as any).api('admin/invite').then(x => {
|
||||||
|
this.inviteCode = x.code;
|
||||||
|
}).catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed ${e}` });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateMeta() {
|
||||||
|
(this as any).api('admin/update-meta', {
|
||||||
|
disableRegistration: this.disableRegistration,
|
||||||
|
disableLocalTimeline: this.disableLocalTimeline,
|
||||||
|
bannerUrl: this.bannerUrl
|
||||||
|
}).then(() => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Saved` });
|
||||||
|
}).catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed ${e}` });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
129
src/client/app/admin/views/users.vue
Normal file
129
src/client/app/admin/views/users.vue
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%i18n:@verify-user%</div>
|
||||||
|
<section class="fit-top">
|
||||||
|
<ui-input v-model="verifyUsername" type="text">
|
||||||
|
<span slot="prefix">@</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-button @click="verifyUser" :disabled="verifying">%i18n:@verify%</ui-button>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%i18n:@unverify-user%</div>
|
||||||
|
<section class="fit-top">
|
||||||
|
<ui-input v-model="unverifyUsername" type="text">
|
||||||
|
<span slot="prefix">@</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-button @click="unverifyUser" :disabled="unverifying">%i18n:@unverify%</ui-button>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%i18n:@suspend-user%</div>
|
||||||
|
<section class="fit-top">
|
||||||
|
<ui-input v-model="suspendUsername" type="text">
|
||||||
|
<span slot="prefix">@</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-button @click="suspendUser" :disabled="suspending">%i18n:@suspend%</ui-button>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%i18n:@unsuspend-user%</div>
|
||||||
|
<section class="fit-top">
|
||||||
|
<ui-input v-model="unsuspendUsername" type="text">
|
||||||
|
<span slot="prefix">@</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-button @click="unsuspendUser" :disabled="unsuspending">%i18n:@unsuspend%</ui-button>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import parseAcct from "../../../../misc/acct/parse";
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
verifyUsername: null,
|
||||||
|
verifying: false,
|
||||||
|
unverifyUsername: null,
|
||||||
|
unverifying: false,
|
||||||
|
suspendUsername: null,
|
||||||
|
suspending: false,
|
||||||
|
unsuspendUsername: null,
|
||||||
|
unsuspending: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async verifyUser() {
|
||||||
|
this.verifying = true;
|
||||||
|
|
||||||
|
const process = async () => {
|
||||||
|
const user = await (this as any).os.api('users/show', parseAcct(this.verifyUsername));
|
||||||
|
await (this as any).os.api('admin/verify-user', { userId: user.id });
|
||||||
|
(this as any).os.apis.dialog({ text: '%i18n:@verified%' });
|
||||||
|
};
|
||||||
|
|
||||||
|
await process().catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.verifying = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async unverifyUser() {
|
||||||
|
this.unverifying = true;
|
||||||
|
|
||||||
|
const process = async () => {
|
||||||
|
const user = await (this as any).os.api('users/show', parseAcct(this.unverifyUsername));
|
||||||
|
await (this as any).os.api('admin/unverify-user', { userId: user.id });
|
||||||
|
(this as any).os.apis.dialog({ text: '%i18n:@unverified%' });
|
||||||
|
};
|
||||||
|
|
||||||
|
await process().catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.unverifying = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async suspendUser() {
|
||||||
|
this.suspending = true;
|
||||||
|
|
||||||
|
const process = async () => {
|
||||||
|
const user = await (this as any).os.api('users/show', parseAcct(this.suspendUsername));
|
||||||
|
await (this as any).os.api('admin/suspend-user', { userId: user.id });
|
||||||
|
(this as any).os.apis.dialog({ text: '%i18n:@suspended%' });
|
||||||
|
};
|
||||||
|
|
||||||
|
await process().catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.suspending = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async unsuspendUser() {
|
||||||
|
this.unsuspending = true;
|
||||||
|
|
||||||
|
const process = async () => {
|
||||||
|
const user = await (this as any).os.api('users/show', parseAcct(this.unsuspendUsername));
|
||||||
|
await (this as any).os.api('admin/unsuspend-user', { userId: user.id });
|
||||||
|
(this as any).os.apis.dialog({ text: '%i18n:@unsuspended%' });
|
||||||
|
};
|
||||||
|
|
||||||
|
await process().catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.unsuspending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -46,6 +46,7 @@
|
||||||
if (`${url.pathname}/`.startsWith('/docs/')) app = 'docs';
|
if (`${url.pathname}/`.startsWith('/docs/')) app = 'docs';
|
||||||
if (`${url.pathname}/`.startsWith('/dev/')) app = 'dev';
|
if (`${url.pathname}/`.startsWith('/dev/')) app = 'dev';
|
||||||
if (`${url.pathname}/`.startsWith('/auth/')) app = 'auth';
|
if (`${url.pathname}/`.startsWith('/auth/')) app = 'auth';
|
||||||
|
if (`${url.pathname}/`.startsWith('/admin/')) app = 'admin';
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Detect the user language
|
//#region Detect the user language
|
||||||
|
|
|
@ -23,7 +23,6 @@ import updateBanner from './api/update-banner';
|
||||||
import MkIndex from './views/pages/index.vue';
|
import MkIndex from './views/pages/index.vue';
|
||||||
import MkHome from './views/pages/home.vue';
|
import MkHome from './views/pages/home.vue';
|
||||||
import MkDeck from './views/pages/deck/deck.vue';
|
import MkDeck from './views/pages/deck/deck.vue';
|
||||||
import MkAdmin from './views/pages/admin/admin.vue';
|
|
||||||
import MkStats from './views/pages/stats/stats.vue';
|
import MkStats from './views/pages/stats/stats.vue';
|
||||||
import MkUser from './views/pages/user/user.vue';
|
import MkUser from './views/pages/user/user.vue';
|
||||||
import MkFavorites from './views/pages/favorites.vue';
|
import MkFavorites from './views/pages/favorites.vue';
|
||||||
|
@ -57,7 +56,6 @@ init(async (launch) => {
|
||||||
{ path: '/', name: 'index', component: MkIndex },
|
{ path: '/', name: 'index', component: MkIndex },
|
||||||
{ path: '/home', name: 'home', component: MkHome },
|
{ path: '/home', name: 'home', component: MkHome },
|
||||||
{ path: '/deck', name: 'deck', component: MkDeck },
|
{ path: '/deck', name: 'deck', component: MkDeck },
|
||||||
{ path: '/admin', name: 'admin', component: MkAdmin },
|
|
||||||
{ path: '/stats', name: 'stats', component: MkStats },
|
{ path: '/stats', name: 'stats', component: MkStats },
|
||||||
{ path: '/i/customize-home', component: MkHomeCustomize },
|
{ path: '/i/customize-home', component: MkHomeCustomize },
|
||||||
{ path: '/i/favorites', component: MkFavorites },
|
{ path: '/i/favorites', component: MkFavorites },
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="obdskegsannmntldydackcpzezagxqfy mk-admin-card">
|
|
||||||
<header>%i18n:@dashboard%</header>
|
|
||||||
|
|
||||||
<div v-if="stats" class="stats">
|
|
||||||
<div><b>%fa:user% {{ stats.originalUsersCount | number }}</b><span>%i18n:@original-users%</span></div>
|
|
||||||
<div><span>%fa:user% {{ stats.usersCount | number }}</span><span>%i18n:@all-users%</span></div>
|
|
||||||
<div><b>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</b><span>%i18n:@original-notes%</span></div>
|
|
||||||
<div><span>%fa:pencil-alt% {{ stats.notesCount | number }}</span><span>%i18n:@all-notes%</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cpu-memory">
|
|
||||||
<x-cpu-memory :connection="connection"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="this.$store.state.i && this.$store.state.i.isAdmin" class="form">
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
<p>%i18n:@banner-url%</p>
|
|
||||||
<input v-model="bannerUrl">
|
|
||||||
</label>
|
|
||||||
<button class="ui" @click="updateMeta">%i18n:@save%</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
|
|
||||||
<span>%i18n:@disableRegistration%</span>
|
|
||||||
</label>
|
|
||||||
<button class="ui" @click="invite">%i18n:@invite%</button>
|
|
||||||
<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" v-model="disableLocalTimeline" @change="updateMeta">
|
|
||||||
<span>%i18n:@disableLocalTimeline%</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from "vue";
|
|
||||||
import XCpuMemory from "./admin.cpu-memory.vue";
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
components: {
|
|
||||||
XCpuMemory
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
stats: null,
|
|
||||||
disableRegistration: false,
|
|
||||||
disableLocalTimeline: false,
|
|
||||||
bannerUrl: null,
|
|
||||||
inviteCode: null,
|
|
||||||
connection: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.connection = (this as any).os.stream.useSharedConnection('serverStats');
|
|
||||||
|
|
||||||
(this as any).os.getMeta().then(meta => {
|
|
||||||
this.disableRegistration = meta.disableRegistration;
|
|
||||||
this.disableLocalTimeline = meta.disableLocalTimeline;
|
|
||||||
this.bannerUrl = meta.bannerUrl;
|
|
||||||
});
|
|
||||||
|
|
||||||
(this as any).api('stats').then(stats => {
|
|
||||||
this.stats = stats;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
invite() {
|
|
||||||
(this as any).api('admin/invite').then(x => {
|
|
||||||
this.inviteCode = x.code;
|
|
||||||
}).catch(e => {
|
|
||||||
(this as any).os.apis.dialog({ text: `Failed ${e}` });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
updateMeta() {
|
|
||||||
(this as any).api('admin/update-meta', {
|
|
||||||
disableRegistration: this.disableRegistration,
|
|
||||||
disableLocalTimeline: this.disableLocalTimeline,
|
|
||||||
bannerUrl: this.bannerUrl
|
|
||||||
}).then(() => {
|
|
||||||
(this as any).os.apis.dialog({ text: `Saved` });
|
|
||||||
}).catch(e => {
|
|
||||||
(this as any).os.apis.dialog({ text: `Failed ${e}` });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
|
|
||||||
|
|
||||||
.obdskegsannmntldydackcpzezagxqfy
|
|
||||||
> .stats
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
margin-bottom 16px
|
|
||||||
padding 16px
|
|
||||||
border solid 1px #eee
|
|
||||||
border-radius 8px
|
|
||||||
|
|
||||||
> div
|
|
||||||
flex 1
|
|
||||||
text-align center
|
|
||||||
|
|
||||||
> *:first-child
|
|
||||||
display block
|
|
||||||
color var(--primary)
|
|
||||||
|
|
||||||
> *:last-child
|
|
||||||
font-size 70%
|
|
||||||
|
|
||||||
> .cpu-memory
|
|
||||||
margin-bottom 16px
|
|
||||||
padding 16px
|
|
||||||
border solid 1px #eee
|
|
||||||
border-radius: 8px
|
|
||||||
|
|
||||||
> .form
|
|
||||||
> div
|
|
||||||
padding 16px
|
|
||||||
border-bottom solid 1px #eee
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,57 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mk-admin-card">
|
|
||||||
<header>%i18n:@suspend-user%</header>
|
|
||||||
<input v-model="username" type="text" class="ui"/>
|
|
||||||
<button class="ui" @click="suspendUser" :disabled="suspending">%i18n:@suspend%</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from "vue";
|
|
||||||
import parseAcct from "../../../../../../misc/acct/parse";
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
username: null,
|
|
||||||
suspending: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async suspendUser() {
|
|
||||||
this.suspending = true;
|
|
||||||
|
|
||||||
const process = async () => {
|
|
||||||
const user = await (this as any).os.api(
|
|
||||||
"users/show",
|
|
||||||
parseAcct(this.username)
|
|
||||||
);
|
|
||||||
|
|
||||||
await (this as any).os.api("admin/suspend-user", {
|
|
||||||
userId: user.id
|
|
||||||
});
|
|
||||||
|
|
||||||
(this as any).os.apis.dialog({ text: "%i18n:@suspended%" });
|
|
||||||
};
|
|
||||||
|
|
||||||
await process().catch(e => {
|
|
||||||
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.suspending = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
|
|
||||||
|
|
||||||
header
|
|
||||||
margin 10px 0
|
|
||||||
|
|
||||||
|
|
||||||
button
|
|
||||||
margin 16px 0
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,58 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mk-admin-card">
|
|
||||||
<header>%i18n:@unsuspend-user%</header>
|
|
||||||
<input v-model="username" type="text" class="ui"/>
|
|
||||||
<button class="ui" @click="unsuspendUser" :disabled="unsuspending">%i18n:@unsuspend%</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from "vue";
|
|
||||||
import parseAcct from "../../../../../../misc/acct/parse";
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
username: null,
|
|
||||||
unsuspending: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async unsuspendUser() {
|
|
||||||
this.unsuspending = true;
|
|
||||||
|
|
||||||
const process = async () => {
|
|
||||||
const user = await (this as any).os.api(
|
|
||||||
"users/show",
|
|
||||||
parseAcct(this.username)
|
|
||||||
);
|
|
||||||
|
|
||||||
await (this as any).os.api("admin/unsuspend-user", {
|
|
||||||
userId: user.id
|
|
||||||
});
|
|
||||||
|
|
||||||
(this as any).os.apis.dialog({ text: "%i18n:@unsuspended%" });
|
|
||||||
};
|
|
||||||
|
|
||||||
await process().catch(e => {
|
|
||||||
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.unsuspending = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
|
|
||||||
|
|
||||||
header
|
|
||||||
margin 10px 0
|
|
||||||
|
|
||||||
|
|
||||||
button
|
|
||||||
margin 16px 0
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,57 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mk-admin-card">
|
|
||||||
<header>%i18n:@unverify-user%</header>
|
|
||||||
<input v-model="username" type="text" class="ui"/>
|
|
||||||
<button class="ui" @click="unverifyUser" :disabled="unverifying">%i18n:@unverify%</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from "vue";
|
|
||||||
import parseAcct from "../../../../../../misc/acct/parse";
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
username: null,
|
|
||||||
unverifying: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async unverifyUser() {
|
|
||||||
this.unverifying = true;
|
|
||||||
|
|
||||||
const process = async () => {
|
|
||||||
const user = await (this as any).os.api(
|
|
||||||
"users/show",
|
|
||||||
parseAcct(this.username)
|
|
||||||
);
|
|
||||||
|
|
||||||
await (this as any).os.api("admin/unverify-user", {
|
|
||||||
userId: user.id
|
|
||||||
});
|
|
||||||
|
|
||||||
(this as any).os.apis.dialog({ text: "%i18n:@unverified%" });
|
|
||||||
};
|
|
||||||
|
|
||||||
await process().catch(e => {
|
|
||||||
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.unverifying = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
|
|
||||||
|
|
||||||
header
|
|
||||||
margin 10px 0
|
|
||||||
|
|
||||||
|
|
||||||
button
|
|
||||||
margin 16px 0
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,57 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mk-admin-card">
|
|
||||||
<header>%i18n:@verify-user%</header>
|
|
||||||
<input v-model="username" type="text" class="ui"/>
|
|
||||||
<button class="ui" @click="verifyUser" :disabled="verifying">%i18n:@verify%</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from "vue";
|
|
||||||
import parseAcct from "../../../../../../misc/acct/parse";
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
username: null,
|
|
||||||
verifying: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async verifyUser() {
|
|
||||||
this.verifying = true;
|
|
||||||
|
|
||||||
const process = async () => {
|
|
||||||
const user = await (this as any).os.api(
|
|
||||||
"users/show",
|
|
||||||
parseAcct(this.username)
|
|
||||||
);
|
|
||||||
|
|
||||||
await (this as any).os.api("admin/verify-user", {
|
|
||||||
userId: user.id
|
|
||||||
});
|
|
||||||
|
|
||||||
(this as any).os.apis.dialog({ text: "%i18n:@verified%" });
|
|
||||||
};
|
|
||||||
|
|
||||||
await process().catch(e => {
|
|
||||||
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.verifying = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
|
|
||||||
|
|
||||||
header
|
|
||||||
margin 10px 0
|
|
||||||
|
|
||||||
|
|
||||||
button
|
|
||||||
margin 16px 0
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,140 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="mk-admin">
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
|
|
||||||
|
|
||||||
<li v-if="this.$store.state.i && this.$store.state.i.isAdmin"
|
|
||||||
@click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
|
|
||||||
|
|
||||||
<li v-if="this.$store.state.i && this.$store.state.i.isAdmin"
|
|
||||||
@click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li>
|
|
||||||
|
|
||||||
<li v-if="this.$store.state.i && this.$store.state.i.isAdmin"
|
|
||||||
@click="nav('hashtags')" :class="{ active: page == 'hashtags' }">%fa:hashtag .fw%%i18n:@hashtags%</li>
|
|
||||||
|
|
||||||
<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:common.drive%</li> -->
|
|
||||||
<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<main>
|
|
||||||
<div v-show="page == 'dashboard'">
|
|
||||||
<x-dashboard/>
|
|
||||||
<x-charts/>
|
|
||||||
</div>
|
|
||||||
<div v-show="page == 'announcements'">
|
|
||||||
<x-announcements/>
|
|
||||||
</div>
|
|
||||||
<div v-show="page == 'hashtags'">
|
|
||||||
<x-hashtags/>
|
|
||||||
</div>
|
|
||||||
<div v-if="page == 'users'">
|
|
||||||
<x-suspend-user/>
|
|
||||||
<x-unsuspend-user/>
|
|
||||||
<x-verify-user/>
|
|
||||||
<x-unverify-user/>
|
|
||||||
</div>
|
|
||||||
<div v-if="page == 'drive'"></div>
|
|
||||||
<div v-if="page == 'update'"></div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from "vue";
|
|
||||||
import XDashboard from "./admin.dashboard.vue";
|
|
||||||
import XAnnouncements from "./admin.announcements.vue";
|
|
||||||
import XHashtags from "./admin.hashtags.vue";
|
|
||||||
import XSuspendUser from "./admin.suspend-user.vue";
|
|
||||||
import XUnsuspendUser from "./admin.unsuspend-user.vue";
|
|
||||||
import XVerifyUser from "./admin.verify-user.vue";
|
|
||||||
import XUnverifyUser from "./admin.unverify-user.vue";
|
|
||||||
import XCharts from "../../components/charts.vue";
|
|
||||||
|
|
||||||
export default Vue.extend({
|
|
||||||
components: {
|
|
||||||
XDashboard,
|
|
||||||
XAnnouncements,
|
|
||||||
XHashtags,
|
|
||||||
XSuspendUser,
|
|
||||||
XUnsuspendUser,
|
|
||||||
XVerifyUser,
|
|
||||||
XUnverifyUser,
|
|
||||||
XCharts
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
page: 'dashboard'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
nav(page: string) {
|
|
||||||
this.page = page;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus">
|
|
||||||
|
|
||||||
|
|
||||||
.mk-admin
|
|
||||||
display flex
|
|
||||||
height 100%
|
|
||||||
margin 32px
|
|
||||||
|
|
||||||
> nav
|
|
||||||
flex 0 0 250px
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
padding 16px 0 0 0
|
|
||||||
overflow auto
|
|
||||||
border-right solid 1px #ddd
|
|
||||||
|
|
||||||
> ul
|
|
||||||
list-style none
|
|
||||||
|
|
||||||
> li
|
|
||||||
display block
|
|
||||||
padding 10px 16px
|
|
||||||
margin 0
|
|
||||||
color #666
|
|
||||||
cursor pointer
|
|
||||||
user-select none
|
|
||||||
transition margin-left 0.2s ease
|
|
||||||
|
|
||||||
> [data-fa]
|
|
||||||
margin-right 4px
|
|
||||||
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color #555
|
|
||||||
|
|
||||||
&.active
|
|
||||||
margin-left 8px
|
|
||||||
color var(--primary) !important
|
|
||||||
|
|
||||||
> main
|
|
||||||
width 100%
|
|
||||||
padding 16px 32px
|
|
||||||
|
|
||||||
> div
|
|
||||||
> div
|
|
||||||
max-width 800px
|
|
||||||
|
|
||||||
.mk-admin-card
|
|
||||||
padding 32px
|
|
||||||
background #fff
|
|
||||||
box-shadow 0 2px 8px rgba(#000, 0.1)
|
|
||||||
|
|
||||||
&:not(:last-child)
|
|
||||||
margin-bottom 16px
|
|
||||||
|
|
||||||
> header
|
|
||||||
margin 0 0 1em 0
|
|
||||||
padding 0 0 8px 0
|
|
||||||
font-size 1em
|
|
||||||
color #555
|
|
||||||
border-bottom solid 1px #eee
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -57,6 +57,7 @@ const entry = {
|
||||||
mobile: './src/client/app/mobile/script.ts',
|
mobile: './src/client/app/mobile/script.ts',
|
||||||
dev: './src/client/app/dev/script.ts',
|
dev: './src/client/app/dev/script.ts',
|
||||||
auth: './src/client/app/auth/script.ts',
|
auth: './src/client/app/auth/script.ts',
|
||||||
|
admin: './src/client/app/admin/script.ts',
|
||||||
sw: './src/client/app/sw.js'
|
sw: './src/client/app/sw.js'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue