mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-13 06:27:29 -06:00
[frontend] Unified panels (#812)
* settings panel restructuring * clean up old Gin handlers * colorscheme redesign, some other small css tweaks * basic router layout, error boundary * colorscheme redesign, some other small css tweaks * kebab-case consistency * superfluous padding on applist * remove unused consts * redux, whitespace changes.. * use .jsx extensions for components * login flow up till app registration * full redux oauth implementation, with basic error handling * split oauth api functions * oauth api revocation handling * basic profile change submission * move old dir * profile overview * fix keeping track of the wrong instance url (for different instance/api domains) * use redux state for profile form * delete old/index.js, old/basic.js, fully implemented * implement old/user/profile.js * implement password change * remove debug logging * support future api for removing files * customize profile css * remove unneeded wrapper components * restructure form fields * start on admin pages * admin panel settings * admin settings panel * remove old/admin files * add top-level redirect * refactor/cleanup forms * only do API checks on logged-in state * admin-status based routing * federation block routing * federation blocks * upgrade dependencies * react 18 changes * media cleanup * fix useEffect hooks * remove unused require * custom emoji base * emoji uploader * delete last old panel files * sidebar styling, remove unused page * refactor submit functions * fix sidebar boxshadow-border * fix old css variables * fix fake-toot avatar * fix non-square emoji * fix user settings redux keys * properly get admin account contact from instance response * Account.source default values * source.status_format key * mobile responsiveness * mobile element tweaks * proper redirect after removing block * add redirects for old setting panel urls * deletes * fix mobile overflow * clean up debug logging calls
This commit is contained in:
parent
2f22780800
commit
938328cd07
59 changed files with 3989 additions and 2837 deletions
61
web/source/settings-panel/admin/actions.js
Normal file
61
web/source/settings-panel/admin/actions.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const React = require("react");
|
||||
const Redux = require("react-redux");
|
||||
|
||||
const Submit = require("../components/submit");
|
||||
|
||||
const api = require("../lib/api");
|
||||
const submit = require("../lib/submit");
|
||||
|
||||
module.exports = function AdminActionPanel() {
|
||||
const dispatch = Redux.useDispatch();
|
||||
|
||||
const [days, setDays] = React.useState(30);
|
||||
|
||||
const [errorMsg, setError] = React.useState("");
|
||||
const [statusMsg, setStatus] = React.useState("");
|
||||
|
||||
const removeMedia = submit(
|
||||
() => dispatch(api.admin.mediaCleanup(days)),
|
||||
{setStatus, setError}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Admin Actions</h1>
|
||||
<div>
|
||||
<h2>Media cleanup</h2>
|
||||
<p>
|
||||
Clean up remote media older than the specified number of days.
|
||||
If the remote instance is still online they will be refetched when needed.
|
||||
Also cleans up unused headers and avatars from the media cache.
|
||||
</p>
|
||||
<div>
|
||||
<label htmlFor="days">Days: </label>
|
||||
<input id="days" type="number" value={days} onChange={(e) => setDays(e.target.value)}/>
|
||||
</div>
|
||||
<Submit onClick={removeMedia} label="Remove media" errorMsg={errorMsg} statusMsg={statusMsg} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
212
web/source/settings-panel/admin/emoji.js
Normal file
212
web/source/settings-panel/admin/emoji.js
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const React = require("react");
|
||||
const Redux = require("react-redux");
|
||||
const {Switch, Route, Link, Redirect, useRoute, useLocation} = require("wouter");
|
||||
|
||||
const Submit = require("../components/submit");
|
||||
const FakeToot = require("../components/fake-toot");
|
||||
const { formFields } = require("../components/form-fields");
|
||||
|
||||
const api = require("../lib/api");
|
||||
const adminActions = require("../redux/reducers/admin").actions;
|
||||
const submit = require("../lib/submit");
|
||||
|
||||
const base = "/settings/admin/custom-emoji";
|
||||
|
||||
module.exports = function CustomEmoji() {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${base}/:emojiId`}>
|
||||
<EmojiDetailWrapped />
|
||||
</Route>
|
||||
<EmojiOverview />
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
function EmojiOverview() {
|
||||
const dispatch = Redux.useDispatch();
|
||||
const [loaded, setLoaded] = React.useState(false);
|
||||
|
||||
const [errorMsg, setError] = React.useState("");
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!loaded) {
|
||||
Promise.try(() => {
|
||||
return dispatch(api.admin.fetchCustomEmoji());
|
||||
}).then(() => {
|
||||
setLoaded(true);
|
||||
}).catch((e) => {
|
||||
setLoaded(true);
|
||||
setError(e.message);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!loaded) {
|
||||
return (
|
||||
<>
|
||||
<h1>Custom Emoji</h1>
|
||||
Loading...
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Custom Emoji</h1>
|
||||
<EmojiList/>
|
||||
<NewEmoji/>
|
||||
{errorMsg.length > 0 &&
|
||||
<div className="error accent">{errorMsg}</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const NewEmojiForm = formFields(adminActions.updateNewEmojiVal, (state) => state.admin.newEmoji);
|
||||
function NewEmoji() {
|
||||
const dispatch = Redux.useDispatch();
|
||||
const newEmojiForm = Redux.useSelector((state) => state.admin.newEmoji);
|
||||
|
||||
const [errorMsg, setError] = React.useState("");
|
||||
const [statusMsg, setStatus] = React.useState("");
|
||||
|
||||
const uploadEmoji = submit(
|
||||
() => dispatch(api.admin.newEmoji()),
|
||||
{
|
||||
setStatus, setError,
|
||||
onSuccess: function() {
|
||||
URL.revokeObjectURL(newEmojiForm.image);
|
||||
return Promise.all([
|
||||
dispatch(adminActions.updateNewEmojiVal(["image", undefined])),
|
||||
dispatch(adminActions.updateNewEmojiVal(["imageFile", undefined])),
|
||||
dispatch(adminActions.updateNewEmojiVal(["shortcode", ""])),
|
||||
]);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (newEmojiForm.shortcode.length == 0) {
|
||||
if (newEmojiForm.imageFile != undefined) {
|
||||
let [name, ext] = newEmojiForm.imageFile.name.split(".");
|
||||
dispatch(adminActions.updateNewEmojiVal(["shortcode", name]));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let emojiOrShortcode = `:${newEmojiForm.shortcode}:`;
|
||||
|
||||
if (newEmojiForm.image != undefined) {
|
||||
emojiOrShortcode = <img
|
||||
className="emoji"
|
||||
src={newEmojiForm.image}
|
||||
title={`:${newEmojiForm.shortcode}:`}
|
||||
alt={newEmojiForm.shortcode}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Add new custom emoji</h2>
|
||||
|
||||
<FakeToot>
|
||||
Look at this new custom emoji {emojiOrShortcode} isn't it cool?
|
||||
</FakeToot>
|
||||
|
||||
<NewEmojiForm.File
|
||||
id="image"
|
||||
name="Image"
|
||||
fileType="image/png,image/gif"
|
||||
showSize={true}
|
||||
maxSize={50 * 1000}
|
||||
/>
|
||||
|
||||
<NewEmojiForm.TextInput
|
||||
id="shortcode"
|
||||
name="Shortcode (without : :), must be unique on the instance"
|
||||
placeHolder="blobcat"
|
||||
/>
|
||||
|
||||
<Submit onClick={uploadEmoji} label="Upload" errorMsg={errorMsg} statusMsg={statusMsg} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EmojiList() {
|
||||
const emoji = Redux.useSelector((state) => state.admin.emoji);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Overview</h2>
|
||||
<div className="list emoji-list">
|
||||
{Object.entries(emoji).map(([category, entries]) => {
|
||||
return <EmojiCategory key={category} category={category} entries={entries}/>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EmojiCategory({category, entries}) {
|
||||
return (
|
||||
<div className="entry">
|
||||
<b>{category}</b>
|
||||
<div className="emoji-group">
|
||||
{entries.map((e) => {
|
||||
return (
|
||||
// <Link key={e.static_url} to={`${base}/${e.shortcode}`}>
|
||||
<Link key={e.static_url} to={`${base}`}>
|
||||
<a>
|
||||
<img src={e.static_url} alt={e.shortcode} title={`:${e.shortcode}:`}/>
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EmojiDetailWrapped() {
|
||||
/* We wrap the component to generate formFields with a setter depending on the domain
|
||||
if formFields() is used inside the same component that is re-rendered with their state,
|
||||
inputs get re-created on every change, causing them to lose focus, and bad performance
|
||||
*/
|
||||
let [_match, {emojiId}] = useRoute(`${base}/:emojiId`);
|
||||
|
||||
function alterEmoji([key, val]) {
|
||||
return adminActions.updateDomainBlockVal([emojiId, key, val]);
|
||||
}
|
||||
|
||||
const fields = formFields(alterEmoji, (state) => state.admin.blockedInstances[emojiId]);
|
||||
|
||||
return <EmojiDetail id={emojiId} Form={fields} />;
|
||||
}
|
||||
|
||||
function EmojiDetail({id, Form}) {
|
||||
return (
|
||||
"Not implemented yet"
|
||||
);
|
||||
}
|
||||
382
web/source/settings-panel/admin/federation.js
Normal file
382
web/source/settings-panel/admin/federation.js
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const React = require("react");
|
||||
const Redux = require("react-redux");
|
||||
const {Switch, Route, Link, Redirect, useRoute, useLocation} = require("wouter");
|
||||
const fileDownload = require("js-file-download");
|
||||
|
||||
const { formFields } = require("../components/form-fields");
|
||||
|
||||
const api = require("../lib/api");
|
||||
const adminActions = require("../redux/reducers/admin").actions;
|
||||
const submit = require("../lib/submit");
|
||||
|
||||
const base = "/settings/admin/federation";
|
||||
|
||||
// const {
|
||||
// TextInput,
|
||||
// TextArea,
|
||||
// File
|
||||
// } = require("../components/form-fields").formFields(adminActions.setAdminSettingsVal, (state) => state.instances.adminSettings);
|
||||
|
||||
module.exports = function AdminSettings() {
|
||||
const dispatch = Redux.useDispatch();
|
||||
// const instance = Redux.useSelector(state => state.instances.adminSettings);
|
||||
const loadedBlockedInstances = Redux.useSelector(state => state.admin.loadedBlockedInstances);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!loadedBlockedInstances ) {
|
||||
Promise.try(() => {
|
||||
return dispatch(api.admin.fetchDomainBlocks());
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!loadedBlockedInstances) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Federation</h1>
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${base}/:domain`}>
|
||||
<InstancePageWrapped />
|
||||
</Route>
|
||||
<InstanceOverview />
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
function InstanceOverview() {
|
||||
const [filter, setFilter] = React.useState("");
|
||||
const blockedInstances = Redux.useSelector(state => state.admin.blockedInstances);
|
||||
const [_location, setLocation] = useLocation();
|
||||
|
||||
function filterFormSubmit(e) {
|
||||
e.preventDefault();
|
||||
setLocation(`${base}/${filter}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Federation</h1>
|
||||
Here you can see an overview of blocked instances.
|
||||
|
||||
<div className="instance-list">
|
||||
<h2>Blocked instances</h2>
|
||||
<form action={`${base}/view`} className="filter" role="search" onSubmit={filterFormSubmit}>
|
||||
<input name="domain" value={filter} onChange={(e) => setFilter(e.target.value)}/>
|
||||
<Link to={`${base}/${filter}`}><a className="button">Add block</a></Link>
|
||||
</form>
|
||||
<div className="list">
|
||||
{Object.values(blockedInstances).filter((a) => a.domain.startsWith(filter)).map((entry) => {
|
||||
return (
|
||||
<Link key={entry.domain} to={`${base}/${entry.domain}`}>
|
||||
<a className="entry nounderline">
|
||||
<span id="domain">
|
||||
{entry.domain}
|
||||
</span>
|
||||
<span id="date">
|
||||
{new Date(entry.created_at).toLocaleString()}
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BulkBlocking/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const Bulk = formFields(adminActions.updateBulkBlockVal, (state) => state.admin.bulkBlock);
|
||||
function BulkBlocking() {
|
||||
const dispatch = Redux.useDispatch();
|
||||
const {bulkBlock, blockedInstances} = Redux.useSelector(state => state.admin);
|
||||
|
||||
const [errorMsg, setError] = React.useState("");
|
||||
const [statusMsg, setStatus] = React.useState("");
|
||||
|
||||
function importBlocks() {
|
||||
setStatus("Processing");
|
||||
setError("");
|
||||
return Promise.try(() => {
|
||||
return dispatch(api.admin.bulkDomainBlock());
|
||||
}).then(({success, invalidDomains}) => {
|
||||
return Promise.try(() => {
|
||||
return resetBulk();
|
||||
}).then(() => {
|
||||
dispatch(adminActions.updateBulkBlockVal(["list", invalidDomains.join("\n")]));
|
||||
|
||||
let stat = "";
|
||||
if (success == 0) {
|
||||
return setError("No valid domains in import");
|
||||
} else if (success == 1) {
|
||||
stat = "Imported 1 domain";
|
||||
} else {
|
||||
stat = `Imported ${success} domains`;
|
||||
}
|
||||
|
||||
if (invalidDomains.length > 0) {
|
||||
if (invalidDomains.length == 1) {
|
||||
stat += ", input contained 1 invalid domain.";
|
||||
} else {
|
||||
stat += `, input contained ${invalidDomains.length} invalid domains.`;
|
||||
}
|
||||
} else {
|
||||
stat += "!";
|
||||
}
|
||||
|
||||
setStatus(stat);
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
setError(e.message);
|
||||
setStatus("");
|
||||
});
|
||||
}
|
||||
|
||||
function exportBlocks() {
|
||||
return Promise.try(() => {
|
||||
setStatus("Exporting");
|
||||
setError("");
|
||||
let asJSON = bulkBlock.exportType.startsWith("json");
|
||||
let _asCSV = bulkBlock.exportType.startsWith("csv");
|
||||
|
||||
let exportList = Object.values(blockedInstances).map((entry) => {
|
||||
if (asJSON) {
|
||||
return {
|
||||
domain: entry.domain,
|
||||
public_comment: entry.public_comment
|
||||
};
|
||||
} else {
|
||||
return entry.domain;
|
||||
}
|
||||
});
|
||||
|
||||
if (bulkBlock.exportType == "json") {
|
||||
return dispatch(adminActions.updateBulkBlockVal(["list", JSON.stringify(exportList)]));
|
||||
} else if (bulkBlock.exportType == "json-download") {
|
||||
return fileDownload(JSON.stringify(exportList), "block-export.json");
|
||||
} else if (bulkBlock.exportType == "plain") {
|
||||
return dispatch(adminActions.updateBulkBlockVal(["list", exportList.join("\n")]));
|
||||
}
|
||||
}).then(() => {
|
||||
setStatus("Exported!");
|
||||
}).catch((e) => {
|
||||
setError(e.message);
|
||||
setStatus("");
|
||||
});
|
||||
}
|
||||
|
||||
function resetBulk(e) {
|
||||
if (e != undefined) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return dispatch(adminActions.resetBulkBlockVal());
|
||||
}
|
||||
|
||||
function disableInfoFields(props={}) {
|
||||
if (bulkBlock.list[0] == "[") {
|
||||
return {
|
||||
...props,
|
||||
disabled: true,
|
||||
placeHolder: "Domain list is a JSON import, input disabled"
|
||||
};
|
||||
} else {
|
||||
return props;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bulk">
|
||||
<h2>Import / Export <a onClick={resetBulk}>reset</a></h2>
|
||||
<Bulk.TextArea
|
||||
id="list"
|
||||
name="Domains, one per line"
|
||||
placeHolder={`google.com\nfacebook.com`}
|
||||
/>
|
||||
|
||||
<Bulk.TextArea
|
||||
id="public_comment"
|
||||
name="Public comment"
|
||||
inputProps={disableInfoFields({rows: 3})}
|
||||
/>
|
||||
|
||||
<Bulk.TextArea
|
||||
id="private_comment"
|
||||
name="Private comment"
|
||||
inputProps={disableInfoFields({rows: 3})}
|
||||
/>
|
||||
|
||||
<Bulk.Checkbox
|
||||
id="obfuscate"
|
||||
name="Obfuscate domains? "
|
||||
inputProps={disableInfoFields()}
|
||||
/>
|
||||
|
||||
<div className="hidden">
|
||||
<Bulk.File
|
||||
id="json"
|
||||
fileType="application/json"
|
||||
withPreview={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="messagebutton">
|
||||
<div>
|
||||
<button type="submit" onClick={importBlocks}>Import</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" onClick={exportBlocks}>Export</button>
|
||||
|
||||
<Bulk.Select id="exportType" name="Export type" options={
|
||||
<>
|
||||
<option value="plain">One per line in text field</option>
|
||||
<option value="json">JSON in text field</option>
|
||||
<option value="json-download">JSON file download</option>
|
||||
<option disabled value="csv">CSV in text field (glitch-soc)</option>
|
||||
<option disabled value="csv-download">CSV file download (glitch-soc)</option>
|
||||
</>
|
||||
}/>
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
{errorMsg.length > 0 &&
|
||||
<div className="error accent">{errorMsg}</div>
|
||||
}
|
||||
{statusMsg.length > 0 &&
|
||||
<div className="accent">{statusMsg}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BackButton() {
|
||||
return (
|
||||
<Link to={base}>
|
||||
<a className="button">< back</a>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function InstancePageWrapped() {
|
||||
/* We wrap the component to generate formFields with a setter depending on the domain
|
||||
if formFields() is used inside the same component that is re-rendered with their state,
|
||||
inputs get re-created on every change, causing them to lose focus, and bad performance
|
||||
*/
|
||||
let [_match, {domain}] = useRoute(`${base}/:domain`);
|
||||
|
||||
if (domain == "view") { // from form field submission
|
||||
let realDomain = (new URL(document.location)).searchParams.get("domain");
|
||||
if (realDomain == undefined) {
|
||||
return <Redirect to={base}/>;
|
||||
} else {
|
||||
domain = realDomain;
|
||||
}
|
||||
}
|
||||
|
||||
function alterDomain([key, val]) {
|
||||
return adminActions.updateDomainBlockVal([domain, key, val]);
|
||||
}
|
||||
|
||||
const fields = formFields(alterDomain, (state) => state.admin.newInstanceBlocks[domain]);
|
||||
|
||||
return <InstancePage domain={domain} Form={fields} />;
|
||||
}
|
||||
|
||||
function InstancePage({domain, Form}) {
|
||||
const dispatch = Redux.useDispatch();
|
||||
const entry = Redux.useSelector(state => state.admin.newInstanceBlocks[domain]);
|
||||
const [_location, setLocation] = useLocation();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (entry == undefined) {
|
||||
dispatch(api.admin.getEditableDomainBlock(domain));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [errorMsg, setError] = React.useState("");
|
||||
const [statusMsg, setStatus] = React.useState("");
|
||||
|
||||
if (entry == undefined) {
|
||||
return "Loading...";
|
||||
}
|
||||
|
||||
const updateBlock = submit(
|
||||
() => dispatch(api.admin.updateDomainBlock(domain)),
|
||||
{setStatus, setError}
|
||||
);
|
||||
|
||||
const removeBlock = submit(
|
||||
() => dispatch(api.admin.removeDomainBlock(domain)),
|
||||
{setStatus, setError, startStatus: "Removing", successStatus: "Removed!", onSuccess: () => {
|
||||
setLocation(base);
|
||||
}}
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1><BackButton/> Federation settings for: {domain}</h1>
|
||||
{entry.new && "No stored block yet, you can add one below:"}
|
||||
|
||||
<Form.TextArea
|
||||
id="public_comment"
|
||||
name="Public comment"
|
||||
/>
|
||||
|
||||
<Form.TextArea
|
||||
id="private_comment"
|
||||
name="Private comment"
|
||||
/>
|
||||
|
||||
<Form.Checkbox
|
||||
id="obfuscate"
|
||||
name="Obfuscate domain? "
|
||||
/>
|
||||
|
||||
<div className="messagebutton">
|
||||
<button type="submit" onClick={updateBlock}>{entry.new ? "Add block" : "Save block"}</button>
|
||||
|
||||
{!entry.new &&
|
||||
<button className="danger" onClick={removeBlock}>Remove block</button>
|
||||
}
|
||||
|
||||
{errorMsg.length > 0 &&
|
||||
<div className="error accent">{errorMsg}</div>
|
||||
}
|
||||
{statusMsg.length > 0 &&
|
||||
<div className="accent">{statusMsg}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
110
web/source/settings-panel/admin/settings.js
Normal file
110
web/source/settings-panel/admin/settings.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const React = require("react");
|
||||
const Redux = require("react-redux");
|
||||
|
||||
const Submit = require("../components/submit");
|
||||
|
||||
const api = require("../lib/api");
|
||||
const submit = require("../lib/submit");
|
||||
|
||||
const adminActions = require("../redux/reducers/instances").actions;
|
||||
|
||||
const {
|
||||
TextInput,
|
||||
TextArea,
|
||||
File
|
||||
} = require("../components/form-fields").formFields(adminActions.setAdminSettingsVal, (state) => state.instances.adminSettings);
|
||||
|
||||
module.exports = function AdminSettings() {
|
||||
const dispatch = Redux.useDispatch();
|
||||
|
||||
const [errorMsg, setError] = React.useState("");
|
||||
const [statusMsg, setStatus] = React.useState("");
|
||||
|
||||
const updateSettings = submit(
|
||||
() => dispatch(api.admin.updateInstance()),
|
||||
{setStatus, setError}
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Instance Settings</h1>
|
||||
<TextInput
|
||||
id="title"
|
||||
name="Title"
|
||||
placeHolder="My GoToSocial instance"
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
id="short_description"
|
||||
name="Short description"
|
||||
placeHolder="A small testing instance for the GoToSocial alpha."
|
||||
/>
|
||||
<TextArea
|
||||
id="description"
|
||||
name="Full description"
|
||||
placeHolder="A small testing instance for the GoToSocial alpha."
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
id="contact_account.username"
|
||||
name="Contact user (local account username)"
|
||||
placeHolder="admin"
|
||||
/>
|
||||
<TextInput
|
||||
id="email"
|
||||
name="Contact email"
|
||||
placeHolder="admin@example.com"
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
id="terms"
|
||||
name="Terms & Conditions"
|
||||
placeHolder=""
|
||||
/>
|
||||
|
||||
{/* <div className="file-upload">
|
||||
<h3>Instance avatar</h3>
|
||||
<div>
|
||||
<img className="preview avatar" src={instance.avatar} alt={instance.avatar ? `Avatar image for the instance` : "No instance avatar image set"} />
|
||||
<File
|
||||
id="avatar"
|
||||
fileType="image/*"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="file-upload">
|
||||
<h3>Instance header</h3>
|
||||
<div>
|
||||
<img className="preview header" src={instance.header} alt={instance.header ? `Header image for the instance` : "No instance header image set"} />
|
||||
<File
|
||||
id="header"
|
||||
fileType="image/*"
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
<Submit onClick={updateSettings} label="Save" errorMsg={errorMsg} statusMsg={statusMsg} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue