diff --git a/.github/workflows/ui-lint.yml b/.github/workflows/ui-lint.yml index 492a5fe..31e9854 100644 --- a/.github/workflows/ui-lint.yml +++ b/.github/workflows/ui-lint.yml @@ -7,21 +7,23 @@ on: - "package.json" - "package-lock.json" - ".github/workflows/ui-lint.yml" + pull_request: permissions: contents: read + pull-requests: read jobs: ui-lint: name: UI Lint - runs-on: buildjet-4vcpu-ubuntu-2204 + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: v21.1.0 + node-version: v22.15.0 cache: "npm" cache-dependency-path: "ui/package-lock.json" - name: Install dependencies diff --git a/ui/eslint.config.cjs b/ui/eslint.config.cjs index a6c0c1f..e8f433e 100644 --- a/ui/eslint.config.cjs +++ b/ui/eslint.config.cjs @@ -1,48 +1,44 @@ -const { - defineConfig, - globalIgnores, -} = require("eslint/config"); +const { defineConfig, globalIgnores } = require("eslint/config"); +const eslintPluginPrettierRecommended = require("eslint-plugin-prettier/recommended"); const globals = require("globals"); -const { - fixupConfigRules, -} = require("@eslint/compat"); +const { fixupConfigRules } = require("@eslint/compat"); const tsParser = require("@typescript-eslint/parser"); const reactRefresh = require("eslint-plugin-react-refresh"); const js = require("@eslint/js"); -const { - FlatCompat, -} = require("@eslint/eslintrc"); +const { FlatCompat } = require("@eslint/eslintrc"); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, }); -module.exports = defineConfig([{ +module.exports = defineConfig([ + { languageOptions: { - globals: { - ...globals.browser, - }, + globals: { + ...globals.browser, + }, - parser: tsParser, - ecmaVersion: "latest", - sourceType: "module", + parser: tsParser, + ecmaVersion: "latest", + sourceType: "module", - parserOptions: { - project: ["./tsconfig.json", "./tsconfig.node.json"], - tsconfigRootDir: __dirname, - ecmaFeatures: { - jsx: true - } + parserOptions: { + project: ["./tsconfig.json", "./tsconfig.node.json"], + tsconfigRootDir: __dirname, + ecmaFeatures: { + jsx: true, }, + }, }, - extends: fixupConfigRules(compat.extends( + extends: fixupConfigRules( + compat.extends( "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/stylistic", @@ -51,43 +47,53 @@ module.exports = defineConfig([{ "plugin:react/jsx-runtime", "plugin:import/recommended", "prettier", - )), + ), + ), plugins: { - "react-refresh": reactRefresh, + "react-refresh": reactRefresh, }, rules: { - "react-refresh/only-export-components": ["warn", { - allowConstantExport: true, - }], + "react-refresh/only-export-components": [ + "warn", + { + allowConstantExport: true, + }, + ], - "import/order": ["error", { - groups: ["builtin", "external", "internal", "parent", "sibling"], - "newlines-between": "always", - }], + "import/order": [ + "error", + { + groups: ["builtin", "external", "internal", "parent", "sibling"], + "newlines-between": "always", + }, + ], }, settings: { - "react": { - "version": "detect" - }, - "import/resolver": { - alias: { - map: [ - ["@components", "./src/components"], - ["@routes", "./src/routes"], - ["@assets", "./src/assets"], - ["@", "./src"], - ], + react: { + version: "detect", + }, + "import/resolver": { + alias: { + map: [ + ["@components", "./src/components"], + ["@routes", "./src/routes"], + ["@assets", "./src/assets"], + ["@", "./src"], + ], - extensions: [".ts", ".tsx", ".js", ".jsx", ".json"], - }, + extensions: [".ts", ".tsx", ".js", ".jsx", ".json"], }, + }, }, -}, globalIgnores([ + }, + globalIgnores([ "**/dist", "**/.eslintrc.cjs", "**/tailwind.config.js", "**/postcss.config.js", -])]); + ]), + eslintPluginPrettierRecommended, +]); diff --git a/ui/package-lock.json b/ui/package-lock.json index 8ac57a1..1541770 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -59,12 +59,13 @@ "eslint": "^9.26.0", "eslint-config-prettier": "^10.1.5", "eslint-plugin-import": "^2.31.0", + "eslint-plugin-prettier": "^5.4.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.1.0", "postcss": "^8.5.3", - "prettier": "^3.5.3", + "prettier": "3.5.3", "prettier-plugin-tailwindcss": "^0.6.11", "tailwindcss": "^4.1.7", "typescript": "^5.8.3", @@ -919,6 +920,19 @@ "node": ">= 8" } }, + "node_modules/@pkgr/core": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz", + "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@react-aria/focus": { "version": "3.20.3", "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.3.tgz", @@ -3539,6 +3553,37 @@ "semver": "bin/semver.js" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz", + "integrity": "sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-react": { "version": "7.37.5", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", @@ -3746,6 +3791,13 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/fast-equals": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", @@ -5554,6 +5606,19 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/prettier-plugin-tailwindcss": { "version": "0.6.11", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz", @@ -6370,6 +6435,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.6.tgz", + "integrity": "sha512-2pR2ubZSV64f/vqm9eLPz/KOvR9Dm+Co/5ChLgeHl0yEDRc6h5hXHoxEQH8Y5Ljycozd3p1k5TTSVdzYGkPvLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", diff --git a/ui/package.json b/ui/package.json index eb9a9a3..9dffedf 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,8 +14,8 @@ "build:device": "tsc && vite build --mode=device --emptyOutDir", "build:staging": "tsc && vite build --mode=cloud-staging", "build:prod": "tsc && vite build --mode=cloud-production", - "lint": "eslint './src/**/*.{ts,tsx}'", - "lint:fix": "eslint './src/**/*.{ts,tsx}' --fix", + "lint": "eslint './src/**/*.{ts,tsx,css}'", + "lint:fix": "eslint './src/**/*.{ts,tsx,css}' --fix", "preview": "vite preview" }, "dependencies": { @@ -70,12 +70,13 @@ "eslint": "^9.26.0", "eslint-config-prettier": "^10.1.5", "eslint-plugin-import": "^2.31.0", + "eslint-plugin-prettier": "^5.4.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.1.0", "postcss": "^8.5.3", - "prettier": "^3.5.3", + "prettier": "3.5.3", "prettier-plugin-tailwindcss": "^0.6.11", "tailwindcss": "^4.1.7", "typescript": "^5.8.3", diff --git a/ui/src/components/Button.tsx b/ui/src/components/Button.tsx index 97fcc5f..638aa5b 100644 --- a/ui/src/components/Button.tsx +++ b/ui/src/components/Button.tsx @@ -16,7 +16,7 @@ const sizes = { const themes = { primary: cx( // Base styles - "bg-blue-700 dark:border-blue-600 border border-blue-900/60 text-white shadow-sm", + "border border-blue-900/60 bg-blue-700 text-white shadow-sm dark:border-blue-600", // Hover states "group-hover:bg-blue-800", // Active states @@ -24,9 +24,9 @@ const themes = { ), danger: cx( // Base styles - "bg-red-600 text-white border-red-700 shadow-xs shadow-red-200/80 dark:border-red-600 dark:shadow-red-900/20", + "border-red-700 bg-red-600 text-white shadow-xs shadow-red-200/80 dark:border-red-600 dark:shadow-red-900/20", // Hover states - "group-hover:bg-red-700 group-hover:border-red-800 dark:group-hover:bg-red-700 dark:group-hover:border-red-600", + "group-hover:border-red-800 group-hover:bg-red-700 dark:group-hover:border-red-600 dark:group-hover:bg-red-700", // Active states "group-active:bg-red-800 dark:group-active:bg-red-800", // Focus states @@ -34,7 +34,7 @@ const themes = { ), light: cx( // Base styles - "bg-white text-black border-slate-800/30 shadow-xs dark:bg-slate-800 dark:border-slate-300/20 dark:text-white", + "border-slate-800/30 bg-white text-black shadow-xs dark:border-slate-300/20 dark:bg-slate-800 dark:text-white", // Hover states "group-hover:bg-blue-50/80 dark:group-hover:bg-slate-700", // Active states @@ -44,7 +44,7 @@ const themes = { ), lightDanger: cx( // Base styles - "bg-white text-black border-red-400/60 shadow-xs", + "border-red-400/60 bg-white text-black shadow-xs", // Hover states "group-hover:bg-red-50/80", // Active states @@ -54,9 +54,9 @@ const themes = { ), blank: cx( // Base styles - "bg-white/0 text-black border-transparent dark:text-white", + "border-transparent bg-white/0 text-black dark:text-white", // Hover states - "group-hover:bg-white group-hover:border-slate-800/30 group-hover:shadow-sm dark:group-hover:bg-slate-700 dark:group-hover:border-slate-600", + "group-hover:border-slate-800/30 group-hover:bg-white group-hover:shadow-sm dark:group-hover:border-slate-600 dark:group-hover:bg-slate-700", // Active states "group-active:bg-slate-100/80", ), @@ -65,16 +65,16 @@ const themes = { const btnVariants = cva({ base: cx( // Base styles - "border rounded-sm select-none", + "rounded-sm border select-none", // Size classes - "justify-center items-center shrink-0", + "shrink-0 items-center justify-center", // Transition classes "outline-hidden transition-all duration-200", // Text classes - "font-display text-center font-medium leading-tight", + "text-center font-display leading-tight font-medium", // States - "group-focus:outline-hidden group-focus:ring-2 group-focus:ring-offset-2 group-focus:ring-blue-700", - "group-disabled:opacity-50 group-disabled:pointer-events-none", + "group-focus:ring-2 group-focus:ring-blue-700 group-focus:ring-offset-2 group-focus:outline-hidden", + "group-disabled:pointer-events-none group-disabled:opacity-50", ), variants: { @@ -241,7 +241,7 @@ type LabelPropsType = Pick & React.ComponentProps & { disabled?: boolean }; export const LabelButton = ({ htmlFor, ...props }: LabelPropsType) => { const classes = cx( - "group outline-hidden block cursor-pointer", + "group block cursor-pointer outline-hidden", props.disabled ? "pointer-events-none opacity-70!" : "", props.fullWidth ? "w-full" : "", props.loading ? "pointer-events-none" : "", diff --git a/ui/src/components/CardHeader.tsx b/ui/src/components/CardHeader.tsx index c9ed3ce..5c3f325 100644 --- a/ui/src/components/CardHeader.tsx +++ b/ui/src/components/CardHeader.tsx @@ -8,10 +8,14 @@ interface Props { export const CardHeader = ({ headline, description, Button }: Props) => { return ( -
-
-

{headline}

- {description &&
{description}
} +
+
+

+ {headline} +

+ {description && ( +
{description}
+ )}
{Button &&
{Button}
}
diff --git a/ui/src/components/Checkbox.tsx b/ui/src/components/Checkbox.tsx index cf9855d..9fd55f4 100644 --- a/ui/src/components/Checkbox.tsx +++ b/ui/src/components/Checkbox.tsx @@ -15,7 +15,7 @@ const checkboxVariants = cva({ "form-checkbox block rounded", // Colors - "border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-800 checked:accent-blue-700 checked:dark:accent-blue-500 transition-colors", + "border-slate-300 bg-slate-50 transition-colors checked:accent-blue-700 dark:border-slate-600 dark:bg-slate-800 checked:dark:accent-blue-500", // Hover "hover:bg-slate-200/50 dark:hover:bg-slate-700/50", @@ -24,7 +24,7 @@ const checkboxVariants = cva({ "active:bg-slate-200 dark:active:bg-slate-700", // Focus - "focus:border-slate-300 dark:focus:border-slate-600 focus:outline-hidden focus:ring-2 focus:ring-blue-700 dark:focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-slate-900", + "focus:border-slate-300 focus:ring-2 focus:ring-blue-700 focus:ring-offset-2 focus:outline-hidden dark:focus:border-slate-600 dark:focus:ring-blue-500 dark:focus:ring-offset-slate-900", // Disabled "disabled:pointer-events-none disabled:opacity-30", diff --git a/ui/src/components/Combobox.tsx b/ui/src/components/Combobox.tsx index 3fce228..3d3a67a 100644 --- a/ui/src/components/Combobox.tsx +++ b/ui/src/components/Combobox.tsx @@ -74,11 +74,11 @@ export function Combobox({ "dark:bg-slate-800 dark:text-white dark:hover:bg-slate-700 dark:active:bg-slate-800/60", // Focus - "focus:outline-blue-600 focus:ring-2 focus:ring-blue-700 focus:ring-offset-2 dark:focus:outline-blue-500 dark:focus:ring-blue-500", + "focus:ring-2 focus:ring-blue-700 focus:ring-offset-2 focus:outline-blue-600 dark:focus:ring-blue-500 dark:focus:outline-blue-500", // Disabled disabled && - "pointer-events-none select-none bg-slate-50 text-slate-500/80 disabled:hover:bg-white dark:bg-slate-800 dark:text-slate-400/80 dark:disabled:hover:bg-slate-800", + "pointer-events-none bg-slate-50 text-slate-500/80 select-none disabled:hover:bg-white dark:bg-slate-800 dark:text-slate-400/80 dark:disabled:hover:bg-slate-800", )} placeholder={disabled ? disabledMessage : placeholder} displayValue={displayValue} @@ -95,7 +95,7 @@ export function Combobox({ value={option} className={clsx( // General styling - "cursor-default select-none px-4 py-2", + "cursor-default px-4 py-2 select-none", // Hover and active states "hover:bg-blue-50/80 ui-active:bg-blue-50/80 ui-active:text-blue-900", diff --git a/ui/src/components/ConfirmDialog.tsx b/ui/src/components/ConfirmDialog.tsx index 3771096..62f03c8 100644 --- a/ui/src/components/ConfirmDialog.tsx +++ b/ui/src/components/ConfirmDialog.tsx @@ -84,8 +84,8 @@ export function ConfirmDialog({ >
-
-

+
+

{title}

diff --git a/ui/src/components/Container.tsx b/ui/src/components/Container.tsx index a759ca5..114ad96 100644 --- a/ui/src/components/Container.tsx +++ b/ui/src/components/Container.tsx @@ -4,7 +4,7 @@ import React, { ReactNode } from "react"; import { cx } from "@/cva.config"; function Container({ children, className }: { children: ReactNode; className?: string }) { - return
{children}
; + return
{children}
; } function Article({ children }: { children: React.ReactNode }) { diff --git a/ui/src/components/CustomTooltip.tsx b/ui/src/components/CustomTooltip.tsx index a27f607..a311c50 100644 --- a/ui/src/components/CustomTooltip.tsx +++ b/ui/src/components/CustomTooltip.tsx @@ -18,7 +18,7 @@ export default function CustomTooltip({ payload }: CustomTooltipProps) {
- + {stat} {toolTipData?.unit}
diff --git a/ui/src/components/DhcpLeaseCard.tsx b/ui/src/components/DhcpLeaseCard.tsx index 8a6e59c..fbcecd2 100644 --- a/ui/src/components/DhcpLeaseCard.tsx +++ b/ui/src/components/DhcpLeaseCard.tsx @@ -14,7 +14,7 @@ export default function DhcpLeaseCard({ }) { return ( -
+

DHCP Lease Information diff --git a/ui/src/components/EmptyCard.tsx b/ui/src/components/EmptyCard.tsx index ad3370e..aab2532 100644 --- a/ui/src/components/EmptyCard.tsx +++ b/ui/src/components/EmptyCard.tsx @@ -32,7 +32,7 @@ export default function EmptyCard({ {IconElm && ( )} -

+

{headline}

diff --git a/ui/src/components/FieldLabel.tsx b/ui/src/components/FieldLabel.tsx index f9065a1..38ca19f 100644 --- a/ui/src/components/FieldLabel.tsx +++ b/ui/src/components/FieldLabel.tsx @@ -21,7 +21,7 @@ export default function FieldLabel({