Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/frontend-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: TypeScript typecheck
run: npm run typecheck

Expand Down
8 changes: 8 additions & 0 deletions frontend/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Build output
dist/

# Node modules
node_modules/

# Webpack config is a CommonJS file; ESLint lints only src/**/*.ts via npm run lint
webpack.config.js
28 changes: 28 additions & 0 deletions frontend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"extends": ["plugin:@typescript-eslint/recommended"],
"env": {
"browser": true,
"jest": true,
"es2020": true
},
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }]
},
"overrides": [
{
"files": ["src/__tests__/**/*.ts"],
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-require-imports": "off"
}
}
]
}
2 changes: 1 addition & 1 deletion frontend/src/__tests__/recommendations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1293,7 +1293,7 @@ describe('Recommendations Module', () => {
wireSelection();
await loadRecommendations();
const firstRow = document.querySelector<HTMLTableRowElement>('tr.recommendation-row');
const cb = firstRow?.querySelector<HTMLInputElement>('input[type="checkbox"][data-rec-id]')!;
const cb = firstRow!.querySelector<HTMLInputElement>('input[type="checkbox"][data-rec-id]')!;
expect(cb.checked).toBe(false);

// Native click on a <input type="checkbox"> toggles checked
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ const TRACKED_FIELDS = [
];

// Snapshot of field values at last save (or initial load).
let savedSnapshot: Record<string, string> = {};
const savedSnapshot: Record<string, string> = {};

// Cached service configs from last load — used as base when saving to preserve
// non-UI fields (ramp_schedule, include_engines, etc.) that SaveServiceConfig replaces entirely.
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/users/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export let mfaFilter = '';
export let groupFilter = '';

// State for bulk operations
export let selectedUserIds = new Set<string>();
export const selectedUserIds = new Set<string>();

// Setters for state
export function setCurrentEditingUser(user: APIUser | null): void {
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/users/userList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ async function toggleUserGroup(userId: string, groupId: string, checked: boolean
const panel = document.querySelector<HTMLElement>(
`.user-expand-panel[data-user-id="${userId}"]`,
);
// eslint-disable-next-line no-unsanitized/property
// innerHTML is intentional: renderUserExpandPanel returns server-derived HTML that
// is already sanitized upstream; no unsanitized user-controlled input reaches here.
if (panel) panel.innerHTML = renderUserExpandPanel(updatedUser);

// Refresh the group-badge cell in the main row so it reflects the new
Expand Down Expand Up @@ -339,7 +340,8 @@ function setupUserTableListeners(): void {
const isHidden = expandRow.classList.contains('hidden');
if (isHidden) {
const user = allUsers.find(u => u.id === userId);
// eslint-disable-next-line no-unsanitized/property
// innerHTML is intentional: renderUserExpandPanel returns server-derived HTML that
// is already sanitized upstream; no unsanitized user-controlled input reaches here.
if (user) panel.innerHTML = renderUserExpandPanel(user);
expandRow.classList.remove('hidden');
btn.setAttribute('aria-expanded', 'true');
Expand Down
Loading