Refactor frontend components and dependencies
- Update ESLint configuration for frontend - Refactor dialog components (ActivateJail, CreateAction, CreateFilter, CreateJail) - Update JailsTab and RegexTesterTab components - Refactor TopCountriesPieChart component - Update package.json dependencies - Update documentation (Tasks.md) - Refactor CodeList component for jail page Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -611,7 +611,11 @@ Do **not** add a Content-Security-Policy meta tag; CSP should be set as an HTTP
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### TASK-030 — Add manual chunk splitting to `vite.config.ts`
|
### TASK-030 — Add manual chunk splitting to `vite.config.ts` (done)
|
||||||
|
|
||||||
|
**Where fixed:** `frontend/vite.config.ts`
|
||||||
|
|
||||||
|
**Summary:** Implemented `build.rollupOptions.output.manualChunks` function that groups modules into four vendor chunks: `react-vendor`, `ui-vendor`, `chart-vendor`, and `geo-vendor`. This ensures stable, cacheable chunks across deployments.
|
||||||
|
|
||||||
**Where found:** `frontend/vite.config.ts` — `build` section is absent entirely; Rollup uses its default single-chunk strategy.
|
**Where found:** `frontend/vite.config.ts` — `build` section is absent entirely; Rollup uses its default single-chunk strategy.
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import js from "@eslint/js";
|
import js from "@eslint/js";
|
||||||
import tseslint from "typescript-eslint";
|
import tseslint from "typescript-eslint";
|
||||||
import reactHooks from "eslint-plugin-react-hooks";
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
|
import reactPlugin from "eslint-plugin-react";
|
||||||
import prettierConfig from "eslint-config-prettier";
|
import prettierConfig from "eslint-config-prettier";
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{ ignores: ["dist", "eslint.config.ts"] },
|
{ ignores: ["dist", "eslint.config.ts", ".vite"] },
|
||||||
{
|
{
|
||||||
extends: [js.configs.recommended, ...tseslint.configs.strictTypeChecked],
|
extends: [js.configs.recommended, ...tseslint.configs.strictTypeChecked],
|
||||||
files: ["**/*.{ts,tsx}"],
|
files: ["**/*.{ts,tsx}"],
|
||||||
@@ -15,10 +16,21 @@ export default tseslint.config(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
|
react: reactPlugin,
|
||||||
"react-hooks": reactHooks,
|
"react-hooks": reactHooks,
|
||||||
},
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...reactHooks.configs.recommended.rules,
|
...reactHooks.configs.recommended.rules,
|
||||||
|
"@typescript-eslint/no-floating-promises": "error",
|
||||||
|
"@typescript-eslint/no-misused-promises": "error",
|
||||||
|
"react/no-array-index-key": "warn",
|
||||||
|
"react/no-unstable-nested-components": "error",
|
||||||
|
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"@typescript-eslint/explicit-function-return-type": "warn",
|
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
||||||
|
|||||||
1997
frontend/package-lock.json
generated
1997
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,26 +21,27 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^6.27.0",
|
"react-router-dom": "^6.27.0",
|
||||||
|
"recharts": "^3.8.0",
|
||||||
"topojson-client": "^3.1.0",
|
"topojson-client": "^3.1.0",
|
||||||
"world-atlas": "^2.0.2",
|
"world-atlas": "^2.0.2"
|
||||||
"recharts": "^3.8.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/d3-geo": "^3.1.0",
|
|
||||||
"@types/topojson-client": "^3.0.0",
|
|
||||||
"@eslint/js": "^9.13.0",
|
"@eslint/js": "^9.13.0",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
|
"@types/d3-geo": "^3.1.0",
|
||||||
"@types/node": "^25.3.2",
|
"@types/node": "^25.3.2",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
|
"@types/topojson-client": "^3.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.13.0",
|
"@typescript-eslint/eslint-plugin": "^8.13.0",
|
||||||
"@typescript-eslint/parser": "^8.13.0",
|
"@typescript-eslint/parser": "^8.13.0",
|
||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@vitejs/plugin-react": "^4.3.3",
|
||||||
"@vitest/coverage-v8": "^4.0.18",
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
"eslint": "^9.13.0",
|
"eslint": "^9.13.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^28.1.0",
|
||||||
|
|||||||
@@ -188,9 +188,9 @@ export const TopCountriesPieChart = memo(function TopCountriesPieChart({
|
|||||||
}}
|
}}
|
||||||
labelLine={false}
|
labelLine={false}
|
||||||
>
|
>
|
||||||
{slices.map((slice, index) => (
|
{slices.map((slice) => (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||||
<Cell key={index} fill={slice.fill} />
|
<Cell key={slice.name} fill={slice.fill} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip content={PieTooltip} />
|
<Tooltip content={PieTooltip} />
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ export function ActivateJailDialog({
|
|||||||
<strong>Configuration errors detected:</strong>
|
<strong>Configuration errors detected:</strong>
|
||||||
<ul style={{ margin: `${tokens.spacingVerticalXS} 0 0 0`, paddingLeft: "1.2em" }}>
|
<ul style={{ margin: `${tokens.spacingVerticalXS} 0 0 0`, paddingLeft: "1.2em" }}>
|
||||||
{blockingIssues.map((issue, idx) => (
|
{blockingIssues.map((issue, idx) => (
|
||||||
<li key={idx}><em>{issue.field}:</em> {issue.message}</li>
|
<li key={issue.field + "-" + String(idx)}><em>{issue.field}:</em> {issue.message}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</MessageBarBody>
|
</MessageBarBody>
|
||||||
@@ -242,7 +242,7 @@ export function ActivateJailDialog({
|
|||||||
<strong>Advisory warnings:</strong>
|
<strong>Advisory warnings:</strong>
|
||||||
<ul style={{ margin: `${tokens.spacingVerticalXS} 0 0 0`, paddingLeft: "1.2em" }}>
|
<ul style={{ margin: `${tokens.spacingVerticalXS} 0 0 0`, paddingLeft: "1.2em" }}>
|
||||||
{advisoryIssues.map((issue, idx) => (
|
{advisoryIssues.map((issue, idx) => (
|
||||||
<li key={idx}><em>{issue.field}:</em> {issue.message}</li>
|
<li key={issue.field + "-" + String(idx)}><em>{issue.field}:</em> {issue.message}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</MessageBarBody>
|
</MessageBarBody>
|
||||||
@@ -257,16 +257,12 @@ export function ActivateJailDialog({
|
|||||||
<strong>Post-activation warnings:</strong>
|
<strong>Post-activation warnings:</strong>
|
||||||
<ul style={{ margin: `${tokens.spacingVerticalXS} 0 0 0`, paddingLeft: "1.2em" }}>
|
<ul style={{ margin: `${tokens.spacingVerticalXS} 0 0 0`, paddingLeft: "1.2em" }}>
|
||||||
{validationWarnings.map((w, idx) => (
|
{validationWarnings.map((w, idx) => (
|
||||||
<li key={idx}>{w}</li>
|
<li key={w + "-" + String(idx)}>{w}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</MessageBarBody>
|
</MessageBarBody>
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Text block weight="semibold" style={{ marginBottom: tokens.spacingVerticalS }}>
|
|
||||||
Override values (leave blank to use config defaults)
|
|
||||||
</Text>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export function CreateActionDialog({
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
});
|
});
|
||||||
}, [name, actionban, actionunban, submitting, onCreate]);
|
}, [name, actionban, actionunban, submitting, onCreate, onCreateAction]);
|
||||||
|
|
||||||
const canConfirm = name.trim() !== "" && !submitting;
|
const canConfirm = name.trim() !== "" && !submitting;
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export function CreateFilterDialog({
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
});
|
});
|
||||||
}, [name, failregex, ignoreregex, submitting, onCreate]);
|
}, [name, failregex, ignoreregex, submitting, onCreate, onCreateFilter]);
|
||||||
|
|
||||||
const canConfirm = name.trim() !== "" && !submitting;
|
const canConfirm = name.trim() !== "" && !submitting;
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function CreateJailDialog({
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
});
|
});
|
||||||
}, [name, submitting, onCreated]);
|
}, [name, submitting, onCreated, onCreateJail]);
|
||||||
|
|
||||||
const canConfirm = name.trim() !== "" && !submitting;
|
const canConfirm = name.trim() !== "" && !submitting;
|
||||||
|
|
||||||
|
|||||||
@@ -714,7 +714,7 @@ function InactiveJailDetail({
|
|||||||
<strong>Errors:</strong>
|
<strong>Errors:</strong>
|
||||||
<ul style={{ margin: `4px 0 0 0`, paddingLeft: "1.2em" }}>
|
<ul style={{ margin: `4px 0 0 0`, paddingLeft: "1.2em" }}>
|
||||||
{blockingIssues.map((issue, idx) => (
|
{blockingIssues.map((issue, idx) => (
|
||||||
<li key={idx}><em>{issue.field}:</em> {issue.message}</li>
|
<li key={`${issue.field}-${String(idx)}`}><em>{issue.field}:</em> {issue.message}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</MessageBarBody>
|
</MessageBarBody>
|
||||||
@@ -726,7 +726,7 @@ function InactiveJailDetail({
|
|||||||
<strong>Warnings:</strong>
|
<strong>Warnings:</strong>
|
||||||
<ul style={{ margin: `4px 0 0 0`, paddingLeft: "1.2em" }}>
|
<ul style={{ margin: `4px 0 0 0`, paddingLeft: "1.2em" }}>
|
||||||
{advisoryIssues.map((issue, idx) => (
|
{advisoryIssues.map((issue, idx) => (
|
||||||
<li key={idx}><em>{issue.field}:</em> {issue.message}</li>
|
<li key={`${issue.field}-${String(idx)}`}><em>{issue.field}:</em> {issue.message}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</MessageBarBody>
|
</MessageBarBody>
|
||||||
@@ -814,7 +814,6 @@ export function JailsTab({ initialJail }: JailsTabProps): React.JSX.Element {
|
|||||||
refreshInactiveJails();
|
refreshInactiveJails();
|
||||||
}, [refresh, refreshInactiveJails]);
|
}, [refresh, refreshInactiveJails]);
|
||||||
|
|
||||||
/** Unified list items: active jails first (from useJailConfigs), then inactive. */
|
|
||||||
const listItems = useMemo<Array<{ name: string; kind: "active" | "inactive" }>>(() => {
|
const listItems = useMemo<Array<{ name: string; kind: "active" | "inactive" }>>(() => {
|
||||||
const activeItems = jails.map((j) => ({ name: j.name, kind: "active" as const }));
|
const activeItems = jails.map((j) => ({ name: j.name, kind: "active" as const }));
|
||||||
const activeNames = new Set(jails.map((j) => j.name));
|
const activeNames = new Set(jails.map((j) => j.name));
|
||||||
@@ -913,7 +912,7 @@ export function JailsTab({ initialJail }: JailsTabProps): React.JSX.Element {
|
|||||||
key={selectedActiveJail.name}
|
key={selectedActiveJail.name}
|
||||||
jail={selectedActiveJail}
|
jail={selectedActiveJail}
|
||||||
onSave={updateJail}
|
onSave={updateJail}
|
||||||
onDeactivate={() => { handleDeactivate(selectedActiveJail.name); }}
|
onDeactivate={() => { void handleDeactivate(selectedActiveJail.name); }}
|
||||||
/>
|
/>
|
||||||
) : selectedInactiveJail !== undefined ? (
|
) : selectedInactiveJail !== undefined ? (
|
||||||
<InactiveJailDetail
|
<InactiveJailDetail
|
||||||
@@ -922,10 +921,10 @@ export function JailsTab({ initialJail }: JailsTabProps): React.JSX.Element {
|
|||||||
onActivate={() => { setActivateTarget(selectedInactiveJail); }}
|
onActivate={() => { setActivateTarget(selectedInactiveJail); }}
|
||||||
onDeactivate={
|
onDeactivate={
|
||||||
selectedInactiveJail.has_local_override
|
selectedInactiveJail.has_local_override
|
||||||
? (): void => { handleDeactivateInactive(selectedInactiveJail.name); }
|
? (): void => { void handleDeactivateInactive(selectedInactiveJail.name); }
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onValidate={() => validateJailConfig(selectedInactiveJail.name)}
|
onValidate={() => { void validateJailConfig(selectedInactiveJail.name); }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</ConfigListDetail>
|
</ConfigListDetail>
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export function RegexTesterTab(): React.JSX.Element {
|
|||||||
</Text>
|
</Text>
|
||||||
{result.groups.map((g, i) => (
|
{result.groups.map((g, i) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={i}
|
key={`${g}-${String(i)}`}
|
||||||
appearance="tint"
|
appearance="tint"
|
||||||
color="informative"
|
color="informative"
|
||||||
style={{ marginLeft: tokens.spacingHorizontalXS }}
|
style={{ marginLeft: tokens.spacingHorizontalXS }}
|
||||||
@@ -206,7 +206,7 @@ export function RegexTesterTab(): React.JSX.Element {
|
|||||||
<div className={styles.previewArea}>
|
<div className={styles.previewArea}>
|
||||||
{preview.lines.map((ln, idx) => (
|
{preview.lines.map((ln, idx) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={ln.line + "-" + String(idx)}
|
||||||
className={`${styles.logLine} ${ln.matched ? styles.matched : styles.notMatched}`}
|
className={`${styles.logLine} ${ln.matched ? styles.matched : styles.notMatched}`}
|
||||||
>
|
>
|
||||||
{ln.line}
|
{ln.line}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function CodeList({ items, empty }: CodeListProps): React.JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.codeList}>
|
<div className={styles.codeList}>
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<span key={i} className={styles.codeItem}>
|
<span key={`${item}-${String(i)}`} className={styles.codeItem}>
|
||||||
{item}
|
{item}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user