Implement admin drive page

This commit is contained in:
syuilo 2018-12-14 19:09:11 +09:00
parent 8bd17703c3
commit 796237b3c6
No known key found for this signature in database
GPG Key ID: BDC4C49D06AB9D69
4 changed files with 231 additions and 4 deletions

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

View File

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

View File

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

View 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 }));
}));