parent
11303b5bec
commit
d2a7c56149
|
@ -4,6 +4,7 @@ ChangeLog
|
||||||
unreleased
|
unreleased
|
||||||
----------
|
----------
|
||||||
* アクティブユーザー数のチャートを追加
|
* アクティブユーザー数のチャートを追加
|
||||||
|
* 管理画面でドライブのファイルをURLやIDから操作できるように
|
||||||
* ログイン時に二段階認証が分かりにくいのを改善
|
* ログイン時に二段階認証が分かりにくいのを改善
|
||||||
* 投稿のツールチップを出すのは時間の上だけに変更
|
* 投稿のツールチップを出すのは時間の上だけに変更
|
||||||
* ハッシュタグ判定の強化
|
* ハッシュタグ判定の強化
|
||||||
|
|
|
@ -1216,6 +1216,10 @@ admin/views/charts.vue:
|
||||||
network-usage: "通信量"
|
network-usage: "通信量"
|
||||||
|
|
||||||
admin/views/drive.vue:
|
admin/views/drive.vue:
|
||||||
|
operation: "操作"
|
||||||
|
fileid-or-url: "ファイルIDまたはファイルURL"
|
||||||
|
file-not-found: "ファイルが見つかりません"
|
||||||
|
lookup: "照会"
|
||||||
sort:
|
sort:
|
||||||
title: "ソート"
|
title: "ソート"
|
||||||
createdAtAsc: "アップロード日時が古い順"
|
createdAtAsc: "アップロード日時が古い順"
|
||||||
|
@ -1231,6 +1235,8 @@ admin/views/drive.vue:
|
||||||
deleted: "削除しました"
|
deleted: "削除しました"
|
||||||
mark-as-sensitive: "閲覧注意に設定"
|
mark-as-sensitive: "閲覧注意に設定"
|
||||||
unmark-as-sensitive: "閲覧注意を解除"
|
unmark-as-sensitive: "閲覧注意を解除"
|
||||||
|
marked-as-sensitive: "閲覧注意に設定しました"
|
||||||
|
unmarked-as-sensitive: "閲覧注意を解除しました"
|
||||||
|
|
||||||
admin/views/users.vue:
|
admin/views/users.vue:
|
||||||
operation: "操作"
|
operation: "操作"
|
||||||
|
|
|
@ -1,5 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="pwnqwyet">
|
<div class="pwnqwyet">
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title"><fa :icon="faTerminal"/> {{ $t('operation') }}</div>
|
||||||
|
<section class="fit-top">
|
||||||
|
<ui-input v-model="target" type="text">
|
||||||
|
<span>{{ $t('fileid-or-url') }}</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-horizon-group>
|
||||||
|
<ui-button @click="findAndToggleSensitive(true)"><fa :icon="faEyeSlash"/> {{ $t('mark-as-sensitive') }}</ui-button>
|
||||||
|
<ui-button @click="findAndToggleSensitive(false)"><fa :icon="faEye"/> {{ $t('unmark-as-sensitive') }}</ui-button>
|
||||||
|
</ui-horizon-group>
|
||||||
|
<ui-button @click="findAndDel()"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
|
||||||
|
<ui-button @click="show()"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button>
|
||||||
|
<ui-textarea v-if="file" :value="file | json5" readonly tall style="margin-top:16px;"></ui-textarea>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
|
||||||
<ui-card>
|
<ui-card>
|
||||||
<div slot="title"><fa :icon="faCloud"/> {{ $t('@.drive') }}</div>
|
<div slot="title"><fa :icon="faCloud"/> {{ $t('@.drive') }}</div>
|
||||||
<section class="fit-top">
|
<section class="fit-top">
|
||||||
|
@ -57,7 +73,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../i18n';
|
import i18n from '../../i18n';
|
||||||
import { faCloud } from '@fortawesome/free-solid-svg-icons';
|
import { faCloud, faTerminal, faSearch } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
|
import { faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
@ -65,13 +81,15 @@ export default Vue.extend({
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
file: null,
|
||||||
|
target: null,
|
||||||
sort: '+createdAt',
|
sort: '+createdAt',
|
||||||
origin: 'combined',
|
origin: 'combined',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
files: [],
|
files: [],
|
||||||
existMore: false,
|
existMore: false,
|
||||||
faCloud, faTrashAlt, faEye, faEyeSlash
|
faCloud, faTrashAlt, faEye, faEyeSlash, faTerminal, faSearch
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -94,6 +112,24 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
async fetchFile() {
|
||||||
|
try {
|
||||||
|
return await this.$root.api('drive/files/show', this.target.startsWith('http') ? { url: this.target } : { fileId: this.target });
|
||||||
|
} catch (e) {
|
||||||
|
if (e == 'file-not-found') {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: this.$t('file-not-found')
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: e.toString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
fetch() {
|
fetch() {
|
||||||
this.$root.api('admin/drive/files', {
|
this.$root.api('admin/drive/files', {
|
||||||
origin: this.origin,
|
origin: this.origin,
|
||||||
|
@ -147,6 +183,52 @@ export default Vue.extend({
|
||||||
|
|
||||||
file.isSensitive = !file.isSensitive;
|
file.isSensitive = !file.isSensitive;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async show() {
|
||||||
|
const file = await this.fetchFile();
|
||||||
|
this.$root.api('admin/drive/show-file', { fileId: file.id }).then(info => {
|
||||||
|
this.file = info;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async findAndToggleSensitive(sensitive) {
|
||||||
|
const process = async () => {
|
||||||
|
const file = await this.fetchFile();
|
||||||
|
await this.$root.api('drive/files/update', {
|
||||||
|
fileId: file.id,
|
||||||
|
isSensitive: sensitive
|
||||||
|
});
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'success',
|
||||||
|
text: sensitive ? this.$t('marked-as-sensitive') : this.$t('unmarked-as-sensitive')
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
await process().catch(e => {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: e.toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async findAndDel() {
|
||||||
|
const process = async () => {
|
||||||
|
const file = await this.fetchFile();
|
||||||
|
await this.$root.api('drive/files/delete', { fileId: file.id });
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'success',
|
||||||
|
text: this.$t('deleted')
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
await process().catch(e => {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: e.toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
28
src/server/api/endpoints/admin/drive/show-file.ts
Normal file
28
src/server/api/endpoints/admin/drive/show-file.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import $ from 'cafy';
|
||||||
|
import ID, { transform } from '../../../../../misc/cafy-id';
|
||||||
|
import define from '../../../define';
|
||||||
|
import DriveFile from '../../../../../models/drive-file';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
|
||||||
|
params: {
|
||||||
|
fileId: {
|
||||||
|
validator: $.type(ID),
|
||||||
|
transform: transform,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
|
||||||
|
const file = await DriveFile.findOne({
|
||||||
|
_id: ps.fileId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (file == null) {
|
||||||
|
return rej('file not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
res(file);
|
||||||
|
}));
|
|
@ -1,6 +1,9 @@
|
||||||
import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id';
|
import $ from 'cafy';
|
||||||
import DriveFile, { pack } from '../../../../../models/drive-file';
|
import * as mongo from 'mongodb';
|
||||||
|
import ID, { transform } from '../../../../../misc/cafy-id';
|
||||||
|
import DriveFile, { pack, IDriveFile } from '../../../../../models/drive-file';
|
||||||
import define from '../../../define';
|
import define from '../../../define';
|
||||||
|
import config from '../../../../../config';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
stability: 'stable',
|
stability: 'stable',
|
||||||
|
@ -16,24 +19,62 @@ export const meta = {
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
fileId: {
|
fileId: {
|
||||||
validator: $.type(ID),
|
validator: $.type(ID).optional,
|
||||||
transform: transform,
|
transform: transform,
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': '対象のファイルID',
|
'ja-JP': '対象のファイルID',
|
||||||
'en-US': 'Target file ID'
|
'en-US': 'Target file ID'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
url: {
|
||||||
|
validator: $.str.optional,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '対象のファイルのURL',
|
||||||
|
'en-US': 'Target file URL'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
||||||
// Fetch file
|
let file: IDriveFile;
|
||||||
const file = await DriveFile
|
|
||||||
.findOne({
|
if (ps.fileId) {
|
||||||
|
file = await DriveFile.findOne({
|
||||||
_id: ps.fileId,
|
_id: ps.fileId,
|
||||||
'metadata.userId': user._id,
|
|
||||||
'metadata.deletedAt': { $exists: false }
|
'metadata.deletedAt': { $exists: false }
|
||||||
});
|
});
|
||||||
|
} else if (ps.url) {
|
||||||
|
const isInternalStorageUrl = ps.url.startsWith(config.drive_url);
|
||||||
|
if (isInternalStorageUrl) {
|
||||||
|
// Extract file if from url
|
||||||
|
// e.g.
|
||||||
|
// http://misskey.local/files/foo?original=bar --> foo
|
||||||
|
const fileId = new mongo.ObjectID(ps.url.replace(config.drive_url, '').replace(/\?(.*)$/, '').replace(/\//g, ''));
|
||||||
|
file = await DriveFile.findOne({
|
||||||
|
_id: fileId,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
file = await DriveFile.findOne({
|
||||||
|
$or: [{
|
||||||
|
'metadata.url': ps.url
|
||||||
|
}, {
|
||||||
|
'metadata.webpublicUrl': ps.url
|
||||||
|
}, {
|
||||||
|
'metadata.thumbnailUrl': ps.url
|
||||||
|
}],
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return rej('fileId or url required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) {
|
||||||
|
return rej('access denied');
|
||||||
|
}
|
||||||
|
|
||||||
if (file === null) {
|
if (file === null) {
|
||||||
return rej('file-not-found');
|
return rej('file-not-found');
|
||||||
|
|
Loading…
Reference in a new issue