diff --git a/web/source/settings-panel/admin/federation.js b/web/source/settings-panel/admin/federation.js
index 6898de602..23117be9f 100644
--- a/web/source/settings-panel/admin/federation.js
+++ b/web/source/settings-panel/admin/federation.js
@@ -21,62 +21,108 @@
 const Promise = require("bluebird");
 const React = require("react");
 const Redux = require("react-redux");
+const {Switch, Route, Link, useRoute} = require("wouter");
 
 const Submit = require("../components/submit");
 
 const api = require("../lib/api");
 const adminActions = require("../redux/reducers/instances").actions;
 
-const {
-	TextInput,
-	TextArea,
-	File
-} = require("../components/form-fields").formFields(adminActions.setAdminSettingsVal, (state) => state.instances.adminSettings);
+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 [loaded, setLoaded] = React.useState(false);
+	// const instance = Redux.useSelector(state => state.instances.adminSettings);
+	const { blockedInstances } = Redux.useSelector(state => state.admin);
 
 	const [errorMsg, setError] = React.useState("");
 	const [statusMsg, setStatus] = React.useState("");
 
-	React.useEffect(() => {
-		Promise.try(() => {
-			return dispatch(api.admin.fetchDomainBlocks());
-		}).then(() => {
-			setLoaded(true);
-		}).catch((e) => {
-			console.log(e);
-		});
-	}, []);
+	const [loaded, setLoaded] = React.useState(false);
 
-	function submit() {
-		setStatus("PATCHing");
-		setError("");
-		return Promise.try(() => {
-			return dispatch(api.admin.updateInstance());
-		}).then(() => {
-			setStatus("Saved!");
-		}).catch((e) => {
-			setError(e.message);
-			setStatus("");
-		});
-	}
+	React.useEffect(() => {
+		if (blockedInstances != undefined) {
+			setLoaded(true);
+		} else {
+			return Promise.try(() => {
+				return dispatch(api.admin.fetchDomainBlocks());
+			}).then(() => {
+				setLoaded(true);
+			});
+		}
+	}, []);
 
 	if (!loaded) {
 		return (
 			<div>
 				<h1>Federation</h1>
-				Loading instance blocks...
+				Loading...
 			</div>
 		);
 	}
 
 	return (
 		<div>
-			<h1>Federation</h1>
+			<Switch>
+				<Route path={`${base}/:domain`}>
+					<InstancePage blockedInstances={blockedInstances}/>
+				</Route>
+				<InstanceOverview blockedInstances={blockedInstances} />
+			</Switch>
 		</div>
 	);
