1 import classNames from "classnames";
2 import { Component, linkEvent } from "inferno";
9 GetFederatedInstancesResponse,
12 } from "lemmy-js-client";
13 import { i18n } from "../../i18next";
14 import { InitialFetchRequest } from "../../interfaces";
15 import { FirstLoadService } from "../../services/FirstLoadService";
16 import { HttpService, RequestState } from "../../services/HttpService";
19 capitalizeFirstLetter,
22 removeFromEmojiDataModel,
28 import { HtmlTags } from "../common/html-tags";
29 import { Spinner } from "../common/icon";
30 import Tabs from "../common/tabs";
31 import { PersonListing } from "../person/person-listing";
32 import { EmojiForm } from "./emojis-form";
33 import RateLimitForm from "./rate-limit-form";
34 import { SiteForm } from "./site-form";
35 import { TaglineForm } from "./tagline-form";
37 type AdminSettingsData = RouteDataResponse<{
38 bannedRes: BannedPersonsResponse;
39 instancesRes: GetFederatedInstancesResponse;
42 interface AdminSettingsState {
43 siteRes: GetSiteResponse;
46 instancesRes: RequestState<GetFederatedInstancesResponse>;
47 bannedRes: RequestState<BannedPersonsResponse>;
48 leaveAdminTeamRes: RequestState<GetSiteResponse>;
49 emojiLoading: boolean;
52 isIsomorphic: boolean;
55 export class AdminSettings extends Component<any, AdminSettingsState> {
56 private isoData = setIsoData<AdminSettingsData>(this.context);
57 state: AdminSettingsState = {
58 siteRes: this.isoData.site_res,
61 bannedRes: { state: "empty" },
62 instancesRes: { state: "empty" },
63 leaveAdminTeamRes: { state: "empty" },
70 constructor(props: any, context: any) {
71 super(props, context);
73 this.handleEditSite = this.handleEditSite.bind(this);
74 this.handleEditEmoji = this.handleEditEmoji.bind(this);
75 this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this);
76 this.handleCreateEmoji = this.handleCreateEmoji.bind(this);
78 // Only fetch the data if coming from another route
79 if (FirstLoadService.isFirstLoad) {
80 const { bannedRes, instancesRes } = this.isoData.routeData;
91 static async fetchInitialData({
94 }: InitialFetchRequest): Promise<AdminSettingsData> {
96 bannedRes: await client.getBannedPersons({
99 instancesRes: await client.getFederatedInstances({
100 auth: auth as string,
105 async componentDidMount() {
106 if (!this.state.isIsomorphic) {
107 await this.fetchData();
111 get documentTitle(): string {
112 return `${i18n.t("admin_settings")} - ${
113 this.state.siteRes.site_view.site.name
118 const federationData =
119 this.state.instancesRes.state === "success"
120 ? this.state.instancesRes.data.federated_instances
124 <div className="admin-settings container-lg">
126 title={this.documentTitle}
127 path={this.context.router.route.match.url}
133 label: i18n.t("site"),
134 getNode: isSelected => (
136 className={classNames("tab-pane show", {
142 <div className="row">
143 <div className="col-12 col-md-6">
145 showLocal={showLocal(this.isoData)}
146 allowedInstances={federationData?.allowed}
147 blockedInstances={federationData?.blocked}
148 onSaveSite={this.handleEditSite}
149 siteRes={this.state.siteRes}
150 themeList={this.state.themeList}
151 loading={this.state.loading}
154 <div className="col-12 col-md-6">
163 key: "rate_limiting",
164 label: "Rate Limiting",
165 getNode: isSelected => (
167 className={classNames("tab-pane", {
171 id="rate_limiting-tab-pane"
175 this.state.siteRes.site_view.local_site_rate_limit
177 onSaveSite={this.handleEditSite}
178 loading={this.state.loading}
185 label: i18n.t("taglines"),
186 getNode: isSelected => (
188 className={classNames("tab-pane", {
192 id="taglines-tab-pane"
194 <div className="row">
196 taglines={this.state.siteRes.taglines}
197 onSaveSite={this.handleEditSite}
198 loading={this.state.loading}
206 label: i18n.t("emojis"),
207 getNode: isSelected => (
209 className={classNames("tab-pane", {
215 <div className="row">
217 onCreate={this.handleCreateEmoji}
218 onDelete={this.handleDeleteEmoji}
219 onEdit={this.handleEditEmoji}
220 loading={this.state.emojiLoading}
234 bannedRes: { state: "loading" },
235 instancesRes: { state: "loading" },
239 const auth = myAuthRequired();
241 const [bannedRes, instancesRes, themeList] = await Promise.all([
242 HttpService.client.getBannedPersons({ auth }),
243 HttpService.client.getFederatedInstances({ auth }),
257 <h5>{capitalizeFirstLetter(i18n.t("admins"))}</h5>
258 <ul className="list-unstyled">
259 {this.state.siteRes.admins.map(admin => (
260 <li key={admin.person.id} className="list-inline-item">
261 <PersonListing person={admin.person} />
273 onClick={linkEvent(this, this.handleLeaveAdminTeam)}
274 className="btn btn-danger mb-2"
276 {this.state.leaveAdminTeamRes.state == "loading" ? (
279 i18n.t("leave_admin_team")
286 switch (this.state.bannedRes.state) {
294 const bans = this.state.bannedRes.data.banned;
297 <h5>{i18n.t("banned_users")}</h5>
298 <ul className="list-unstyled">
299 {bans.map(banned => (
300 <li key={banned.person.id} className="list-inline-item">
301 <PersonListing person={banned.person} />
311 async handleEditSite(form: EditSite) {
312 this.setState({ loading: true });
314 const editRes = await HttpService.client.editSite(form);
316 if (editRes.state === "success") {
318 s.siteRes.site_view = editRes.data.site_view;
319 // TODO: Where to get taglines from?
320 s.siteRes.taglines = editRes.data.taglines;
323 toast(i18n.t("site_saved"));
326 this.setState({ loading: false });
331 handleSwitchTab(i: { ctx: AdminSettings; tab: string }) {
332 i.ctx.setState({ currentTab: i.tab });
335 async handleLeaveAdminTeam(i: AdminSettings) {
336 i.setState({ leaveAdminTeamRes: { state: "loading" } });
338 leaveAdminTeamRes: await HttpService.client.leaveAdmin({
339 auth: myAuthRequired(),
343 if (this.state.leaveAdminTeamRes.state === "success") {
344 toast(i18n.t("left_admin_team"));
345 this.context.router.history.replace("/");
349 async handleEditEmoji(form: EditCustomEmoji) {
350 this.setState({ emojiLoading: true });
352 const res = await HttpService.client.editCustomEmoji(form);
353 if (res.state === "success") {
354 updateEmojiDataModel(res.data.custom_emoji);
357 this.setState({ emojiLoading: false });
360 async handleDeleteEmoji(form: DeleteCustomEmoji) {
361 this.setState({ emojiLoading: true });
363 const res = await HttpService.client.deleteCustomEmoji(form);
364 if (res.state === "success") {
365 removeFromEmojiDataModel(res.data.id);
368 this.setState({ emojiLoading: false });
371 async handleCreateEmoji(form: CreateCustomEmoji) {
372 this.setState({ emojiLoading: true });
374 const res = await HttpService.client.createCustomEmoji(form);
375 if (res.state === "success") {
376 updateEmojiDataModel(res.data.custom_emoji);
379 this.setState({ emojiLoading: false });