/* GoToSocial Copyright (C) 2021-2023 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 . */ "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 BackButton = require("../components/back-button"); const Loading = require("../components/loading"); const { matchSorter } = require("match-sorter"); 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()); }); } }, [dispatch, loadedBlockedInstances]); if (!loadedBlockedInstances) { return (

Federation

); } return ( ); }; function InstanceOverview() { const [filter, setFilter] = React.useState(""); const blockedInstances = Redux.useSelector(state => state.admin.blockedInstances); const [_location, setLocation] = useLocation(); const filteredInstances = React.useMemo(() => { return matchSorter(Object.values(blockedInstances), filter, {keys: ["domain"]}); }, [blockedInstances, filter]); function filterFormSubmit(e) { e.preventDefault(); setLocation(`${base}/${filter}`); } return ( <>

Federation

Here you can see an overview of blocked instances.

Blocked instances

setFilter(e.target.value)}/> Add block
{filteredInstances.map((entry) => { return ( {entry.domain} {new Date(entry.created_at).toLocaleString()} ); })}
); } 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 (

Import / Export reset

}/>

{errorMsg.length > 0 &&
{errorMsg}
} {statusMsg.length > 0 &&
{statusMsg}
}
); } 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 ; } else { domain = realDomain; } } function alterDomain([key, val]) { return adminActions.updateDomainBlockVal([domain, key, val]); } const fields = formFields(alterDomain, (state) => state.admin.newInstanceBlocks[domain]); return ; } 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)); } }, [dispatch, domain, entry]); const [errorMsg, setError] = React.useState(""); const [statusMsg, setStatus] = React.useState(""); if (entry == undefined) { return ; } 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 (

Federation settings for: {domain}

{entry.new ? "No stored block yet, you can add one below:" : Editing domain blocks is not implemented yet, check here for progress. }
{entry.new ? : } {errorMsg.length > 0 &&
{errorMsg}
} {statusMsg.length > 0 &&
{statusMsg}
}
); }