[Client] Admin page improved
This commit is contained in:
		@@ -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>
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -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,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>
 | 
			
		||||
@@ -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>
 | 
			
		||||
		Reference in New Issue
	
	Block a user