[feature/frontend] Let admins send test email to validate SMTP config (#2934)

* [feature/frontend] Let admins send test email to validate SMTP config

* wee
This commit is contained in:
tobi
2024-05-27 19:03:54 +02:00
committed by GitHub
parent 1e7b32490d
commit a276b1ca06
18 changed files with 276 additions and 52 deletions

View File

@@ -0,0 +1,73 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
import { gtsApi } from "../../gts-api";
const extended = gtsApi.injectEndpoints({
endpoints: (build) => ({
mediaCleanup: build.mutation({
query: (days) => ({
method: "POST",
url: `/api/v1/admin/media_cleanup`,
params: {
remote_cache_days: days
}
})
}),
instanceKeysExpire: build.mutation({
query: (domain) => ({
method: "POST",
url: `/api/v1/admin/domain_keys_expire`,
params: {
domain: domain
}
})
}),
sendTestEmail: build.mutation<any, { email: string, message?: string }>({
query: (params) => ({
method: "POST",
url: `/api/v1/admin/email/test`,
params: params,
})
}),
}),
});
/**
* POST to /api/v1/admin/media_cleanup to trigger manual cleanup.
*/
const useMediaCleanupMutation = extended.useMediaCleanupMutation;
/**
* POST to /api/v1/admin/domain_keys_expire to expire domain keys for the given domain.
*/
const useInstanceKeysExpireMutation = extended.useInstanceKeysExpireMutation;
/**
* POST to /api/v1/admin/email/test to send a test email to the given address.
*/
const useSendTestEmailMutation = extended.useSendTestEmailMutation;
export {
useMediaCleanupMutation,
useInstanceKeysExpireMutation,
useSendTestEmailMutation,
};

View File

@@ -37,26 +37,6 @@ const extended = gtsApi.injectEndpoints({
...replaceCacheOnMutation("instanceV1"),
}),
mediaCleanup: build.mutation({
query: (days) => ({
method: "POST",
url: `/api/v1/admin/media_cleanup`,
params: {
remote_cache_days: days
}
})
}),
instanceKeysExpire: build.mutation({
query: (domain) => ({
method: "POST",
url: `/api/v1/admin/domain_keys_expire`,
params: {
domain: domain
}
})
}),
getAccount: build.query<AdminAccount, string>({
query: (id) => ({
url: `/api/v1/admin/accounts/${id}`
@@ -214,8 +194,6 @@ const extended = gtsApi.injectEndpoints({
export const {
useUpdateInstanceMutation,
useMediaCleanupMutation,
useInstanceKeysExpireMutation,
useGetAccountQuery,
useLazyGetAccountQuery,
useActionAccountMutation,

View File

@@ -0,0 +1,29 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
import React from "react";
import Test from "./test";
export default function Email() {
return (
<div className="admin-actions-email">
<Test />
</div>
);
}

View File

@@ -0,0 +1,77 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { TextInput } from "../../../../components/form/inputs";
import MutationButton from "../../../../components/form/mutation-button";
import { useTextInput } from "../../../../lib/form";
import { useSendTestEmailMutation } from "../../../../lib/query/admin/actions";
import { useInstanceV1Query } from "../../../../lib/query/gts-api";
import useFormSubmit from "../../../../lib/form/submit";
export default function Test({}) {
const { data: instance } = useInstanceV1Query();
const form = {
email: useTextInput("email", { defaultValue: instance?.email }),
message: useTextInput("message")
};
const [submit, result] = useFormSubmit(form, useSendTestEmailMutation(), { changedOnly: false });
return (
<form onSubmit={submit}>
<div className="form-section-docs">
<h2>Send test email</h2>
<p>
To check whether your instance email configuration is correct, you can
try sending a test email to the given address, with an optional message.
<br/>
If you do not have SMTP configured for your instance, this will do nothing.
</p>
<a
href="https://docs.gotosocial.org/en/latest/configuration/smtp/"
target="_blank"
className="docslink"
rel="noreferrer"
>
Learn more about SMTP configuration (opens in a new tab)
</a>
</div>
<TextInput
field={form.email}
label="Email"
placeholder="someone@example.org"
// Get email validation for free.
type="email"
required={true}
/>
<TextInput
field={form.message}
label="Message (optional)"
placeholder="Please disregard this test email, thanks!"
/>
<MutationButton
disabled={!form.email.value}
label="Send"
result={result}
/>
</form>
);
}

View File

@@ -21,7 +21,7 @@ import React from "react";
import { TextInput } from "../../../../components/form/inputs";
import MutationButton from "../../../../components/form/mutation-button";
import { useTextInput } from "../../../../lib/form";
import { useInstanceKeysExpireMutation } from "../../../../lib/query/admin";
import { useInstanceKeysExpireMutation } from "../../../../lib/query/admin/actions";
export default function ExpireRemote({}) {
const domainField = useTextInput("domain");
@@ -35,15 +35,20 @@ export default function ExpireRemote({}) {
return (
<form onSubmit={submitExpire}>
<h2>Expire remote instance keys</h2>
<p>
Mark all public keys from the given remote instance as expired.<br/><br/>
This is useful in cases where the remote domain has had to rotate their keys for whatever
reason (security issue, data leak, routine safety procedure, etc), and your instance can no
longer communicate with theirs properly using cached keys. A key marked as expired in this way
will be lazily refetched next time a request is made to your instance signed by the owner of that
key.
</p>
<div className="form-section-docs">
<h2>Expire remote instance keys</h2>
<p>
Mark all public keys from the given remote instance as expired.
<br/>
This is useful in cases where the remote domain has had to rotate
their keys for whatever reason (security issue, data leak, routine
safety procedure, etc), and your instance can no longer communicate
with theirs properly using cached keys.
<br/>
A key marked as expired in this way will be lazily refetched next time
a request is made to your instance signed by the owner of that key.
</p>
</div>
<TextInput
field={domainField}
label="Domain"

View File

@@ -22,9 +22,8 @@ import ExpireRemote from "./expireremote";
export default function Keys() {
return (
<>
<h1>Key Actions</h1>
<div className="admin-actions-keys">
<ExpireRemote />
</>
</div>
);
}

View File

@@ -22,10 +22,10 @@ import React from "react";
import { useTextInput } from "../../../../lib/form";
import { TextInput } from "../../../../components/form/inputs";
import MutationButton from "../../../../components/form/mutation-button";
import { useMediaCleanupMutation } from "../../../../lib/query/admin";
import { useMediaCleanupMutation } from "../../../../lib/query/admin/actions";
export default function Cleanup({}) {
const daysField = useTextInput("days", { defaultValue: "30" });
const daysField = useTextInput("days", { defaultValue: "7" });
const [mediaCleanup, mediaCleanupResult] = useMediaCleanupMutation();
@@ -36,12 +36,24 @@ export default function Cleanup({}) {
return (
<form onSubmit={submitCleanup}>
<h2>Cleanup</h2>
<p>
<div className="form-section-docs">
<h2>Cleanup</h2>
<p>
Clean up remote media older than the specified number of days.
<br/>
If the remote instance is still online they will be refetched when needed.
<br/>
Also cleans up unused headers and avatars from the media cache.
</p>
</p>
<a
href="https://docs.gotosocial.org/en/latest/admin/media_caching/"
target="_blank"
className="docslink"
rel="noreferrer"
>
Learn more about media caching + cleanup (opens in a new tab)
</a>
</div>
<TextInput
field={daysField}
label="Days"

View File

@@ -22,9 +22,8 @@ import Cleanup from "./cleanup";
export default function Media() {
return (
<>
<h1>Media Actions</h1>
<div className="admin-actions-media">
<Cleanup />
</>
</div>
);
}

View File

@@ -34,6 +34,7 @@ import { useHasPermission } from "../../lib/navigation/util";
* - /settings/admin/emojis/local/:emojiId
* - /settings/admin/emojis/remote
* - /settings/admin/actions
* - /settings/admin/actions/email
* - /settings/admin/actions/media
* - /settings/admin/actions/keys
* - /settings/admin/http-header-permissions/blocks
@@ -94,9 +95,14 @@ function AdminActionsMenu() {
<MenuItem
name="Actions"
itemUrl="actions"
defaultChild="media"
defaultChild="email"
icon="fa-bolt"
>
<MenuItem
name="Email"
itemUrl="email"
icon="fa-email-bulk"
/>
<MenuItem
name="Media"
itemUrl="media"

View File

@@ -31,6 +31,7 @@ import EmojiDetail from "./emoji/local/detail";
import RemoteEmoji from "./emoji/remote";
import HeaderPermsOverview from "./http-header-permissions/overview";
import HeaderPermDetail from "./http-header-permissions/detail";
import Email from "./actions/email";
/*
EXPORTED COMPONENTS
@@ -47,6 +48,7 @@ import HeaderPermDetail from "./http-header-permissions/detail";
* - /settings/admin/actions
* - /settings/admin/actions/media
* - /settings/admin/actions/keys
* - /settings/admin/actions/email
* - /settings/admin/http-header-permissions/allows
* - /settings/admin/http-header-permissions/allows/:allowId
* - /settings/admin/http-header-permissions/blocks
@@ -108,6 +110,7 @@ function AdminEmojisRouter() {
/**
* - /settings/admin/actions
* - /settings/admin/actions/email
* - /settings/admin/actions/media
* - /settings/admin/actions/keys
*/
@@ -121,9 +124,10 @@ function AdminActionsRouter() {
<Router base={thisBase}>
<ErrorBoundary>
<Switch>
<Route path="/email" component={Email} />
<Route path="/media" component={Media} />
<Route path="/keys" component={Keys} />
<Route><Redirect to="/media" /></Route>
<Route><Redirect to="/email" /></Route>
</Switch>
</ErrorBoundary>
</Router>