[Client] Admin page improved
This commit is contained in:
		
							parent
							
								
									819b535ab0
								
							
						
					
					
						commit
						f2e719b361
					
				
					 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,7 +49,6 @@ 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										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,10 +39,7 @@ 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue