diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6dca8d1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Enforce LF line endings for all text files +* text=auto eol=lf + +# Explicitly LF for source files +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.jsx text eol=lf +*.json text eol=lf +*.md text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.css text eol=lf +*.mjs text eol=lf diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..180f6a1 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,51 @@ +name: Build and Push Docker Image + +on: + push: + branches: + - main + tags: + - 'v*' + +permissions: + contents: read + packages: write + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/kubeorch/ui + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix= + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 diff --git a/app/dashboard/notifications/page.tsx b/app/dashboard/notifications/page.tsx index 54aa0ba..a4d0d94 100644 --- a/app/dashboard/notifications/page.tsx +++ b/app/dashboard/notifications/page.tsx @@ -91,6 +91,8 @@ export default function NotificationsPage() { {notifications.map(notification => (
{getInviteLink(inviteCode || "XXXXXX")}
@@ -124,10 +124,10 @@ export default function SettingsPage() {
-
+
Automatically generate a new invite code after each
user signs up
diff --git a/app/dashboard/workflow/new/page.tsx b/app/dashboard/workflow/new/page.tsx
index 61035e0..84ea3e4 100644
--- a/app/dashboard/workflow/new/page.tsx
+++ b/app/dashboard/workflow/new/page.tsx
@@ -760,6 +760,8 @@ export default function NewWorkflowPage() {
{/* File Upload - Full width */}
document.getElementById("file-input")?.click()
}
+ onKeyDown={e => {
+ if (e.key === "Enter" || e.key === " ")
+ document.getElementById("file-input")?.click();
+ }}
>
> = {
- github: Github,
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const iconMap: Record> = {
+ github: GithubIcon,
lock: Lock,
shield: Shield,
globe: Globe,
diff --git a/components/layout/TopBar.tsx b/components/layout/TopBar.tsx
index 9c638a8..6bcda02 100644
--- a/components/layout/TopBar.tsx
+++ b/components/layout/TopBar.tsx
@@ -73,8 +73,13 @@ export function TopBar({ onOpenCommandPalette }: TopBarProps) {
{
+ if (e.key === "Enter" || e.key === " ") onOpenCommandPalette?.();
+ }}
>
{/* Resize Handle */}
) {
);
}
-function CardTitle({ className, ...props }: React.ComponentProps<"h3">) {
+function CardTitle({
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"h3">) {
return (
+ // eslint-disable-next-line jsx-a11y/heading-has-content -- children are always passed by consumers
) {
className
)}
{...props}
- />
+ >
+ {children}
+
);
}
diff --git a/components/ui/compact-card.tsx b/components/ui/compact-card.tsx
index 8e6cbbd..8c1a13b 100644
--- a/components/ui/compact-card.tsx
+++ b/components/ui/compact-card.tsx
@@ -31,13 +31,20 @@ function CompactCardHeader({
);
}
-function CompactCardTitle({ className, ...props }: React.ComponentProps<"h3">) {
+function CompactCardTitle({
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"h3">) {
return (
+ // eslint-disable-next-line jsx-a11y/heading-has-content -- children are always passed by consumers
+ >
+ {children}
+
);
}
diff --git a/components/ui/github-icon.tsx b/components/ui/github-icon.tsx
new file mode 100644
index 0000000..c27a03a
--- /dev/null
+++ b/components/ui/github-icon.tsx
@@ -0,0 +1,22 @@
+import type { SVGProps } from "react";
+
+export function GithubIcon({
+ className,
+ size = 24,
+ ...props
+}: SVGProps & { size?: number }) {
+ return (
+
+ );
+}
diff --git a/components/workflow/CommandPalette.tsx b/components/workflow/CommandPalette.tsx
index 31fab3a..d91a9b7 100644
--- a/components/workflow/CommandPalette.tsx
+++ b/components/workflow/CommandPalette.tsx
@@ -501,6 +501,7 @@ export default function CommandPalette({
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
className="pl-9 pr-9"
+ // eslint-disable-next-line jsx-a11y/no-autofocus -- intentional: command palette search should be focused immediately on open
autoFocus
/>
{searchQuery && (
diff --git a/components/workflow/ImportDialog.tsx b/components/workflow/ImportDialog.tsx
index 4fa9433..75bac6f 100644
--- a/components/workflow/ImportDialog.tsx
+++ b/components/workflow/ImportDialog.tsx
@@ -293,6 +293,8 @@ export function ImportDialog({ isOpen, onClose, onImport }: ImportDialogProps) {
{/* File Upload */}
document.getElementById("import-file-input")?.click()
}
+ onKeyDown={e => {
+ if (e.key === "Enter" || e.key === " ")
+ document.getElementById("import-file-input")?.click();
+ }}
>
-
+
Git URL
@@ -318,6 +318,8 @@ export function ImportDialog({
{/* Drag and drop zone */}
document.getElementById("file-input")?.click()}
+ onKeyDown={e => {
+ if (e.key === "Enter" || e.key === " ")
+ document.getElementById("file-input")?.click();
+ }}
>
) : (
<>
-
+
Analyze Repository
>
)}
diff --git a/lib/utils/__tests__/errorReporter.test.ts b/lib/utils/__tests__/errorReporter.test.ts
index a775ff4..d500382 100644
--- a/lib/utils/__tests__/errorReporter.test.ts
+++ b/lib/utils/__tests__/errorReporter.test.ts
@@ -16,22 +16,14 @@ describe("errorReporter", () => {
const error = new Error("test error");
errorReporter.report(error);
- expect(console.error).toHaveBeenCalledWith(
- "[ErrorReporter]",
- error,
- ""
- );
+ expect(console.error).toHaveBeenCalledWith("[ErrorReporter]", error, "");
});
it("should include source in log label", () => {
const error = new Error("test error");
errorReporter.report(error, { source: "TestComponent" });
- expect(console.error).toHaveBeenCalledWith(
- "[TestComponent]",
- error,
- ""
- );
+ expect(console.error).toHaveBeenCalledWith("[TestComponent]", error, "");
});
it("should include metadata in log", () => {
@@ -97,10 +89,7 @@ describe("errorReporter", () => {
errorReporter.initGlobalHandlers();
- expect(addEventSpy).toHaveBeenCalledWith(
- "error",
- expect.any(Function)
- );
+ expect(addEventSpy).toHaveBeenCalledWith("error", expect.any(Function));
expect(addEventSpy).toHaveBeenCalledWith(
"unhandledrejection",
expect.any(Function)
diff --git a/lib/utils/errorReporter.ts b/lib/utils/errorReporter.ts
index 6058d49..b868be2 100644
--- a/lib/utils/errorReporter.ts
+++ b/lib/utils/errorReporter.ts
@@ -41,7 +41,7 @@ function reportMessage(message: string, context: ErrorContext = {}): void {
function initGlobalHandlers(): void {
if (typeof window === "undefined") return;
- window.addEventListener("error", (event) => {
+ window.addEventListener("error", event => {
report(event.error ?? event.message, {
source: "window.onerror",
metadata: {
@@ -52,7 +52,7 @@ function initGlobalHandlers(): void {
});
});
- window.addEventListener("unhandledrejection", (event) => {
+ window.addEventListener("unhandledrejection", event => {
report(event.reason, {
source: "unhandledrejection",
severity: "error",