Skip to content

Commit

Permalink
Add User Rate Limits tab (#125)
Browse files Browse the repository at this point in the history
* Add User Rate Limits tab

* update readme
  • Loading branch information
beastafk authored Nov 6, 2024
1 parent a04b24a commit 9adc13e
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 83 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ with a proper manifest.json generation on build)
* Allow setting version using `SYNAPSE_ADMIN_VERSION` environment variable on build (if git is not available)
* [Add option to control user's experimental features](https://github.com/etkecc/synapse-admin/pull/111)
* [Add random password generation on user create/edit form](https://github.com/etkecc/synapse-admin/pull/123)
* [Add option to set user's rate limits](https://github.com/etkecc/synapse-admin/pull/125)

_the list will be updated as new changes are added_

Expand Down
144 changes: 72 additions & 72 deletions src/components/ExperimentalFeatures.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,86 +10,86 @@ const experimentalFeaturesMap = {
msc3575: "enable experimental sliding sync support",
};
const ExperimentalFeatureRow = (props: { featureKey: string, featureValue: boolean, updateFeature: (feature_name: string, feature_value: boolean) => void}) => {
const featureKey = props.featureKey;
const featureValue = props.featureValue;
const featureDescription = experimentalFeaturesMap[featureKey] ?? "";
const [checked, setChecked] = useState(featureValue);
const featureKey = props.featureKey;
const featureValue = props.featureValue;
const featureDescription = experimentalFeaturesMap[featureKey] ?? "";
const [checked, setChecked] = useState(featureValue);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(event.target.checked);
props.updateFeature(featureKey, event.target.checked);
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(event.target.checked);
props.updateFeature(featureKey, event.target.checked);
};

return <Stack
direction="row"
spacing={2}
alignItems="start"
sx={{
padding: 2,
}}
>
<Switch checked={checked} onChange={handleChange} />
<Stack>
<Typography
variant="subtitle1"
sx={{
fontWeight: "medium",
color: "text.primary"
}}
>
{featureKey}
</Typography>
<Typography
variant="body2"
color="text.secondary"
>
{featureDescription}
</Typography>
</Stack>
return <Stack
direction="row"
spacing={2}
alignItems="start"
sx={{
padding: 2,
}}
>
<Switch checked={checked} onChange={handleChange} />
<Stack>
<Typography
variant="subtitle1"
sx={{
fontWeight: "medium",
color: "text.primary"
}}
>
{featureKey}
</Typography>
<Typography
variant="body2"
color="text.secondary"
>
{featureDescription}
</Typography>
</Stack>
</Stack>
}

export const ExperimentalFeaturesList = () => {
const record = useRecordContext();
const notify = useNotify();
const dataProvider = useDataProvider() as SynapseDataProvider;
const [features, setFeatures] = useState({});
if (!record) {
return null;
}
const record = useRecordContext();
const notify = useNotify();
const dataProvider = useDataProvider() as SynapseDataProvider;
const [features, setFeatures] = useState({});
if (!record) {
return null;
}

useEffect(() => {
const fetchFeatures = async () => {
const features = await dataProvider.getFeatures(record.id);
setFeatures(features);
}
useEffect(() => {
const fetchFeatures = async () => {
const features = await dataProvider.getFeatures(record.id);
setFeatures(features);
}

fetchFeatures();
}, []);
fetchFeatures();
}, []);

const updateFeature = async (feature_name: string, feature_value: boolean) => {
const updatedFeatures = {...features, [feature_name]: feature_value} as ExperimentalFeaturesModel;
setFeatures(updatedFeatures);
const reponse = await dataProvider.updateFeatures(record.id, updatedFeatures);
notify("ra.notification.updated", {
messageArgs: { smart_count: 1 },
type: "success",
});
};
const updateFeature = async (feature_name: string, feature_value: boolean) => {
const updatedFeatures = {...features, [feature_name]: feature_value} as ExperimentalFeaturesModel;
setFeatures(updatedFeatures);
const reponse = await dataProvider.updateFeatures(record.id, updatedFeatures);
notify("ra.notification.updated", {
messageArgs: { smart_count: 1 },
type: "success",
});
};

return <>
<Stack
direction="column"
spacing={1}
>
{Object.keys(features).map((featureKey: string) =>
<ExperimentalFeatureRow
key={featureKey}
featureKey={featureKey}
featureValue={features[featureKey]}
updateFeature={updateFeature}
/>
)}
</Stack>
</>
return <>
<Stack
direction="column"
spacing={1}
>
{Object.keys(features).map((featureKey: string) =>
<ExperimentalFeatureRow
key={featureKey}
featureKey={featureKey}
featureValue={features[featureKey]}
updateFeature={updateFeature}
/>
)}
</Stack>
</>
}
90 changes: 90 additions & 0 deletions src/components/UserRateLimits.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Stack, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import { useDataProvider, useNotify, useRecordContext, useTranslate } from "react-admin";
import { TextField } from "@mui/material";
import { useFormContext } from "react-hook-form";

const RateLimitRow = ({ limit, value, updateRateLimit }: { limit: string, value: number, updateRateLimit: (limit: string, value: number) => void }) => {
const translate = useTranslate();

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
updateRateLimit(limit, parseInt(event.target.value));
};

return <Stack
spacing={1}
alignItems="start"
sx={{
padding: 2,
}}
>
<TextField
id="outlined-number"
type="number"
value={value}
onChange={handleChange}
slotProps={{
inputLabel: {
shrink: true,
},
}}
label={translate(`resources.users.limits.${limit}`)}
/>
<Stack>
<Typography
variant="body2"
color="text.secondary"
>
{translate(`resources.users.limits.${limit}_text`)}
</Typography>
</Stack>
</Stack>
}

export const UserRateLimits = () => {
const translate = useTranslate();
const notify = useNotify();
const record = useRecordContext();
const form = useFormContext();
const dataProvider = useDataProvider();
const [rateLimits, setRateLimits] = useState({
messages_per_second: 0,
burst_count: 0,
});

if (!record) {
return null;
}

useEffect(() => {
const fetchRateLimits = async () => {
const rateLimits = await dataProvider.getRateLimits(record.id);
if (Object.keys(rateLimits).length > 0) {
setRateLimits(rateLimits);
}
}

fetchRateLimits();
}, []);

const updateRateLimit = async (limit: string, value: number) => {
let updatedRateLimits = { ...rateLimits, [limit]: value };
setRateLimits(updatedRateLimits);
form.setValue(`rates.${limit}`, value, { shouldDirty: true });
};

return <>
<Stack
direction="column"
>
{Object.keys(rateLimits).map((limit: string) =>
<RateLimitRow
key={limit}
limit={limit}
value={rateLimits[limit]}
updateRateLimit={updateRateLimit}
/>
)}
</Stack>
</>
};
8 changes: 7 additions & 1 deletion src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const de: SynapseTranslationMessages = {
},
users: {
invalid_user_id: "Lokaler Anteil der Matrix Benutzer-ID ohne Homeserver.",
tabs: { sso: "SSO" },
tabs: { sso: "SSO", experimental: "Experimentell", limits: "Rate Limits" },
},
rooms: {
details: "Raumdetails",
Expand Down Expand Up @@ -192,6 +192,12 @@ const de: SynapseTranslationMessages = {
redact_events: "Schwärzen aller vom Benutzer gesendeten Ereignisse (-s)",
generate_password: "Passwort generieren",
},
limits: {
messages_per_second: "Nachrichten pro Sekunde",
messages_per_second_text: "Die Anzahl der Aktionen, die in einer Sekunde durchgeführt werden können. 0 bedeutet, dass die Rate-Limitierung für diesen Benutzer deaktiviert ist.",
burst_count: "Burst-Anzahl",
burst_count_text: "Die Anzahl der Aktionen, die vor der Begrenzung durchgeführt werden können.",
}
},
rooms: {
name: "Raum |||| Räume",
Expand Down
12 changes: 11 additions & 1 deletion src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ const en: SynapseTranslationMessages = {
},
users: {
invalid_user_id: "Localpart of a Matrix user-id without homeserver.",
tabs: { sso: "SSO" },
tabs: {
sso: "SSO",
experimental: "Experimental",
limits: "Rate Limits",
},
},
rooms: {
details: "Room details",
Expand Down Expand Up @@ -161,6 +165,12 @@ const en: SynapseTranslationMessages = {
redact_events: "Redact all events sent by the user(-s)",
generate_password: "Generate password",
},
limits: {
messages_per_second: "Messages per second",
messages_per_second_text: "The number of actions that can be performed in a second. 0 mean that ratelimiting is disabled for this user",
burst_count: "Burst count",
burst_count_text: "How many actions that can be performed before being limited.",
}
},
rooms: {
name: "Room |||| Rooms",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/fa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const fa: SynapseTranslationMessages = {
},
users: {
invalid_user_id: "بخش محلی یک شناسه کاربری ماتریکس بدون سرور خانگی.",
tabs: { sso: "SSO" },
tabs: { sso: "SSO", experimental: "تجربی", limits: "محدودیت ها" },
},
rooms: {
tabs: {
Expand Down Expand Up @@ -157,6 +157,12 @@ const fa: SynapseTranslationMessages = {
redact_events: "تنقيح جميع الأحداث المرسلة من قبل المستخدم (-s)",
generate_password: "توليد رمز عبور",
},
limits: {
messages_per_second: "پیام در ثانیه",
messages_per_second_text: "تعداد عملیاتی که می تواند در یک ثانیه انجام شود. 0 به معنای غیرفعال کردن محدودیت برای این کاربر است.",
burst_count: "تعداد پیچیدگی",
burst_count_text: "تعداد عملیاتی که می تواند قبل از محدودیت انجام شود.",
}
},
rooms: {
name: "اتاق |||| اتاق ها",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const fr: SynapseTranslationMessages = {
},
users: {
invalid_user_id: "Partie locale d'un identifiant utilisateur Matrix sans le nom du serveur d’accueil.",
tabs: { sso: "Authentification unique" },
tabs: { sso: "Authentification unique", experimental: "Expérimental", limits: "Limites" },
},
rooms: {
tabs: {
Expand Down Expand Up @@ -159,6 +159,12 @@ const fr: SynapseTranslationMessages = {
redact_events: "Expurger tous les événements envoyés par l'utilisateur(-s)",
generate_password: "Générer un mot de passe",
},
limits: {
messages_per_second: "Messages par seconde",
messages_per_second_text: "Le nombre d'actions que l'utilisateur peut effectuer par seconde. 0 signifie que la limitation est désactivée pour cet utilisateur.",
burst_count: "Compteur de pics",
burst_count_text: "Le nombre d'actions que l'utilisateur peut effectuer avant d'être limité.",
}
},
rooms: {
name: "Salon |||| Salons",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface SynapseTranslationMessages extends TranslationMessages {
};
users: {
invalid_user_id: string;
tabs: { sso: string };
tabs: { sso: string; experimental: string; limits: string; };
};
rooms: {
details?: string; // TODO: fa, fr, it, zh
Expand Down Expand Up @@ -157,6 +157,12 @@ interface SynapseTranslationMessages extends TranslationMessages {
redact_events: string;
generate_password: string;
};
limits: {
messages_per_second: string;
messages_per_second_text: string;
burst_count: string;
burst_count_text: string;
};
};
rooms: {
name: string;
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const it: SynapseTranslationMessages = {
},
users: {
invalid_user_id: "ID utente non valido su questo homeserver.",
tabs: { sso: "SSO" },
tabs: { sso: "SSO", experimental: "Sperimentale", limits: "Limiti" },
},
rooms: {
tabs: {
Expand Down Expand Up @@ -158,6 +158,12 @@ const it: SynapseTranslationMessages = {
redact_events: "Ridurre tutti gli eventi inviati dall'utente(-s)",
generate_password: "Genera password",
},
limits: {
messages_per_second: "Messaggi al secondo",
messages_per_second_text: "Il numero di azioni che l'utente può eseguire al secondo. 0 significa che la limitazione è disabilitata per questo utente.",
burst_count: "Burst-conteggio",
burst_count_text: "Il numero di azioni che l'utente può eseguire prima di essere limitato.",
}
},
rooms: {
name: "Stanza |||| Stanze",
Expand Down
Loading

0 comments on commit 9adc13e

Please sign in to comment.