Implement admin drive page
This commit is contained in:
		
							parent
							
								
									8bd17703c3
								
							
						
					
					
						commit
						796237b3c6
					
				
					 4 changed files with 231 additions and 4 deletions
				
			
		
							
								
								
									
										139
									
								
								src/client/app/admin/views/drive.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/client/app/admin/views/drive.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,139 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="pwnqwyet">
 | 
				
			||||||
 | 
						<ui-card>
 | 
				
			||||||
 | 
							<div slot="title"><fa :icon="faCloud"/> {{ $t('@.drive') }}</div>
 | 
				
			||||||
 | 
							<section class="fit-top">
 | 
				
			||||||
 | 
								<ui-horizon-group inputs>
 | 
				
			||||||
 | 
									<ui-select v-model="sort">
 | 
				
			||||||
 | 
										<span slot="label">{{ $t('sort.title') }}</span>
 | 
				
			||||||
 | 
										<option value="-createdAt">{{ $t('sort.createdAtAsc') }}</option>
 | 
				
			||||||
 | 
										<option value="+createdAt">{{ $t('sort.createdAtDesc') }}</option>
 | 
				
			||||||
 | 
										<option value="-size">{{ $t('sort.sizeAsc') }}</option>
 | 
				
			||||||
 | 
										<option value="+size">{{ $t('sort.sizeDesc') }}</option>
 | 
				
			||||||
 | 
									</ui-select>
 | 
				
			||||||
 | 
									<ui-select v-model="origin">
 | 
				
			||||||
 | 
										<span slot="label">{{ $t('origin.title') }}</span>
 | 
				
			||||||
 | 
										<option value="combined">{{ $t('origin.combined') }}</option>
 | 
				
			||||||
 | 
										<option value="local">{{ $t('origin.local') }}</option>
 | 
				
			||||||
 | 
										<option value="remote">{{ $t('origin.remote') }}</option>
 | 
				
			||||||
 | 
									</ui-select>
 | 
				
			||||||
 | 
								</ui-horizon-group>
 | 
				
			||||||
 | 
								<div class="kidvdlkg" v-for="file in files">
 | 
				
			||||||
 | 
									<div>
 | 
				
			||||||
 | 
										<div class="thumbnail" :style="thumbnail(file)"></div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
									<div>
 | 
				
			||||||
 | 
										<header>
 | 
				
			||||||
 | 
											<b>{{ file.name }}</b>
 | 
				
			||||||
 | 
											<span class="username">@{{ file.user | acct }}</span>
 | 
				
			||||||
 | 
										</header>
 | 
				
			||||||
 | 
										<div>
 | 
				
			||||||
 | 
											<div>
 | 
				
			||||||
 | 
												<span style="margin-right:16px;">{{ file.type }}</span>
 | 
				
			||||||
 | 
												<span>{{ file.datasize | bytes }}</span>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
											<div><mk-time :time="file.createdAt" mode="detail"/></div>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<ui-button v-if="existMore" @click="fetch">{{ $t('@.load-more') }}</ui-button>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
						</ui-card>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import i18n from '../../i18n';
 | 
				
			||||||
 | 
					import { faCloud } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n: i18n('admin/views/drive.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								sort: '+createdAt',
 | 
				
			||||||
 | 
								origin: 'combined',
 | 
				
			||||||
 | 
								limit: 10,
 | 
				
			||||||
 | 
								offset: 0,
 | 
				
			||||||
 | 
								files: [],
 | 
				
			||||||
 | 
								existMore: false,
 | 
				
			||||||
 | 
								faCloud
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						watch: {
 | 
				
			||||||
 | 
							sort() {
 | 
				
			||||||
 | 
								this.files = [];
 | 
				
			||||||
 | 
								this.offset = 0;
 | 
				
			||||||
 | 
								this.fetch();
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							origin() {
 | 
				
			||||||
 | 
								this.files = [];
 | 
				
			||||||
 | 
								this.offset = 0;
 | 
				
			||||||
 | 
								this.fetch();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
							this.fetch();
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							fetch() {
 | 
				
			||||||
 | 
								this.$root.api('admin/drive/files', {
 | 
				
			||||||
 | 
									origin: this.origin,
 | 
				
			||||||
 | 
									sort: this.sort,
 | 
				
			||||||
 | 
									offset: this.offset,
 | 
				
			||||||
 | 
									limit: this.limit + 1
 | 
				
			||||||
 | 
								}).then(files => {
 | 
				
			||||||
 | 
									if (files.length == this.limit + 1) {
 | 
				
			||||||
 | 
										files.pop();
 | 
				
			||||||
 | 
										this.existMore = true;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										this.existMore = false;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									this.files = this.files.concat(files);
 | 
				
			||||||
 | 
									this.offset += this.limit;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							thumbnail(file: any): any {
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									'background-color': file.properties.avgColor && file.properties.avgColor.length == 3 ? `rgb(${file.properties.avgColor.join(',')})` : 'transparent',
 | 
				
			||||||
 | 
									'background-image': `url(${file.thumbnailUrl})`
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.pwnqwyet
 | 
				
			||||||
 | 
						@media (min-width 500px)
 | 
				
			||||||
 | 
							padding 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.kidvdlkg
 | 
				
			||||||
 | 
							display flex
 | 
				
			||||||
 | 
							padding 16px 0
 | 
				
			||||||
 | 
							border-top solid 1px var(--faceDivider)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> div:first-child
 | 
				
			||||||
 | 
								> .thumbnail
 | 
				
			||||||
 | 
									display block
 | 
				
			||||||
 | 
									width 64px
 | 
				
			||||||
 | 
									height 64px
 | 
				
			||||||
 | 
									background-size cover
 | 
				
			||||||
 | 
									background-position center center
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> div:last-child
 | 
				
			||||||
 | 
								flex 1
 | 
				
			||||||
 | 
								padding-left 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								> header
 | 
				
			||||||
 | 
									> .username
 | 
				
			||||||
 | 
										margin-left 8px
 | 
				
			||||||
 | 
										opacity 0.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -22,12 +22,11 @@
 | 
				
			||||||
			<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li>
 | 
								<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li>
 | 
				
			||||||
			<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li>
 | 
								<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li>
 | 
				
			||||||
			<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li>
 | 
								<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li>
 | 
				
			||||||
 | 
								<li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li>
 | 
				
			||||||
			<!-- <li @click="nav('federation')" :class="{ active: page == 'federation' }"><fa :icon="faShareAlt" fixed-width/>{{ $t('federation') }}</li> -->
 | 
								<!-- <li @click="nav('federation')" :class="{ active: page == 'federation' }"><fa :icon="faShareAlt" fixed-width/>{{ $t('federation') }}</li> -->
 | 
				
			||||||
			<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li>
 | 
								<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li>
 | 
				
			||||||
			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li>
 | 
								<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li>
 | 
				
			||||||
			<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }"><fa icon="hashtag" fixed-width/>{{ $t('hashtags') }}</li>
 | 
								<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }"><fa icon="hashtag" fixed-width/>{{ $t('hashtags') }}</li>
 | 
				
			||||||
 | 
					 | 
				
			||||||
			<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li> -->
 | 
					 | 
				
			||||||
		</ul>
 | 
							</ul>
 | 
				
			||||||
		<div class="back-to-misskey">
 | 
							<div class="back-to-misskey">
 | 
				
			||||||
			<a href="/"><fa :icon="faArrowLeft"/> {{ $t('back-to-misskey') }}</a>
 | 
								<a href="/"><fa :icon="faArrowLeft"/> {{ $t('back-to-misskey') }}</a>
 | 
				
			||||||
| 
						 | 
					@ -45,7 +44,7 @@
 | 
				
			||||||
			<div v-if="page == 'emoji'"><x-emoji/></div>
 | 
								<div v-if="page == 'emoji'"><x-emoji/></div>
 | 
				
			||||||
			<div v-if="page == 'announcements'"><x-announcements/></div>
 | 
								<div v-if="page == 'announcements'"><x-announcements/></div>
 | 
				
			||||||
			<div v-if="page == 'hashtags'"><x-hashtags/></div>
 | 
								<div v-if="page == 'hashtags'"><x-hashtags/></div>
 | 
				
			||||||
			<div v-if="page == 'drive'"></div>
 | 
								<div v-if="page == 'drive'"><x-drive/></div>
 | 
				
			||||||
			<div v-if="page == 'update'"></div>
 | 
								<div v-if="page == 'update'"></div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</main>
 | 
						</main>
 | 
				
			||||||
| 
						 | 
					@ -63,6 +62,7 @@ import XEmoji from "./emoji.vue";
 | 
				
			||||||
import XAnnouncements from "./announcements.vue";
 | 
					import XAnnouncements from "./announcements.vue";
 | 
				
			||||||
import XHashtags from "./hashtags.vue";
 | 
					import XHashtags from "./hashtags.vue";
 | 
				
			||||||
import XUsers from "./users.vue";
 | 
					import XUsers from "./users.vue";
 | 
				
			||||||
 | 
					import XDrive from "./drive.vue";
 | 
				
			||||||
import { faHeadset, faArrowLeft, faShareAlt } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faHeadset, faArrowLeft, faShareAlt } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faGrin } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faGrin } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,7 +79,8 @@ export default Vue.extend({
 | 
				
			||||||
		XEmoji,
 | 
							XEmoji,
 | 
				
			||||||
		XAnnouncements,
 | 
							XAnnouncements,
 | 
				
			||||||
		XHashtags,
 | 
							XHashtags,
 | 
				
			||||||
		XUsers
 | 
							XUsers,
 | 
				
			||||||
 | 
							XDrive,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	provide: {
 | 
						provide: {
 | 
				
			||||||
		isMobile
 | 
							isMobile
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
import * as mongo from 'mongodb';
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
const deepcopy = require('deepcopy');
 | 
					const deepcopy = require('deepcopy');
 | 
				
			||||||
import { pack as packFolder } from './drive-folder';
 | 
					import { pack as packFolder } from './drive-folder';
 | 
				
			||||||
 | 
					import { pack as packUser } from './user';
 | 
				
			||||||
import monkDb, { nativeDbConn } from '../db/mongodb';
 | 
					import monkDb, { nativeDbConn } from '../db/mongodb';
 | 
				
			||||||
import isObjectId from '../misc/is-objectid';
 | 
					import isObjectId from '../misc/is-objectid';
 | 
				
			||||||
import getDriveFileUrl, { getOriginalUrl } from '../misc/get-drive-file-url';
 | 
					import getDriveFileUrl, { getOriginalUrl } from '../misc/get-drive-file-url';
 | 
				
			||||||
| 
						 | 
					@ -131,6 +132,7 @@ export const packMany = (
 | 
				
			||||||
	options?: {
 | 
						options?: {
 | 
				
			||||||
		detail?: boolean
 | 
							detail?: boolean
 | 
				
			||||||
		self?: boolean,
 | 
							self?: boolean,
 | 
				
			||||||
 | 
							withUser?: boolean,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
) => {
 | 
					) => {
 | 
				
			||||||
	return Promise.all(files.map(f => pack(f, options)));
 | 
						return Promise.all(files.map(f => pack(f, options)));
 | 
				
			||||||
| 
						 | 
					@ -144,6 +146,7 @@ export const pack = (
 | 
				
			||||||
	options?: {
 | 
						options?: {
 | 
				
			||||||
		detail?: boolean,
 | 
							detail?: boolean,
 | 
				
			||||||
		self?: boolean,
 | 
							self?: boolean,
 | 
				
			||||||
 | 
							withUser?: boolean,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
) => new Promise<any>(async (resolve, reject) => {
 | 
					) => new Promise<any>(async (resolve, reject) => {
 | 
				
			||||||
	const opts = Object.assign({
 | 
						const opts = Object.assign({
 | 
				
			||||||
| 
						 | 
					@ -208,6 +211,11 @@ export const pack = (
 | 
				
			||||||
		*/
 | 
							*/
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (opts.withUser) {
 | 
				
			||||||
 | 
							// Populate user
 | 
				
			||||||
 | 
							_target.user = await packUser(_file.metadata.userId);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	delete _target.withoutChunks;
 | 
						delete _target.withoutChunks;
 | 
				
			||||||
	delete _target.storage;
 | 
						delete _target.storage;
 | 
				
			||||||
	delete _target.storageProps;
 | 
						delete _target.storageProps;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/server/api/endpoints/admin/drive/files.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/server/api/endpoints/admin/drive/files.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import File, { packMany } from '../../../../../models/drive-file';
 | 
				
			||||||
 | 
					import define from '../../../define';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						requireCredential: false,
 | 
				
			||||||
 | 
						requireModerator: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
							limit: {
 | 
				
			||||||
 | 
								validator: $.num.optional.range(1, 100),
 | 
				
			||||||
 | 
								default: 10
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							offset: {
 | 
				
			||||||
 | 
								validator: $.num.optional.min(0),
 | 
				
			||||||
 | 
								default: 0
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sort: {
 | 
				
			||||||
 | 
								validator: $.str.optional.or([
 | 
				
			||||||
 | 
									'+createdAt',
 | 
				
			||||||
 | 
									'-createdAt',
 | 
				
			||||||
 | 
									'+size',
 | 
				
			||||||
 | 
									'-size',
 | 
				
			||||||
 | 
								]),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							origin: {
 | 
				
			||||||
 | 
								validator: $.str.optional.or([
 | 
				
			||||||
 | 
									'combined',
 | 
				
			||||||
 | 
									'local',
 | 
				
			||||||
 | 
									'remote',
 | 
				
			||||||
 | 
								]),
 | 
				
			||||||
 | 
								default: 'local'
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, (ps, me) => new Promise(async (res, rej) => {
 | 
				
			||||||
 | 
						let _sort;
 | 
				
			||||||
 | 
						if (ps.sort) {
 | 
				
			||||||
 | 
							if (ps.sort == '+createdAt') {
 | 
				
			||||||
 | 
								_sort = {
 | 
				
			||||||
 | 
									uploadDate: -1
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							} else if (ps.sort == '-createdAt') {
 | 
				
			||||||
 | 
								_sort = {
 | 
				
			||||||
 | 
									uploadDate: 1
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							} else if (ps.sort == '+size') {
 | 
				
			||||||
 | 
								_sort = {
 | 
				
			||||||
 | 
									length: -1
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							} else if (ps.sort == '+size') {
 | 
				
			||||||
 | 
								_sort = {
 | 
				
			||||||
 | 
									length: -1
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							_sort = {
 | 
				
			||||||
 | 
								_id: -1
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const q =
 | 
				
			||||||
 | 
							ps.origin == 'local' ? { host: null } :
 | 
				
			||||||
 | 
							ps.origin == 'remote' ? { host: { $ne: null } } :
 | 
				
			||||||
 | 
							{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const files = await File
 | 
				
			||||||
 | 
							.find(q, {
 | 
				
			||||||
 | 
								limit: ps.limit,
 | 
				
			||||||
 | 
								sort: _sort,
 | 
				
			||||||
 | 
								skip: ps.offset
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res(await packMany(files, { detail: true, withUser: true }));
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue