From d79c2f26888b512faaa0526936b8752cfb6c8b28 Mon Sep 17 00:00:00 2001
From: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Mon, 3 Jun 2024 11:20:53 +0200
Subject: [PATCH] [feature/frontend] Add debug sections to settings panel
(#2950)
* [feature/frontend] Add debug sections to settings panel
* max-width
* swagger
---
docs/api/swagger.yaml | 8 ++
internal/api/model/instancev1.go | 2 +
internal/api/model/instancev2.go | 2 +
internal/typeutils/internaltofrontend.go | 9 ++
web/source/settings/index.tsx | 43 +++---
web/source/settings/lib/navigation/util.ts | 7 +
.../settings/lib/query/admin/debug/index.ts | 59 +++++++++
web/source/settings/lib/types/debug.ts | 26 ++++
web/source/settings/lib/types/instance.ts | 1 +
web/source/settings/style.css | 17 +++
.../views/admin/debug/apurl/index.tsx | 124 ++++++++++++++++++
.../views/admin/debug/caches/index.tsx | 52 ++++++++
web/source/settings/views/admin/menu.tsx | 32 ++++-
web/source/settings/views/admin/router.tsx | 33 ++++-
web/source/tsconfig.json | 2 +-
15 files changed, 395 insertions(+), 22 deletions(-)
create mode 100644 web/source/settings/lib/query/admin/debug/index.ts
create mode 100644 web/source/settings/lib/types/debug.ts
create mode 100644 web/source/settings/views/admin/debug/apurl/index.tsx
create mode 100644 web/source/settings/views/admin/debug/caches/index.tsx
diff --git a/docs/api/swagger.yaml b/docs/api/swagger.yaml
index 78725f325..050cf4c9b 100644
--- a/docs/api/swagger.yaml
+++ b/docs/api/swagger.yaml
@@ -1409,6 +1409,10 @@ definitions:
$ref: '#/definitions/instanceV1Configuration'
contact_account:
$ref: '#/definitions/account'
+ debug:
+ description: Whether or not instance is running in DEBUG mode. Omitted if false.
+ type: boolean
+ x-go-name: Debug
description:
description: |-
Description of the instance.
@@ -1571,6 +1575,10 @@ definitions:
$ref: '#/definitions/instanceV2Configuration'
contact:
$ref: '#/definitions/instanceV2Contact'
+ debug:
+ description: Whether or not instance is running in DEBUG mode. Omitted if false.
+ type: boolean
+ x-go-name: Debug
description:
description: |-
Description of the instance.
diff --git a/internal/api/model/instancev1.go b/internal/api/model/instancev1.go
index 03e27619c..217edc08c 100644
--- a/internal/api/model/instancev1.go
+++ b/internal/api/model/instancev1.go
@@ -59,6 +59,8 @@ type InstanceV1 struct {
//
// example: 0.1.1 cb85f65
Version string `json:"version"`
+ // Whether or not instance is running in DEBUG mode. Omitted if false.
+ Debug *bool `json:"debug,omitempty"`
// Primary language of the instance.
// example: ["en"]
Languages []string `json:"languages"`
diff --git a/internal/api/model/instancev2.go b/internal/api/model/instancev2.go
index dda9033b4..a1b98ea65 100644
--- a/internal/api/model/instancev2.go
+++ b/internal/api/model/instancev2.go
@@ -40,6 +40,8 @@ type InstanceV2 struct {
//
// example: 0.1.1 cb85f65
Version string `json:"version"`
+ // Whether or not instance is running in DEBUG mode. Omitted if false.
+ Debug *bool `json:"debug,omitempty"`
// The URL for the source code of the software running on this instance, in keeping with AGPL license requirements.
// example: https://github.com/superseriousbusiness/gotosocial
SourceURL string `json:"source_url"`
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index 2b340a191..abe2cfaee 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -26,6 +26,7 @@ import (
"strings"
"time"
+ "codeberg.org/gruf/go-debug"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
@@ -1200,6 +1201,10 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
instance.Version = toMastodonVersion(instance.Version)
}
+ if debug.DEBUG {
+ instance.Debug = util.Ptr(true)
+ }
+
// configuration
instance.Configuration.Statuses.MaxCharacters = config.GetStatusesMaxChars()
instance.Configuration.Statuses.MaxMediaAttachments = config.GetStatusesMediaMaxFiles()
@@ -1307,6 +1312,10 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
instance.Version = toMastodonVersion(instance.Version)
}
+ if debug.DEBUG {
+ instance.Debug = util.Ptr(true)
+ }
+
// thumbnail
thumbnail := apimodel.InstanceV2Thumbnail{}
diff --git a/web/source/settings/index.tsx b/web/source/settings/index.tsx
index 25e3d1f3c..5317658d2 100644
--- a/web/source/settings/index.tsx
+++ b/web/source/settings/index.tsx
@@ -27,7 +27,7 @@ import { store, persistor } from "./redux/store";
import { Authorization } from "./components/authorization";
import Loading from "./components/loading";
import { Account } from "./lib/types/account";
-import { BaseUrlContext, RoleContext } from "./lib/navigation/util";
+import { BaseUrlContext, RoleContext, InstanceDebugContext } from "./lib/navigation/util";
import { SidebarMenu } from "./lib/navigation/menu";
import { Redirect, Route, Router } from "wouter";
import AdminMenu from "./views/admin/menu";
@@ -37,6 +37,7 @@ import UserRouter from "./views/user/router";
import { ErrorBoundary } from "./lib/navigation/error";
import ModerationRouter from "./views/moderation/router";
import AdminRouter from "./views/admin/router";
+import { useInstanceV1Query } from "./lib/query/gts-api";
interface AppProps {
account: Account;
@@ -44,29 +45,33 @@ interface AppProps {
export function App({ account }: AppProps) {
const roles: string[] = useMemo(() => [ account.role.name ], [account]);
+ const { data: instance } = useInstanceV1Query();
+
return (
-
-
-
-
-
-
-
-
-
-
-
-
- {/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*
Ensure user ends up somewhere
if they just open /settings.
*/}
-
-
-
-
-
+
+
+
+
+
+
);
}
diff --git a/web/source/settings/lib/navigation/util.ts b/web/source/settings/lib/navigation/util.ts
index 2c5885c4d..b1ef7834d 100644
--- a/web/source/settings/lib/navigation/util.ts
+++ b/web/source/settings/lib/navigation/util.ts
@@ -21,6 +21,7 @@ import { createContext, useContext } from "react";
const RoleContext = createContext([]);
const BaseUrlContext = createContext("");
const MenuLevelContext = createContext(0);
+const InstanceDebugContext = createContext(false);
function urlSafe(str: string) {
return str.toLowerCase().replace(/[\s/]+/g, "-");
@@ -67,6 +68,10 @@ function useMenuLevel() {
return useContext(MenuLevelContext);
}
+function useInstanceDebug() {
+ return useContext(InstanceDebugContext);
+}
+
export {
urlSafe,
RoleContext,
@@ -76,4 +81,6 @@ export {
useBaseUrl,
MenuLevelContext,
useMenuLevel,
+ InstanceDebugContext,
+ useInstanceDebug,
};
diff --git a/web/source/settings/lib/query/admin/debug/index.ts b/web/source/settings/lib/query/admin/debug/index.ts
new file mode 100644
index 000000000..5dabd4666
--- /dev/null
+++ b/web/source/settings/lib/query/admin/debug/index.ts
@@ -0,0 +1,59 @@
+/*
+ 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 { ApURLResponse } from "../../../types/debug";
+import { gtsApi } from "../../gts-api";
+
+const extended = gtsApi.injectEndpoints({
+ endpoints: (build) => ({
+ ApURL: build.query({
+ query: (url) => {
+ // Get the url in a SearchParam
+ // so we can escape it.
+ const urlParam = new URLSearchParams();
+ urlParam.set("url", url);
+
+ return {
+ url: `/api/v1/admin/debug/apurl?${urlParam.toString()}`,
+ };
+ }
+ }),
+ ClearCaches: build.mutation<{}, void>({
+ query: () => ({
+ method: "POST",
+ url: `/api/v1/admin/debug/caches/clear`
+ })
+ }),
+ }),
+});
+
+/**
+ * Lazy GET to /api/v1/admin/debug/apurl.
+ */
+const useLazyApURLQuery = extended.useLazyApURLQuery;
+
+/**
+ * POST to /api/v1/admin/debug/caches/clear to empty in-memory caches.
+ */
+const useClearCachesMutation = extended.useClearCachesMutation;
+
+export {
+ useLazyApURLQuery,
+ useClearCachesMutation,
+};
diff --git a/web/source/settings/lib/types/debug.ts b/web/source/settings/lib/types/debug.ts
new file mode 100644
index 000000000..dab2c485d
--- /dev/null
+++ b/web/source/settings/lib/types/debug.ts
@@ -0,0 +1,26 @@
+/*
+ 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 .
+*/
+
+export interface ApURLResponse {
+ request_url: string;
+ request_headers: string;
+ response_headers: string;
+ response_code: number;
+ response_body: string;
+}
diff --git a/web/source/settings/lib/types/instance.ts b/web/source/settings/lib/types/instance.ts
index adc55687c..4c6f5061b 100644
--- a/web/source/settings/lib/types/instance.ts
+++ b/web/source/settings/lib/types/instance.ts
@@ -27,6 +27,7 @@ export interface InstanceV1 {
short_description_text?: string;
email: string;
version: string;
+ debug?: boolean;
languages: any[]; // TODO: define this
registrations: boolean;
approval_required: boolean;
diff --git a/web/source/settings/style.css b/web/source/settings/style.css
index 3d59ad5c5..d2420bdfc 100644
--- a/web/source/settings/style.css
+++ b/web/source/settings/style.css
@@ -1350,6 +1350,23 @@ button.with-padding {
}
}
+.admin-debug-apurl {
+ width: 100%;
+
+ .prism-highlighted {
+ max-width: 40rem;
+
+ /*
+ Normally we'd want to use a scrollbar for pre
+ and code, but it actually looks a bit better
+ to wrap here because there are many long lines.
+ */
+ pre, code {
+ white-space: pre-wrap;
+ }
+ }
+}
+
@media screen and (orientation: portrait) {
.reports .report .byline {
grid-template-columns: 1fr;
diff --git a/web/source/settings/views/admin/debug/apurl/index.tsx b/web/source/settings/views/admin/debug/apurl/index.tsx
new file mode 100644
index 000000000..a0c2fc738
--- /dev/null
+++ b/web/source/settings/views/admin/debug/apurl/index.tsx
@@ -0,0 +1,124 @@
+/*
+ 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 React, { useEffect, useRef } from "react";
+import { useTextInput } from "../../../../lib/form";
+import { useLazyApURLQuery } from "../../../../lib/query/admin/debug";
+import { TextInput } from "../../../../components/form/inputs";
+import MutationButton from "../../../../components/form/mutation-button";
+import { ApURLResponse } from "../../../../lib/types/debug";
+import Loading from "../../../../components/loading";
+
+// Used for syntax highlighting of json result.
+import Prism from "../../../../../frontend/prism";
+
+export default function ApURL() {
+ const urlField = useTextInput("url");
+
+ const [apURL, apURLResult] = useLazyApURLQuery();
+ function submit(e) {
+ e.preventDefault();
+ apURL(urlField.value ?? "");
+ }
+
+ return (
+
+
+
+
+ );
+}
+
+interface ApURLResultProps {
+ isLoading: boolean;
+ isFetching: boolean;
+ isSuccess: boolean;
+ data?: ApURLResponse,
+ isError: boolean;
+}
+
+function ApURLResult({
+ isLoading,
+ isFetching,
+ isSuccess,
+ data,
+ isError,
+}: ApURLResultProps) {
+ if (!(isSuccess || isError)) {
+ // Hasn't been called yet.
+ return null;
+ }
+
+ if (isLoading || isFetching) {
+ return ;
+ }
+
+ if (!data) {
+ return "No data";
+ }
+
+ const jsonObj = {
+ ...data,
+ response_body: data.response_body.length > 0 ? JSON.parse(data.response_body) : "",
+ };
+
+ const jsonStr = JSON.stringify(jsonObj, null, 2);
+ return ;
+}
+
+function Highlighted({ jsonStr }: { jsonStr: string }) {
+ const ref = useRef(null);
+ useEffect(() => {
+ if (ref.current) {
+ Prism.highlightElement(ref.current);
+ }
+ }, []);
+
+ // Prism takes control of the `pre` so wrap
+ // the whole thing in a div that we control.
+ return (
+
+
+
+ {jsonStr}
+
+
+
+ );
+}
diff --git a/web/source/settings/views/admin/debug/caches/index.tsx b/web/source/settings/views/admin/debug/caches/index.tsx
new file mode 100644
index 000000000..e3e9b4f82
--- /dev/null
+++ b/web/source/settings/views/admin/debug/caches/index.tsx
@@ -0,0 +1,52 @@
+/*
+ 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 React from "react";
+
+import MutationButton from "../../../../components/form/mutation-button";
+import { useClearCachesMutation } from "../../../../lib/query/admin/debug";
+
+export default function Caches({}) {
+ const [clearCaches, clearCachesResult] = useClearCachesMutation();
+ function submit(e) {
+ e.preventDefault();
+ clearCaches();
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/web/source/settings/views/admin/menu.tsx b/web/source/settings/views/admin/menu.tsx
index 3b88f6be3..7b9c00514 100644
--- a/web/source/settings/views/admin/menu.tsx
+++ b/web/source/settings/views/admin/menu.tsx
@@ -19,7 +19,7 @@
import { MenuItem } from "../../lib/navigation/menu";
import React from "react";
-import { useHasPermission } from "../../lib/navigation/util";
+import { useHasPermission, useInstanceDebug } from "../../lib/navigation/util";
/*
EXPORTED COMPONENTS
@@ -60,6 +60,7 @@ export default function AdminMenu() {
+
);
}
@@ -160,3 +161,32 @@ function AdminHTTPHeaderPermissionsMenu() {
);
}
+
+function AdminDebugMenu() {
+ // Don't attach this menu if instance
+ // is not running in debug mode.
+ const debug = useInstanceDebug();
+ if (!debug) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/web/source/settings/views/admin/router.tsx b/web/source/settings/views/admin/router.tsx
index 92cd7ac33..d1240adc3 100644
--- a/web/source/settings/views/admin/router.tsx
+++ b/web/source/settings/views/admin/router.tsx
@@ -18,7 +18,7 @@
*/
import React from "react";
-import { BaseUrlContext, useBaseUrl, useHasPermission } from "../../lib/navigation/util";
+import { BaseUrlContext, useBaseUrl, useHasPermission, useInstanceDebug } from "../../lib/navigation/util";
import { Redirect, Route, Router, Switch } from "wouter";
import { ErrorBoundary } from "../../lib/navigation/error";
import InstanceSettings from "./instance/settings";
@@ -32,6 +32,8 @@ import RemoteEmoji from "./emoji/remote";
import HeaderPermsOverview from "./http-header-permissions/overview";
import HeaderPermDetail from "./http-header-permissions/detail";
import Email from "./actions/email";
+import ApURL from "./debug/apurl";
+import Caches from "./debug/caches";
/*
EXPORTED COMPONENTS
@@ -53,6 +55,7 @@ import Email from "./actions/email";
* - /settings/admin/http-header-permissions/allows/:allowId
* - /settings/admin/http-header-permissions/blocks
* - /settings/admin/http-header-permissions/blocks/:blockId
+ * - /settings/admin/debug
*/
export default function AdminRouter() {
const parentUrl = useBaseUrl();
@@ -66,6 +69,7 @@ export default function AdminRouter() {
+
);
@@ -186,3 +190,30 @@ function AdminHTTPHeaderPermissionsRouter() {
);
}
+
+function AdminDebugRouter() {
+ const parentUrl = useBaseUrl();
+ const thisBase = "/debug";
+ const absBase = parentUrl + thisBase;
+
+ // Don't attach this route if instance
+ // is not running in debug mode.
+ const debug = useInstanceDebug();
+ if (!debug) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/source/tsconfig.json b/web/source/tsconfig.json
index 7d96a7deb..a7a63ad22 100644
--- a/web/source/tsconfig.json
+++ b/web/source/tsconfig.json
@@ -44,7 +44,7 @@
// "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
- // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+ "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */