feat: Log user ips (#8872)
* wip * store ip and headers * Update admin-file.vue * require admin for view ip/headers * IP (recent) 消した * admin必須 * opt in * clean ips periodically * respect logging setting in drive/files/create
This commit is contained in:
@ -17,6 +17,7 @@ const accountData = localStorage.getItem('account');
|
||||
export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
|
||||
|
||||
export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator);
|
||||
export const iAmAdmin = $i != null && $i.isAdmin;
|
||||
|
||||
export async function signout() {
|
||||
waiting();
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer v-if="file" :content-max="500" :margin-min="16" :margin-max="32">
|
||||
<MkSpacer v-if="file" :content-max="600" :margin-min="16" :margin-max="32">
|
||||
<div v-if="tab === 'overview'" class="cxqhhsmd _formRoot">
|
||||
<a class="_formBlock thumbnail" :href="file.url" target="_blank">
|
||||
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
|
||||
@ -39,6 +39,20 @@
|
||||
<MkButton danger @click="del"><i class="fas fa-trash-alt"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="tab === 'ip' && info" class="_formRoot">
|
||||
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
|
||||
<MkKeyValue v-if="info.requestIp" class="_formBlock _monospace" :copy="info.requestIp" oneline>
|
||||
<template #key>IP</template>
|
||||
<template #value>{{ info.requestIp }}</template>
|
||||
</MkKeyValue>
|
||||
<FormSection v-if="info.requestHeaders">
|
||||
<template #label>Headers</template>
|
||||
<MkKeyValue v-for="(v, k) in info.requestHeaders" :key="k" class="_formBlock _monospace">
|
||||
<template #key>{{ k }}</template>
|
||||
<template #value>{{ v }}</template>
|
||||
</MkKeyValue>
|
||||
</FormSection>
|
||||
</div>
|
||||
<div v-else-if="tab === 'raw'" class="_formRoot">
|
||||
<MkObjectView v-if="info" tall :value="info">
|
||||
</MkObjectView>
|
||||
@ -54,13 +68,15 @@ import MkSwitch from '@/components/form/switch.vue';
|
||||
import MkObjectView from '@/components/object-view.vue';
|
||||
import MkDriveFileThumbnail from '@/components/drive-file-thumbnail.vue';
|
||||
import MkKeyValue from '@/components/key-value.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkUserCardMini from '@/components/user-card-mini.vue';
|
||||
import MkInfo from '@/components/ui/info.vue';
|
||||
import bytes from '@/filters/bytes';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { acct } from '@/filters/user';
|
||||
import { iAmAdmin, iAmModerator } from '@/account';
|
||||
|
||||
let tab = $ref('overview');
|
||||
let file: any = $ref(null);
|
||||
@ -108,7 +124,11 @@ const headerTabs = $computed(() => [{
|
||||
key: 'overview',
|
||||
title: i18n.ts.overview,
|
||||
icon: 'fas fa-info-circle',
|
||||
}, {
|
||||
}, iAmModerator ? {
|
||||
key: 'ip',
|
||||
title: 'IP',
|
||||
icon: 'fas fa-bars-staggered',
|
||||
} : null, {
|
||||
key: 'raw',
|
||||
title: 'Raw data',
|
||||
icon: 'fas fa-code',
|
||||
|
@ -14,6 +14,18 @@
|
||||
<XBotProtection/>
|
||||
</FormFolder>
|
||||
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>Log IP address</template>
|
||||
<template v-if="enableIpLogging" #suffix>Enabled</template>
|
||||
<template v-else #suffix>Disabled</template>
|
||||
|
||||
<div class="_formRoot">
|
||||
<FormSwitch v-model="enableIpLogging" class="_formBlock" @update:modelValue="save">
|
||||
<template #label>Enable</template>
|
||||
</FormSwitch>
|
||||
</div>
|
||||
</FormFolder>
|
||||
|
||||
<FormFolder class="_formBlock">
|
||||
<template #label>Summaly Proxy</template>
|
||||
|
||||
@ -51,17 +63,20 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
let summalyProxy: string = $ref('');
|
||||
let enableHcaptcha: boolean = $ref(false);
|
||||
let enableRecaptcha: boolean = $ref(false);
|
||||
let enableIpLogging: boolean = $ref(false);
|
||||
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
summalyProxy = meta.summalyProxy;
|
||||
enableHcaptcha = meta.enableHcaptcha;
|
||||
enableRecaptcha = meta.enableRecaptcha;
|
||||
enableIpLogging = meta.enableIpLogging;
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
summalyProxy,
|
||||
enableIpLogging,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
|
@ -27,6 +27,12 @@
|
||||
<template #key>ID</template>
|
||||
<template #value><span class="_monospace">{{ user.id }}</span></template>
|
||||
</MkKeyValue>
|
||||
<!-- 要る?
|
||||
<MkKeyValue v-if="ips.length > 0" :copy="user.id" oneline style="margin: 1em 0;">
|
||||
<template #key>IP (recent)</template>
|
||||
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
|
||||
</MkKeyValue>
|
||||
-->
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
||||
@ -92,8 +98,18 @@
|
||||
<div v-else-if="tab === 'files'" class="_formRoot">
|
||||
<MkFileListForAdmin :pagination="filesPagination" view-mode="grid"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'ip'" class="_formRoot">
|
||||
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
|
||||
<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo>
|
||||
<template v-if="iAmAdmin && ips">
|
||||
<div v-for="record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;">
|
||||
<span class="date">{{ record.createdAt }}</span>
|
||||
<span class="ip">{{ record.ip }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else-if="tab === 'ap'" class="_formRoot">
|
||||
<MkObjectView v-if="ap" tall :value="user">
|
||||
<MkObjectView v-if="ap" tall :value="ap">
|
||||
</MkObjectView>
|
||||
</div>
|
||||
<div v-else-if="tab === 'raw'" class="_formRoot">
|
||||
@ -122,6 +138,7 @@ import MkKeyValue from '@/components/key-value.vue';
|
||||
import MkSelect from '@/components/form/select.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import MkFileListForAdmin from '@/components/file-list-for-admin.vue';
|
||||
import MkInfo from '@/components/ui/info.vue';
|
||||
import * as os from '@/os';
|
||||
import number from '@/filters/number';
|
||||
import bytes from '@/filters/bytes';
|
||||
@ -129,7 +146,7 @@ import { url } from '@/config';
|
||||
import { userPage, acct } from '@/filters/user';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { i18n } from '@/i18n';
|
||||
import { iAmModerator } from '@/account';
|
||||
import { iAmAdmin, iAmModerator } from '@/account';
|
||||
|
||||
const props = defineProps<{
|
||||
userId: string;
|
||||
@ -140,6 +157,7 @@ let chartSrc = $ref('per-user-notes');
|
||||
let user = $ref<null | misskey.entities.UserDetailed>();
|
||||
let init = $ref();
|
||||
let info = $ref();
|
||||
let ips = $ref(null);
|
||||
let ap = $ref(null);
|
||||
let moderator = $ref(false);
|
||||
let silenced = $ref(false);
|
||||
@ -158,9 +176,12 @@ function createFetcher() {
|
||||
userId: props.userId,
|
||||
}), os.api('admin/show-user', {
|
||||
userId: props.userId,
|
||||
})]).then(([_user, _info]) => {
|
||||
}), iAmAdmin ? os.api('admin/get-user-ips', {
|
||||
userId: props.userId,
|
||||
}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => {
|
||||
user = _user;
|
||||
info = _info;
|
||||
ips = _ips;
|
||||
moderator = info.isModerator;
|
||||
silenced = info.isSilenced;
|
||||
suspended = info.isSuspended;
|
||||
@ -300,7 +321,11 @@ const headerTabs = $computed(() => [{
|
||||
key: 'ap',
|
||||
title: 'AP',
|
||||
icon: 'fas fa-share-alt',
|
||||
}, {
|
||||
}, iAmModerator ? {
|
||||
key: 'ip',
|
||||
title: 'IP',
|
||||
icon: 'fas fa-bars-staggered',
|
||||
} : null, {
|
||||
key: 'raw',
|
||||
title: 'Raw',
|
||||
icon: 'fas fa-code',
|
||||
@ -362,3 +387,17 @@ definePageMetadata(computed(() => ({
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" module>
|
||||
.ip {
|
||||
display: flex;
|
||||
|
||||
> :global(.date) {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
> :global(.ip) {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { reactive, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { readAndCompressImage } from 'browser-image-resizer';
|
||||
import { defaultStore } from '@/store';
|
||||
import { apiUrl } from '@/config';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { $i } from '@/account';
|
||||
import { readAndCompressImage } from 'browser-image-resizer';
|
||||
import { alert } from '@/os';
|
||||
|
||||
type Uploading = {
|
||||
@ -31,7 +31,7 @@ export function uploadFile(
|
||||
file: File,
|
||||
folder?: any,
|
||||
name?: string,
|
||||
keepOriginal: boolean = defaultStore.state.keepOriginalUploading
|
||||
keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
|
||||
): Promise<Misskey.entities.DriveFile> {
|
||||
if (folder && typeof folder === 'object') folder = folder.id;
|
||||
|
||||
@ -45,7 +45,7 @@ export function uploadFile(
|
||||
name: name || file.name || 'untitled',
|
||||
progressMax: undefined,
|
||||
progressValue: undefined,
|
||||
img: window.URL.createObjectURL(file)
|
||||
img: window.URL.createObjectURL(file),
|
||||
});
|
||||
|
||||
uploads.value.push(ctx);
|
||||
@ -86,7 +86,7 @@ export function uploadFile(
|
||||
alert({
|
||||
type: 'error',
|
||||
title: 'Failed to upload',
|
||||
text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}`
|
||||
text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}`,
|
||||
});
|
||||
|
||||
reject();
|
||||
|
Reference in New Issue
Block a user