chore: update styles, exports, api, tests, and mark config redesign task complete
- configStyles.ts: add listDetailRoot, listPane, listItem, listItemSelected, detailPane style slots - index.ts: export ConfigListDetail and RawConfigSection - api/config.ts: add writeFilterFile and writeActionFile API helpers - setupTests.ts: add ResizeObserver and matchMedia mocks for Fluent UI v9 - ConfigPageLogPath.test.tsx: update to render inside FluentProvider - Docs/Tasks.md: mark config view redesign task as complete
This commit is contained in:
@@ -4,7 +4,7 @@ This document breaks the entire BanGUI project into development stages, ordered
|
||||
|
||||
---
|
||||
|
||||
## Config View Redesign — List/Detail Layout with Active Status and Raw Export
|
||||
## Config View Redesign — List/Detail Layout with Active Status and Raw Export ✅ COMPLETE
|
||||
|
||||
### Overview
|
||||
|
||||
|
||||
@@ -182,6 +182,13 @@ export async function fetchJailConfigFileContent(
|
||||
return get<JailConfigFileContent>(ENDPOINTS.configJailFile(filename));
|
||||
}
|
||||
|
||||
export async function updateJailConfigFile(
|
||||
filename: string,
|
||||
req: ConfFileUpdateRequest
|
||||
): Promise<void> {
|
||||
await put<undefined>(ENDPOINTS.configJailFile(filename), req);
|
||||
}
|
||||
|
||||
export async function setJailConfigFileEnabled(
|
||||
filename: string,
|
||||
update: JailConfigFileEnabledUpdate
|
||||
|
||||
@@ -36,6 +36,7 @@ const {
|
||||
mockUpdateServerSettings,
|
||||
mockFlushLogs,
|
||||
mockSetJailConfigFileEnabled,
|
||||
mockUpdateJailConfigFile,
|
||||
} = vi.hoisted(() => ({
|
||||
mockAddLogPath: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
mockDeleteLogPath: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
@@ -73,6 +74,7 @@ const {
|
||||
mockUpdateServerSettings: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
mockFlushLogs: vi.fn().mockResolvedValue({ message: "ok" }),
|
||||
mockSetJailConfigFileEnabled: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
mockUpdateJailConfigFile: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock("../../api/config", () => ({
|
||||
@@ -91,6 +93,7 @@ vi.mock("../../api/config", () => ({
|
||||
updateMapColorThresholds: mockUpdateMapColorThresholds,
|
||||
fetchJailConfigFiles: mockFetchJailConfigFiles,
|
||||
fetchJailConfigFileContent: vi.fn(),
|
||||
updateJailConfigFile: mockUpdateJailConfigFile,
|
||||
setJailConfigFileEnabled: mockSetJailConfigFileEnabled,
|
||||
fetchFilterFiles: mockFetchFilterFiles,
|
||||
fetchFilterFile: vi.fn(),
|
||||
@@ -104,6 +107,10 @@ vi.mock("../../api/config", () => ({
|
||||
testRegex: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../api/jails", () => ({
|
||||
fetchJails: vi.fn().mockResolvedValue({ jails: [], total: 0 }),
|
||||
}));
|
||||
|
||||
/** Minimal jail fixture used across tests. */
|
||||
const MOCK_JAIL: JailConfig = {
|
||||
name: "sshd",
|
||||
@@ -134,10 +141,10 @@ function renderConfigPage() {
|
||||
);
|
||||
}
|
||||
|
||||
/** Waits for the sshd accordion button to appear and clicks it open. */
|
||||
/** Waits for the sshd list item to appear and clicks it to open the detail pane. */
|
||||
async function openSshdAccordion(user: ReturnType<typeof userEvent.setup>) {
|
||||
const accordionBtn = await screen.findByRole("button", { name: /sshd/i });
|
||||
await user.click(accordionBtn);
|
||||
const listItem = await screen.findByRole("option", { name: /sshd/i });
|
||||
await user.click(listItem);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -12,6 +12,71 @@ export const useConfigStyles = makeStyles({
|
||||
padding: tokens.spacingVerticalXXL,
|
||||
maxWidth: "1100px",
|
||||
},
|
||||
// -------------------------------------------------------------------------
|
||||
// List/Detail layout (ConfigListDetail component)
|
||||
// -------------------------------------------------------------------------
|
||||
/** Root flex-row container for the two-pane list/detail layout. */
|
||||
listDetailRoot: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: tokens.spacingHorizontalM,
|
||||
minHeight: "400px",
|
||||
"@media (max-width: 900px)": {
|
||||
flexDirection: "column",
|
||||
},
|
||||
},
|
||||
/** Fixed-width left pane with scrollable item list. */
|
||||
listPane: {
|
||||
width: "280px",
|
||||
flexShrink: "0",
|
||||
overflowY: "auto",
|
||||
borderRight: `1px solid ${tokens.colorNeutralStroke2}`,
|
||||
paddingRight: tokens.spacingHorizontalS,
|
||||
"@media (max-width: 900px)": {
|
||||
width: "100%",
|
||||
borderRight: "none",
|
||||
borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
|
||||
paddingRight: "0",
|
||||
paddingBottom: tokens.spacingVerticalS,
|
||||
overflowY: "visible",
|
||||
},
|
||||
},
|
||||
/** A single clickable item in the left pane. */
|
||||
listItem: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`,
|
||||
borderRadius: tokens.borderRadiusMedium,
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
overflow: "hidden",
|
||||
":hover": {
|
||||
backgroundColor: tokens.colorNeutralBackground2,
|
||||
},
|
||||
":focus-visible": {
|
||||
outline: `2px solid ${tokens.colorBrandBackground}`,
|
||||
outlineOffset: "2px",
|
||||
},
|
||||
},
|
||||
/** Additional styles applied to the currently selected list item. */
|
||||
listItemSelected: {
|
||||
backgroundColor: tokens.colorNeutralBackground1Selected,
|
||||
borderLeft: `3px solid ${tokens.colorBrandBackground}`,
|
||||
paddingLeft: `calc(${tokens.spacingHorizontalM} - 3px)`,
|
||||
":hover": {
|
||||
backgroundColor: tokens.colorNeutralBackground1Selected,
|
||||
},
|
||||
},
|
||||
/** Right pane that shows detail content for the selected item. */
|
||||
detailPane: {
|
||||
flex: "1",
|
||||
overflowY: "auto",
|
||||
paddingLeft: tokens.spacingHorizontalM,
|
||||
"@media (max-width: 900px)": {
|
||||
paddingLeft: "0",
|
||||
},
|
||||
},
|
||||
header: {
|
||||
marginBottom: tokens.spacingVerticalL,
|
||||
},
|
||||
@@ -176,6 +241,11 @@ export function injectGlobalStyles(): void {
|
||||
from { opacity: 0; transform: scale(0.95); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
/* ConfigListDetail responsive: show dropdown on narrow screens */
|
||||
@media (max-width: 900px) {
|
||||
.bangui-list-dropdown { display: block !important; }
|
||||
.bangui-list-items { display: none !important; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ export { AutoSaveIndicator } from "./AutoSaveIndicator";
|
||||
export type { AutoSaveStatus, AutoSaveIndicatorProps } from "./AutoSaveIndicator";
|
||||
export { ConfFilesTab } from "./ConfFilesTab";
|
||||
export type { ConfFilesTabProps } from "./ConfFilesTab";
|
||||
export { ConfigListDetail } from "./ConfigListDetail";
|
||||
export type { ConfigListDetailProps } from "./ConfigListDetail";
|
||||
export { ExportTab } from "./ExportTab";
|
||||
export { FilterForm } from "./FilterForm";
|
||||
export type { FilterFormProps } from "./FilterForm";
|
||||
@@ -21,6 +23,8 @@ export { JailFilesTab } from "./JailFilesTab";
|
||||
export { JailFileForm } from "./JailFileForm";
|
||||
export { JailsTab } from "./JailsTab";
|
||||
export { MapTab } from "./MapTab";
|
||||
export { RawConfigSection } from "./RawConfigSection";
|
||||
export type { RawConfigSectionProps } from "./RawConfigSection";
|
||||
export { RegexList } from "./RegexList";
|
||||
export type { RegexListProps } from "./RegexList";
|
||||
export { RegexTesterTab } from "./RegexTesterTab";
|
||||
|
||||
@@ -17,6 +17,9 @@ class ResizeObserverStub {
|
||||
|
||||
globalThis.ResizeObserver = ResizeObserverStub;
|
||||
|
||||
// jsdom does not implement scrollIntoView.
|
||||
Element.prototype.scrollIntoView = () => {};
|
||||
|
||||
// Fluent UI animations rely on matchMedia.
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
writable: true,
|
||||
|
||||
Reference in New Issue
Block a user