mirror of
https://github.com/tale/headplane.git
synced 2026-08-24 11:36:31 +00:00
feat: switch to mist color from tailwind
This commit is contained in:
+144
-173
@@ -1,192 +1,163 @@
|
||||
import {
|
||||
CircleUser,
|
||||
Globe2,
|
||||
Lock,
|
||||
Server,
|
||||
Settings,
|
||||
Users,
|
||||
} from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { NavLink, useSubmit } from 'react-router';
|
||||
import Logo from '~/components/Logo';
|
||||
import Menu from '~/components/Menu';
|
||||
import { AuthSession } from '~/server/web/sessions';
|
||||
import cn from '~/utils/cn';
|
||||
import { CircleUser, Globe2, Lock, Server, Settings, Users } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { NavLink, useSubmit } from "react-router";
|
||||
|
||||
import Logo from "~/components/Logo";
|
||||
import Menu from "~/components/Menu";
|
||||
import { AuthSession } from "~/server/web/sessions";
|
||||
import cn from "~/utils/cn";
|
||||
|
||||
interface Props {
|
||||
configAvailable: boolean;
|
||||
onboarding: boolean;
|
||||
user?: AuthSession['user'];
|
||||
access: {
|
||||
ui: boolean;
|
||||
machines: boolean;
|
||||
dns: boolean;
|
||||
users: boolean;
|
||||
policy: boolean;
|
||||
settings: boolean;
|
||||
};
|
||||
configAvailable: boolean;
|
||||
onboarding: boolean;
|
||||
user?: AuthSession["user"];
|
||||
access: {
|
||||
ui: boolean;
|
||||
machines: boolean;
|
||||
dns: boolean;
|
||||
users: boolean;
|
||||
policy: boolean;
|
||||
settings: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface LinkProps {
|
||||
href: string;
|
||||
text: string;
|
||||
href: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface TabLinkProps {
|
||||
name: string;
|
||||
to: string;
|
||||
icon: ReactNode;
|
||||
name: string;
|
||||
to: string;
|
||||
icon: ReactNode;
|
||||
}
|
||||
|
||||
function TabLink({ name, to, icon }: TabLinkProps) {
|
||||
return (
|
||||
<div className="relative py-2">
|
||||
<NavLink
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
'px-3 py-2 flex items-center rounded-md text-nowrap gap-x-2.5',
|
||||
'after:absolute after:bottom-0 after:left-3 after:right-3',
|
||||
'after:h-0.5 after:bg-headplane-900 dark:after:bg-headplane-200',
|
||||
'hover:bg-headplane-200 dark:hover:bg-headplane-900',
|
||||
'focus:outline-hidden focus:ring-3',
|
||||
isActive ? 'after:visible' : 'after:invisible',
|
||||
)
|
||||
}
|
||||
prefetch="intent"
|
||||
to={to}
|
||||
>
|
||||
{icon} {name}
|
||||
</NavLink>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative py-2">
|
||||
<NavLink
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
"px-3 py-2 flex items-center rounded-md text-nowrap gap-x-2.5",
|
||||
"after:absolute after:bottom-0 after:left-3 after:right-3",
|
||||
"after:h-0.5 after:bg-mist-900 dark:after:bg-mist-200",
|
||||
"hover:bg-mist-200 dark:hover:bg-mist-900",
|
||||
"focus:outline-hidden focus:ring-3",
|
||||
isActive ? "after:visible" : "after:invisible",
|
||||
)
|
||||
}
|
||||
prefetch="intent"
|
||||
to={to}
|
||||
>
|
||||
{icon} {name}
|
||||
</NavLink>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Link({ href, text }: LinkProps) {
|
||||
return (
|
||||
<a
|
||||
className={cn(
|
||||
'hidden sm:block hover:underline text-sm',
|
||||
'focus:outline-hidden focus:ring-3 rounded-md',
|
||||
)}
|
||||
href={href}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
return (
|
||||
<a
|
||||
className={cn(
|
||||
"hidden sm:block hover:underline text-sm",
|
||||
"focus:outline-hidden focus:ring-3 rounded-md",
|
||||
)}
|
||||
href={href}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Header(data: Props) {
|
||||
const submit = useSubmit();
|
||||
const submit = useSubmit();
|
||||
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
'bg-headplane-100 dark:bg-headplane-950',
|
||||
'text-headplane-800 dark:text-headplane-200',
|
||||
'dark:border-b dark:border-headplane-800',
|
||||
'shadow-inner',
|
||||
)}
|
||||
>
|
||||
<div className="container flex items-center justify-between py-4">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Logo />
|
||||
<h1 className="text-2xl font-semibold">headplane</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4">
|
||||
<Link href="https://tailscale.com/download" text="Download" />
|
||||
<Link href="https://github.com/tale/headplane" text="GitHub" />
|
||||
<Link href="https://github.com/juanfont/headscale" text="Headscale" />
|
||||
{data.user ? (
|
||||
<Menu>
|
||||
<Menu.IconButton
|
||||
className={cn(data.user.picture ? 'p-0' : '')}
|
||||
label="User"
|
||||
>
|
||||
{data.user.picture ? (
|
||||
<img
|
||||
alt={data.user.name}
|
||||
className="w-8 h-8 rounded-full"
|
||||
src={data.user.picture}
|
||||
/>
|
||||
) : (
|
||||
<CircleUser />
|
||||
)}
|
||||
</Menu.IconButton>
|
||||
<Menu.Panel
|
||||
disabledKeys={['profile']}
|
||||
onAction={(key) => {
|
||||
if (key === 'logout') {
|
||||
submit(
|
||||
{},
|
||||
{
|
||||
method: 'POST',
|
||||
action: '/logout',
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Menu.Section>
|
||||
<Menu.Item key="profile" textValue="Profile">
|
||||
<div className="text-black dark:text-headplane-50">
|
||||
<p className="font-bold">{data.user.name}</p>
|
||||
<p>{data.user.email}</p>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="logout" textValue="Logout">
|
||||
<p className="text-red-500 dark:text-red-400">Logout</p>
|
||||
</Menu.Item>
|
||||
</Menu.Section>
|
||||
</Menu.Panel>
|
||||
</Menu>
|
||||
) : undefined}
|
||||
</div>
|
||||
</div>
|
||||
{data.access.ui && !data.onboarding ? (
|
||||
<nav className="container flex items-center gap-x-4 overflow-x-auto font-semibold">
|
||||
{data.access.machines ? (
|
||||
<TabLink
|
||||
icon={<Server className="w-5" />}
|
||||
name="Machines"
|
||||
to="/machines"
|
||||
/>
|
||||
) : undefined}
|
||||
{data.access.users ? (
|
||||
<TabLink
|
||||
icon={<Users className="w-5" />}
|
||||
name="Users"
|
||||
to="/users"
|
||||
/>
|
||||
) : undefined}
|
||||
{data.access.policy ? (
|
||||
<TabLink
|
||||
icon={<Lock className="w-5" />}
|
||||
name="Access Control"
|
||||
to="/acls"
|
||||
/>
|
||||
) : undefined}
|
||||
{data.configAvailable ? (
|
||||
<>
|
||||
{data.access.dns ? (
|
||||
<TabLink
|
||||
icon={<Globe2 className="w-5" />}
|
||||
name="DNS"
|
||||
to="/dns"
|
||||
/>
|
||||
) : undefined}
|
||||
{data.access.settings ? (
|
||||
<TabLink
|
||||
icon={<Settings className="w-5" />}
|
||||
name="Settings"
|
||||
to="/settings"
|
||||
/>
|
||||
) : undefined}
|
||||
</>
|
||||
) : undefined}
|
||||
</nav>
|
||||
) : undefined}
|
||||
</header>
|
||||
);
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
"bg-mist-100 dark:bg-mist-950",
|
||||
"text-mist-800 dark:text-mist-200",
|
||||
"dark:border-b dark:border-mist-800",
|
||||
"shadow-inner",
|
||||
)}
|
||||
>
|
||||
<div className="container flex items-center justify-between py-4">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Logo />
|
||||
<h1 className="text-2xl font-semibold">headplane</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4">
|
||||
<Link href="https://tailscale.com/download" text="Download" />
|
||||
<Link href="https://github.com/tale/headplane" text="GitHub" />
|
||||
<Link href="https://github.com/juanfont/headscale" text="Headscale" />
|
||||
{data.user ? (
|
||||
<Menu>
|
||||
<Menu.IconButton className={cn(data.user.picture ? "p-0" : "")} label="User">
|
||||
{data.user.picture ? (
|
||||
<img
|
||||
alt={data.user.name}
|
||||
className="h-8 w-8 rounded-full"
|
||||
src={data.user.picture}
|
||||
/>
|
||||
) : (
|
||||
<CircleUser />
|
||||
)}
|
||||
</Menu.IconButton>
|
||||
<Menu.Panel
|
||||
disabledKeys={["profile"]}
|
||||
onAction={(key) => {
|
||||
if (key === "logout") {
|
||||
submit(
|
||||
{},
|
||||
{
|
||||
method: "POST",
|
||||
action: "/logout",
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Menu.Section>
|
||||
<Menu.Item key="profile" textValue="Profile">
|
||||
<div className="text-black dark:text-mist-50">
|
||||
<p className="font-bold">{data.user.name}</p>
|
||||
<p>{data.user.email}</p>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="logout" textValue="Logout">
|
||||
<p className="text-red-500 dark:text-red-400">Logout</p>
|
||||
</Menu.Item>
|
||||
</Menu.Section>
|
||||
</Menu.Panel>
|
||||
</Menu>
|
||||
) : undefined}
|
||||
</div>
|
||||
</div>
|
||||
{data.access.ui && !data.onboarding ? (
|
||||
<nav className="container flex items-center gap-x-4 overflow-x-auto font-semibold">
|
||||
{data.access.machines ? (
|
||||
<TabLink icon={<Server className="w-5" />} name="Machines" to="/machines" />
|
||||
) : undefined}
|
||||
{data.access.users ? (
|
||||
<TabLink icon={<Users className="w-5" />} name="Users" to="/users" />
|
||||
) : undefined}
|
||||
{data.access.policy ? (
|
||||
<TabLink icon={<Lock className="w-5" />} name="Access Control" to="/acls" />
|
||||
) : undefined}
|
||||
{data.configAvailable ? (
|
||||
<>
|
||||
{data.access.dns ? (
|
||||
<TabLink icon={<Globe2 className="w-5" />} name="DNS" to="/dns" />
|
||||
) : undefined}
|
||||
{data.access.settings ? (
|
||||
<TabLink icon={<Settings className="w-5" />} name="Settings" to="/settings" />
|
||||
) : undefined}
|
||||
</>
|
||||
) : undefined}
|
||||
</nav>
|
||||
) : undefined}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user