-};
\ No newline at end of file
+};
+
+function InstanceOverview({blockedInstances}) {
+	return (
+		<div>
+			<h1>Federation</h1>
+			{blockedInstances.map((entry) => {
+				return (
+					<Link key={entry.domain} to={`${base}/${entry.domain}`}>
+						<a>{entry.domain}</a>
+					</Link>
+				);
+			})}
+		</div>
+	);
+}
+
+function BackButton() {
+	return (
+		<Link to={base}>
+			<a className="button">&lt; back</a>
+		</Link>
+	);
+}
+
+function InstancePage({blockedInstances}) {
+	let [_match, {domain}] = useRoute(`${base}/:domain`);
+	let [status, setStatus] = React.useState("");
+	let [entry, setEntry] = React.useState(() => {
+		let entry = blockedInstances.find((a) => a.domain == domain);
+	
+		if (entry == undefined) {
+			setStatus(`No block entry found for ${domain}, but you can create one below:`);
+			return {
+				private_comment: ""
+			};
+		} else {
+			return entry;
+		}
+	});
+
+	return (
+		<div>
+			{status}
+			<h1><BackButton/> Federation settings for: {domain}</h1>
+			<div>{entry.private_comment}</div>
+		</div>
+	);
+}
\ No newline at end of file
diff --git a/web/source/settings-panel/components/nav-button.jsx b/web/source/settings-panel/components/nav-button.jsx
index d806400fb..3c76711fb 100644
--- a/web/source/settings-panel/components/nav-button.jsx
+++ b/web/source/settings-panel/components/nav-button.jsx
@@ -22,7 +22,7 @@ const React = require("react");
 const { Link, useRoute } = require("wouter");
 
 module.exports = function NavButton({href, name}) {
-	const [isActive] = useRoute(href);
+	const [isActive] = useRoute(`${href}/:anything?`);
 	return (
 		<Link href={href}>
 			<a className={isActive ? "active" : ""} data-content={name}>
diff --git a/web/source/settings-panel/lib/api/admin.js b/web/source/settings-panel/lib/api/admin.js
index cfc243fd0..cf44fc6ef 100644
--- a/web/source/settings-panel/lib/api/admin.js
+++ b/web/source/settings-panel/lib/api/admin.js
@@ -21,6 +21,7 @@
 const Promise = require("bluebird");
 
 const instance = require("../../redux/reducers/instances").actions;
+const admin = require("../../redux/reducers/admin").actions;
 
 module.exports = function ({ apiCall, getChanges }) {
 	return {
@@ -45,6 +46,8 @@ module.exports = function ({ apiCall, getChanges }) {
 			return function (dispatch, _getState) {
 				return Promise.try(() => {
 					return dispatch(apiCall("GET", "/api/v1/admin/domain_blocks"));
+				}).then((data) => {
+					return dispatch(admin.setBlockedInstances(data));
 				});
 			};
 		}
diff --git a/web/source/settings-panel/lib/api/oauth.js b/web/source/settings-panel/lib/api/oauth.js
index 604bdcc37..4067f5a75 100644
--- a/web/source/settings-panel/lib/api/oauth.js
+++ b/web/source/settings-panel/lib/api/oauth.js
@@ -24,7 +24,7 @@ const { OAUTHError, AuthenticationError } = require("../errors");
 
 const oauth = require("../../redux/reducers/oauth").actions;
 const temporary = require("../../redux/reducers/temporary").actions;
-const user = require("../../redux/reducers/user").actions;
+const admin = require("../../redux/reducers/admin").actions;
 
 module.exports = function oauthAPI({ apiCall, getCurrentUrl }) {
 	return {
@@ -103,8 +103,11 @@ module.exports = function oauthAPI({ apiCall, getCurrentUrl }) {
 				// no role info, try fetching an admin-only route and see if we get an error
 				return Promise.try(() => {
 					return dispatch(apiCall("GET", "/api/v1/admin/domain_blocks"));
-				}).then(() => {
-					return dispatch(oauth.setAdmin(true));
+				}).then((data) => {
+					return Promise.all([
+						dispatch(oauth.setAdmin(true)),
+						dispatch(admin.setBlockedInstances(data))
+					]);
 				}).catch(AuthenticationError, () => {
 					return dispatch(oauth.setAdmin(false));
 				}).catch((e) => {
diff --git a/web/source/settings-panel/lib/get-views.js b/web/source/settings-panel/lib/get-views.js
index 5c68e832c..01031747e 100644
--- a/web/source/settings-panel/lib/get-views.js
+++ b/web/source/settings-panel/lib/get-views.js
@@ -64,11 +64,11 @@ module.exports = function getViews(struct) {
 			let url = `${base}/${urlSafe(name)}`;
 
 			if (firstRoute == undefined) {
-				firstRoute = `${base}/${urlSafe(name)}`;
+				firstRoute = url;
 			}
 
-			routes.push((
-				<Route path={url} key={url}>
+			panelRouterEl.push((
+				<Route path={`${url}/:page?`} key={url}>
 					<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => { }}>
 						{/* FIXME: implement onReset */}
 						<ViewComponent />
@@ -87,14 +87,15 @@ module.exports = function getViews(struct) {
 			</Route>
 		);
 
-		let childrenPath = `${base}/:section`;
-		panelRouterEl.push(
-			<Route key={childrenPath} path={childrenPath}>
-				<Switch>
-					{routes}
-				</Switch>
-			</Route>
-		);
+		// let childrenPath = `${base}/:section`;
+		// panelRouterEl.push(...routes);
+		console.log(panelRouterEl);
+		// 	<Route key={childrenPath} path={childrenPath}>
+		// 		<Switch id="childrenPath-switch">
+		// 			{routes}
+		// 		</Switch>
+		// 	</Route>
+		// );
 
 		sidebarEl.push(
 			<React.Fragment key={name}>
diff --git a/web/source/settings-panel/redux/index.js b/web/source/settings-panel/redux/index.js
index ab2694223..e0dbe9b23 100644
--- a/web/source/settings-panel/redux/index.js
+++ b/web/source/settings-panel/redux/index.js
@@ -36,6 +36,7 @@ const combinedReducers = combineReducers({
 	instances: require("./reducers/instances").reducer,
 	temporary: require("./reducers/temporary").reducer,
 	user: require("./reducers/user").reducer,
+	admin: require("./reducers/admin").reducer,
 });
 
 const persistedReducer = persistReducer(persistConfig, combinedReducers);
diff --git a/web/source/settings-panel/redux/reducers/admin.js b/web/source/settings-panel/redux/reducers/admin.js
new file mode 100644
index 000000000..f8f4e994e
--- /dev/null
+++ b/web/source/settings-panel/redux/reducers/admin.js
@@ -0,0 +1,49 @@
+/*
+	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 {createSlice} = require("@reduxjs/toolkit");
+// const d = require("dotty");
+
+function sortBlocks(blocks) {
+	return blocks.sort((a, b) => { // alphabetical sort
+		return a.domain.localeCompare(b.domain);
+	});
+}
+
+// function deduplicateBlocks(blocks) {
+// 	let a = new Map();
+// 	blocks.forEach((block) => {
+// 		a.set(block.id, block);
+// 	});
+// 	return Array.from(a.values());
+// }
+
+module.exports = createSlice({
+	name: "admin",
+	initialState: {
+		blockedInstances: undefined,
+		blockedInstancesMap: {}
+	},
+	reducers: {
+		setBlockedInstances: (state, {payload}) => {
+			state.blockedInstances = sortBlocks(payload);
+		},
+	}
+});
\ No newline at end of file