From fd64a1e264d828c6248dfe3fe12631af4e93a22c Mon Sep 17 00:00:00 2001 From: tobi Date: Mon, 26 May 2025 13:28:55 +0200 Subject: [PATCH] [feature] Add "Instance Info" settings panel section, with domain blocks + allows (#4193) This pull request adds a new read-only, user-level "instance info" section to the settings panel, which presents api/v2/instance info in a nice readable format, and also gives the user authenticated access to the blocklist and allowlist of the domain. Closes https://codeberg.org/superseriousbusiness/gotosocial/issues/3711 Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4193 Co-authored-by: tobi Co-committed-by: tobi --- web/source/package.json | 2 + web/source/settings/lib/query/gts-api.ts | 16 +- .../settings/lib/query/user/domainperms.ts | 53 ++++ .../settings/lib/types/domain-permission.ts | 1 + web/source/settings/lib/types/instance.ts | 112 +++++-- web/source/settings/lib/util/index.ts | 42 +++ web/source/settings/style.css | 44 +++ .../moderation/domain-permissions/detail.tsx | 4 +- .../domain-permissions/drafts/new.tsx | 4 +- .../settings/views/user/instance/index.tsx | 287 ++++++++++++++++++ web/source/settings/views/user/menu.tsx | 5 + web/source/settings/views/user/router.tsx | 3 + web/source/yarn.lock | 10 + 13 files changed, 543 insertions(+), 40 deletions(-) create mode 100644 web/source/settings/lib/query/user/domainperms.ts create mode 100644 web/source/settings/views/user/instance/index.tsx diff --git a/web/source/package.json b/web/source/package.json index 3cb70e9a6..80dbb114e 100644 --- a/web/source/package.json +++ b/web/source/package.json @@ -16,6 +16,7 @@ "blurhash": "^2.0.5", "get-by-dot": "^1.0.2", "html-to-text": "^9.0.5", + "humanize-duration": "^3.32.2", "is-valid-domain": "^0.1.6", "js-file-download": "^0.4.12", "langs": "^2.0.0", @@ -48,6 +49,7 @@ "@browserify/uglifyify": "^6.0.0", "@joepie91/eslint-config": "^1.1.1", "@types/html-to-text": "^9.0.4", + "@types/humanize-duration": "^3.27.4", "@types/is-valid-domain": "^0.0.2", "@types/papaparse": "^5.3.9", "@types/parse-link-header": "^2.0.3", diff --git a/web/source/settings/lib/query/gts-api.ts b/web/source/settings/lib/query/gts-api.ts index 9d38e435d..33429d8a8 100644 --- a/web/source/settings/lib/query/gts-api.ts +++ b/web/source/settings/lib/query/gts-api.ts @@ -26,7 +26,7 @@ import type { import { serialize as serializeForm } from "object-to-formdata"; import type { FetchBaseQueryMeta } from "@reduxjs/toolkit/dist/query/fetchBaseQuery"; import type { RootState } from '../../redux/store'; -import { InstanceV1 } from '../types/instance'; +import { InstanceV1, InstanceV2 } from '../types/instance'; /** * GTSFetchArgs extends standard FetchArgs used by @@ -186,6 +186,11 @@ export const gtsApi = createApi({ query: () => ({ url: `/api/v1/instance` }) + }), + instanceV2: build.query({ + query: () => ({ + url: `/api/v2/instance` + }) }) }) }); @@ -193,8 +198,13 @@ export const gtsApi = createApi({ /** * Query /api/v1/instance to retrieve basic instance information. * This endpoint does not require authentication/authorization. - * TODO: move this to ./instance. */ const useInstanceV1Query = gtsApi.useInstanceV1Query; -export { useInstanceV1Query }; +/** + * Query /api/v2/instance to retrieve basic instance information. + * This endpoint does not require authentication/authorization. + */ +const useInstanceV2Query = gtsApi.useInstanceV2Query; + +export { useInstanceV1Query, useInstanceV2Query }; diff --git a/web/source/settings/lib/query/user/domainperms.ts b/web/source/settings/lib/query/user/domainperms.ts new file mode 100644 index 000000000..3d8e77bfe --- /dev/null +++ b/web/source/settings/lib/query/user/domainperms.ts @@ -0,0 +1,53 @@ +/* + GoToSocial + Copyright (C) GoToSocial Authors admin@gotosocial.org + SPDX-License-Identifier: AGPL-3.0-or-later + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import { gtsApi } from "../gts-api"; + +import type { DomainPerm } from "../../types/domain-permission"; + +const extended = gtsApi.injectEndpoints({ + endpoints: (build) => ({ + instanceDomainBlocks: build.query({ + query: () => ({ + url: `/api/v1/instance/domain_blocks` + }), + }), + + instanceDomainAllows: build.query({ + query: () => ({ + url: `/api/v1/instance/domain_allows` + }) + }), + }), +}); + +/** + * Get user-level view of all explicitly blocked domains. + */ +const useInstanceDomainBlocksQuery = extended.useInstanceDomainBlocksQuery; + +/** + * Get user-level view of all explicitly allowed domains. + */ +const useInstanceDomainAllowsQuery = extended.useInstanceDomainAllowsQuery; + +export { + useInstanceDomainBlocksQuery, + useInstanceDomainAllowsQuery, +}; diff --git a/web/source/settings/lib/types/domain-permission.ts b/web/source/settings/lib/types/domain-permission.ts index 27c4b56c9..3e947db61 100644 --- a/web/source/settings/lib/types/domain-permission.ts +++ b/web/source/settings/lib/types/domain-permission.ts @@ -33,6 +33,7 @@ export interface DomainPerm { obfuscate?: boolean; private_comment?: string; public_comment?: string; + comment?: string; created_at?: string; created_by?: string; subscription_id?: string; diff --git a/web/source/settings/lib/types/instance.ts b/web/source/settings/lib/types/instance.ts index 9abdc6a96..87d129d92 100644 --- a/web/source/settings/lib/types/instance.ts +++ b/web/source/settings/lib/types/instance.ts @@ -17,36 +17,52 @@ along with this program. If not, see . */ +import { Account } from "./account"; + export interface InstanceV1 { - uri: string; - account_domain: string; - title: string; - description: string; + uri: string; + account_domain: string; + title: string; + description: string; description_text?: string; - short_description: string; + short_description: string; short_description_text?: string; - custom_css: string; - email: string; - version: string; - debug?: boolean; - languages: any[]; // TODO: define this - registrations: boolean; - approval_required: boolean; - invites_enabled: boolean; - configuration: InstanceConfiguration; - urls: InstanceUrls; - stats: InstanceStats; - thumbnail: string; - contact_account: Object; // TODO: define this. - max_toot_chars: number; - rules: any[]; // TODO: define this - terms?: string; + custom_css: string; + email: string; + version: string; + debug?: boolean; + languages: string[]; + registrations: boolean; + approval_required: boolean; + invites_enabled: boolean; + configuration: InstanceV1Configuration; + urls: InstanceV1Urls; + stats: InstanceStats; + thumbnail: string; + contact_account: Account; + max_toot_chars: number; + rules: any[]; // TODO: define this + terms?: string; terms_text?: string; } -export interface InstanceConfiguration { +export interface InstanceV2 { + domain: string; + account_domain: string; + title: string; + version: string; + debug: boolean; + source_url: string; + description: string; + custom_css: string; + thumbnail: InstanceV2Thumbnail; + languages: string[]; + configuration: InstanceV2Configuration; +} + +export interface InstanceV1Configuration { statuses: InstanceStatuses; - media_attachments: InstanceMediaAttachments; + media_attachments: InstanceV1MediaAttachments; polls: InstancePolls; accounts: InstanceAccounts; emojis: InstanceEmojis; @@ -63,15 +79,6 @@ export interface InstanceEmojis { emoji_size_limit: number; } -export interface InstanceMediaAttachments { - supported_mime_types: string[]; - image_size_limit: number; - image_matrix_limit: number; - video_size_limit: number; - video_frame_rate_limit: number; - video_matrix_limit: number; -} - export interface InstancePolls { max_options: number; max_characters_per_option: number; @@ -92,7 +99,46 @@ export interface InstanceStats { user_count: number; } -export interface InstanceUrls { +export interface InstanceV1Urls { streaming_api: string; } +export interface InstanceV1MediaAttachments { + supported_mime_types: string[]; + image_size_limit: number; + image_matrix_limit: number; + video_size_limit: number; + video_frame_rate_limit: number; + video_matrix_limit: number; +} + +export interface InstanceV2Configuration { + urls: InstanceV2URLs; + accounts: InstanceAccounts; + statuses: InstanceStatuses; + media_attachments: InstanceV2MediaAttachments; + polls: InstancePolls; + translation: InstanceV2Translation; + emojis: InstanceEmojis; +} + +export interface InstanceV2MediaAttachments extends InstanceV1MediaAttachments { + description_limit: number; +} + +export interface InstanceV2Thumbnail { + url: string; + thumbnail_type?: string; + static_url?: string; + thumbnail_static_type?: string; + thumbnail_description?: string; + blurhash?: string; +} + +export interface InstanceV2Translation { + enabled: boolean; +} + +export interface InstanceV2URLs { + streaming: string; +} diff --git a/web/source/settings/lib/util/index.ts b/web/source/settings/lib/util/index.ts index 8bcf5ab5d..46b35fd70 100644 --- a/web/source/settings/lib/util/index.ts +++ b/web/source/settings/lib/util/index.ts @@ -22,6 +22,8 @@ import { useMemo } from "react"; import { AdminAccount } from "../types/account"; import { store } from "../../redux/store"; +import humanizeDuration from "humanize-duration"; + export function yesOrNo(b: boolean): string { return b ? "yes" : "no"; } @@ -54,3 +56,43 @@ export function useCapitalize(i?: string): string { return i.charAt(0).toUpperCase() + i.slice(1); }, [i]); } + +/** + * Return human-readable string representation of given bytes. + * + * Adapted from https://stackoverflow.com/a/14919494. + */ +export function useHumanReadableBytes(bytes: number): string { + return useMemo(() => { + const thresh = 1024; + const digitPrecision = 2; + const r = 10**digitPrecision; + const units = ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; + + if (Math.abs(bytes) < thresh) { + return bytes + ' B'; + } + + let u = -1; + let threshed = bytes; + do { threshed /= thresh; ++u; + } while (Math.round(Math.abs(threshed) * r) / r >= thresh && u < units.length - 1); + + return threshed.toFixed(digitPrecision) + ' ' + units[u]; + }, [bytes]); +} + +/** + * Return human-readable string representation of given time in seconds. + */ +export function useHumanReadableDuration(seconds: number): string { + return useMemo(() => { + if (seconds % 2629746 === 0) { + const n = seconds / 2629746; + return n + " month" + (n !== 1 ? "s" : ""); + } + + const ms = seconds*1000; + return humanizeDuration(ms); + }, [seconds]); +} diff --git a/web/source/settings/style.css b/web/source/settings/style.css index 67937bd9e..742407ea3 100644 --- a/web/source/settings/style.css +++ b/web/source/settings/style.css @@ -1549,6 +1549,50 @@ button.tab-button { } } +.instance-info-view { + .info-list .info-list-entry { + /* + Some of the labels are quite + long so ensure there's enough + gap when they're wrapped. + */ + gap: 1rem; + } + + /* + Make sure ellipsis works + properly for v. long domains. + */ + .list.domain-perm-list > .entry > .domain { + display: inline-block; + font-weight: bold; + } + + /* + Make sure we can break. + */ + .list.domain-perm-list > .entry > .public_comment { + word-wrap: anywhere; + } + + /* + Disable the hover effects as + these entries aren't clickable. + */ + .list.domain-perm-list > .entry:hover { + background: $list-entry-bg; + } + .list.domain-perm-list > .entry:nth-child(2n):hover { + background: $list-entry-alternate-bg; + } + .list.domain-perm-list > .entry { + &:active, &:focus, &:hover, &:target { + border-color: $gray1; + border-top-color: transparent; + } + } +} + .instance-rules { list-style-position: inside; margin: 0; diff --git a/web/source/settings/views/moderation/domain-permissions/detail.tsx b/web/source/settings/views/moderation/domain-permissions/detail.tsx index e8ef487e3..35be0e16d 100644 --- a/web/source/settings/views/moderation/domain-permissions/detail.tsx +++ b/web/source/settings/views/moderation/domain-permissions/detail.tsx @@ -307,14 +307,14 @@ function CreateOrUpdateDomainPerm({