[Client] Admin page improved

This commit is contained in:
syuilo
2018-11-02 23:05:53 +09:00
parent 819b535ab0
commit f2e719b361
20 changed files with 529 additions and 533 deletions

View File

@@ -1,52 +0,0 @@
<template>
<div class="qldxjjsrseehkusjuoooapmsprvfrxyl mk-admin-card">
<header>%i18n:@announcements%</header>
<textarea v-model="broadcasts" placeholder='[ { "title": "Title1", "text": "Text1" }, { "title": "Title2", "text": "Text2" } ]'></textarea>
<button class="ui" @click="save">%i18n:@save%</button>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
data() {
return {
broadcasts: '',
};
},
created() {
(this as any).os.getMeta().then(meta => {
this.broadcasts = JSON.stringify(meta.broadcasts, null, ' ');
});
},
methods: {
save() {
let json;
try {
json = JSON.parse(this.broadcasts);
} catch (e) {
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
return;
}
(this as any).api('admin/update-meta', {
broadcasts: json
}).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>
.qldxjjsrseehkusjuoooapmsprvfrxyl
textarea
width 100%
min-height 300px
</style>

View File

@@ -1,137 +0,0 @@
<template>
<div class="zyknedwtlthezamcjlolyusmipqmjgxz">
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<defs>
<linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
</linearGradient>
<mask :id="cpuMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
<polygon
:points="cpuPolygonPoints"
fill="#fff"
fill-opacity="0.5"/>
<polyline
:points="cpuPolylinePoints"
fill="none"
stroke="#fff"
stroke-width="1"/>
</mask>
</defs>
<rect
x="0" y="0"
:width="viewBoxX" :height="viewBoxY"
:style="`stroke: none; fill: url(#${ cpuGradientId }); mask: url(#${ cpuMaskId })`"/>
<text x="1" y="12">CPU <tspan>{{ cpuP }}%</tspan></text>
</svg>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<defs>
<linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
</linearGradient>
<mask :id="memMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
<polygon
:points="memPolygonPoints"
fill="#fff"
fill-opacity="0.5"/>
<polyline
:points="memPolylinePoints"
fill="none"
stroke="#fff"
stroke-width="1"/>
</mask>
</defs>
<rect
x="0" y="0"
:width="viewBoxX" :height="viewBoxY"
:style="`stroke: none; fill: url(#${ memGradientId }); mask: url(#${ memMaskId })`"/>
<text x="1" y="12">MEM <tspan>{{ memP }}%</tspan></text>
</svg>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import * as uuid from 'uuid';
export default Vue.extend({
props: ['connection'],
data() {
return {
viewBoxX: 200,
viewBoxY: 70,
stats: [],
cpuGradientId: uuid(),
cpuMaskId: uuid(),
memGradientId: uuid(),
memMaskId: uuid(),
cpuPolylinePoints: '',
memPolylinePoints: '',
cpuPolygonPoints: '',
memPolygonPoints: '',
cpuP: '',
memP: ''
};
},
mounted() {
this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog);
this.connection.send('requestLog', {
id: Math.random().toString().substr(2, 8),
length: 200
});
},
beforeDestroy() {
this.connection.off('stats', this.onStats);
this.connection.off('statsLog', this.onStatsLog);
},
methods: {
onStats(stats) {
this.stats.push(stats);
if (this.stats.length > 200) this.stats.shift();
const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu_usage) * this.viewBoxY]);
const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.used / s.mem.total)) * this.viewBoxY]);
this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.cpuPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.memPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
this.cpuP = (stats.cpu_usage * 100).toFixed(0);
this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
},
onStatsLog(statsLog) {
statsLog.reverse().forEach(stats => this.onStats(stats));
}
}
});
</script>
<style lang="stylus" scoped>
.zyknedwtlthezamcjlolyusmipqmjgxz
> svg
display block
width 50%
float left
&:first-child
padding-right 5px
&:last-child
padding-left 5px
> text
font-size 10px
fill var(--chartCaption)
> tspan
opacity 0.5
&:after
content ""
display block
clear both
</style>

View File

@@ -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>

View File

@@ -1,45 +0,0 @@
<template>
<div class="jdnqwkzlnxcfftthoybjxrebyolvoucw mk-admin-card">
<header>%i18n:@hided-tags%</header>
<textarea v-model="hidedTags"></textarea>
<button class="ui" @click="save">%i18n:@save%</button>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
data() {
return {
hidedTags: '',
};
},
created() {
(this as any).os.getMeta().then(meta => {
this.hidedTags = meta.hidedTags.join('\n');
});
},
methods: {
save() {
(this as any).api('admin/update-meta', {
hidedTags: this.hidedTags.split('\n')
}).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>
.jdnqwkzlnxcfftthoybjxrebyolvoucw
textarea
width 100%
min-height 300px
</style>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>