mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
chore: split dashboard quick actions into quarters and drop host status row lines
This commit is contained in:
1 parent
46b3adeb57
commit
dbf6c0117f
3 files changed
+78
-31
No files matched your search
@@ -24,6 +24,7 @@ import {
|
||||
import { getDefaultConnectionTab } from "@/lib/host-connection-tabs";
|
||||
import type { RecentActivityItem } from "@/main-axios";
|
||||
import type { Host, TabType } from "@/types/ui-types";
|
||||
import { quarterBorders } from "./quarter-borders";
|
||||
|
||||
export type VersionStatus =
|
||||
"up_to_date" | "requires_update" | "beta" | "unknown";
|
||||
@@ -161,17 +162,19 @@ function ActionButton({
|
||||
label,
|
||||
hint,
|
||||
onClick,
|
||||
className,
|
||||
}: {
|
||||
icon: React.ElementType;
|
||||
label: string;
|
||||
hint: string;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="group/btn flex h-full min-w-0 items-center gap-2 border border-border/60 px-2 py-1.5 text-left transition-colors hover:border-accent-brand/40 hover:bg-muted/50"
|
||||
className={`group/btn flex h-full min-w-0 items-center gap-2 px-3 py-2 text-left transition-colors hover:bg-muted/50 ${className ?? ""}`}
|
||||
>
|
||||
<span className="flex size-6 shrink-0 items-center justify-center border border-border bg-muted transition-colors group-hover/btn:border-accent-brand/40 group-hover/btn:bg-accent-brand/20">
|
||||
<Icon className="size-3 text-accent-brand" />
|
||||
@@ -208,39 +211,48 @@ export function QuickActions({
|
||||
: host.port;
|
||||
return `${host.ip}:${port ?? host.port}`;
|
||||
};
|
||||
const actions = [
|
||||
{
|
||||
icon: Plus,
|
||||
label: t("dashboard.addHost"),
|
||||
hint: t("dashboardTab.registerNewServer"),
|
||||
onClick: () =>
|
||||
onOpenSingletonTab("host-manager", "host-manager:add-host"),
|
||||
},
|
||||
{
|
||||
icon: KeyRound,
|
||||
label: t("dashboard.addCredential"),
|
||||
hint: t("dashboardTab.storeSshKeysOrPasswords"),
|
||||
onClick: () =>
|
||||
onOpenSingletonTab("host-manager", "host-manager:add-credential"),
|
||||
},
|
||||
...(isAdmin
|
||||
? [
|
||||
{
|
||||
icon: Settings,
|
||||
label: t("dashboard.adminSettings"),
|
||||
hint: t("dashboardTab.manageUsersAndRoles"),
|
||||
onClick: () => onOpenSingletonTab("admin-settings"),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
icon: User,
|
||||
label: t("dashboard.userProfile"),
|
||||
hint: t("dashboardTab.manageYourAccount"),
|
||||
onClick: () => onOpenSingletonTab("user-profile"),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="flex min-h-full flex-1 flex-col">
|
||||
<div className="grid flex-1 auto-rows-fr grid-cols-1 gap-1 p-2 sm:grid-cols-2">
|
||||
<ActionButton
|
||||
icon={Plus}
|
||||
label={t("dashboard.addHost")}
|
||||
hint={t("dashboardTab.registerNewServer")}
|
||||
onClick={() =>
|
||||
onOpenSingletonTab("host-manager", "host-manager:add-host")
|
||||
}
|
||||
/>
|
||||
<ActionButton
|
||||
icon={KeyRound}
|
||||
label={t("dashboard.addCredential")}
|
||||
hint={t("dashboardTab.storeSshKeysOrPasswords")}
|
||||
onClick={() =>
|
||||
onOpenSingletonTab("host-manager", "host-manager:add-credential")
|
||||
}
|
||||
/>
|
||||
{isAdmin && (
|
||||
<div className="grid flex-1 auto-rows-fr grid-cols-1 sm:grid-cols-2">
|
||||
{actions.map((action, i) => (
|
||||
<ActionButton
|
||||
icon={Settings}
|
||||
label={t("dashboard.adminSettings")}
|
||||
hint={t("dashboardTab.manageUsersAndRoles")}
|
||||
onClick={() => onOpenSingletonTab("admin-settings")}
|
||||
key={action.label}
|
||||
{...action}
|
||||
className={quarterBorders(i, actions.length)}
|
||||
/>
|
||||
)}
|
||||
<ActionButton
|
||||
icon={User}
|
||||
label={t("dashboard.userProfile")}
|
||||
hint={t("dashboardTab.manageYourAccount")}
|
||||
onClick={() => onOpenSingletonTab("user-profile")}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{pinnedHosts.length > 0 && (
|
||||
<div className="flex flex-col border-t border-border">
|
||||
@@ -308,7 +320,7 @@ export function HostStatusList({
|
||||
defaultConnectAction(actions, host)?.tabType;
|
||||
if (target) onOpenTab(host, target);
|
||||
}}
|
||||
className="group/row flex min-w-0 cursor-pointer items-center gap-2 border-b border-border/60 px-3 py-1.5 last:border-0 hover:bg-muted/50"
|
||||
className="group/row flex min-w-0 cursor-pointer items-center gap-2 px-3 py-1.5 hover:bg-muted/50"
|
||||
>
|
||||
<span
|
||||
className={`size-1.5 shrink-0 rounded-full ${getStatusClasses(availability, scheme, "dot", statusLoading)}`}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Lines only between cells, so the actions read as quarters: one column on
|
||||
* small screens, two above. An odd last cell spans the full row.
|
||||
*/
|
||||
export function quarterBorders(index: number, count: number): string {
|
||||
const classes = ["border-border"];
|
||||
const last = index === count - 1;
|
||||
if (!last) classes.push("border-b");
|
||||
if (Math.floor(index / 2) === Math.floor((count - 1) / 2)) {
|
||||
classes.push("sm:border-b-0");
|
||||
}
|
||||
if (index % 2 === 0 && !last) classes.push("sm:border-r");
|
||||
if (last && count % 2 === 1) classes.push("sm:col-span-2");
|
||||
return classes.join(" ");
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { quarterBorders } from "@/dashboard/quarter-borders";
|
||||
|
||||
describe("quarterBorders", () => {
|
||||
it("draws a cross between four cells", () => {
|
||||
expect([0, 1, 2, 3].map((i) => quarterBorders(i, 4))).toEqual([
|
||||
"border-border border-b sm:border-r",
|
||||
"border-border border-b",
|
||||
"border-border border-b sm:border-b-0 sm:border-r",
|
||||
"border-border sm:border-b-0",
|
||||
]);
|
||||
});
|
||||
|
||||
it("stretches an odd last cell across the row", () => {
|
||||
expect(quarterBorders(2, 3)).toBe(
|
||||
"border-border sm:border-b-0 sm:col-span-2",
|
||||
);
|
||||
expect(quarterBorders(1, 3)).toBe("border-border border-b");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user