diff options
Diffstat (limited to 'packages/client/src')
| -rw-r--r-- | packages/client/src/account.ts | 1 | ||||
| -rw-r--r-- | packages/client/src/pages/admin-file.vue | 26 | ||||
| -rw-r--r-- | packages/client/src/pages/admin/security.vue | 15 | ||||
| -rw-r--r-- | packages/client/src/pages/user-info.vue | 47 | ||||
| -rw-r--r-- | packages/client/src/scripts/upload.ts | 10 |
5 files changed, 87 insertions, 12 deletions
diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index eb2ba0a1e1..38f2ee4b36 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -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(); diff --git a/packages/client/src/pages/admin-file.vue b/packages/client/src/pages/admin-file.vue index 7bfbed35f0..f96a41a7ea 100644 --- a/packages/client/src/pages/admin-file.vue +++ b/packages/client/src/pages/admin-file.vue @@ -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', diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index ec11e42a7d..76fa9d21eb 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -15,6 +15,18 @@ </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> <div class="_formRoot"> @@ -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(); }); diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index cfea2637b7..f9edd208ab 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -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> diff --git a/packages/client/src/scripts/upload.ts b/packages/client/src/scripts/upload.ts index 2f7b30b58d..2f907e5e84 100644 --- a/packages/client/src/scripts/upload.ts +++ b/packages/client/src/scripts/upload.ts @@ -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(); |