use niceId for client routes

This commit is contained in:
miloschwartz
2025-12-06 20:31:09 -05:00
parent 8a8c0edad3
commit d7e06161a8
22 changed files with 375 additions and 222 deletions

View File

@@ -34,7 +34,8 @@ import { useForm } from "react-hook-form";
import { z } from "zod";
const GeneralFormSchema = z.object({
name: z.string().nonempty("Name is required")
name: z.string().nonempty("Name is required"),
niceId: z.string().min(1).max(255).optional()
});
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
@@ -49,7 +50,8 @@ export default function GeneralPage() {
const form = useForm({
resolver: zodResolver(GeneralFormSchema),
defaultValues: {
name: client?.name
name: client?.name,
niceId: client?.niceId || ""
},
mode: "onChange"
});
@@ -84,10 +86,11 @@ export default function GeneralPage() {
try {
await api.post(`/client/${client?.clientId}`, {
name: data.name
name: data.name,
niceId: data.niceId
});
updateClient({ name: data.name });
updateClient({ name: data.name, niceId: data.niceId });
toast({
title: t("clientUpdated"),
@@ -139,6 +142,28 @@ export default function GeneralPage() {
</FormItem>
)}
/>
<FormField
control={form.control}
name="niceId"
render={({ field }) => (
<FormItem>
<FormLabel>
{t("identifier")}
</FormLabel>
<FormControl>
<Input
{...field}
placeholder={t(
"enterIdentifier"
)}
className="flex-1"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</SettingsSectionForm>

View File

@@ -4,7 +4,6 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
import ClientProvider from "@app/providers/ClientProvider";
import { build } from "@server/build";
import { GetClientResponse } from "@server/routers/client";
import { AxiosResponse } from "axios";
import { getTranslations } from "next-intl/server";
@@ -12,7 +11,7 @@ import { redirect } from "next/navigation";
type SettingsLayoutProps = {
children: React.ReactNode;
params: Promise<{ clientId: number | string; orgId: string }>;
params: Promise<{ niceId: number | string; orgId: string }>;
};
export default async function SettingsLayout(props: SettingsLayoutProps) {
@@ -22,8 +21,9 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
let client = null;
try {
console.log("making request to ", `/org/${params.orgId}/client/${params.niceId}`);
const res = await internal.get<AxiosResponse<GetClientResponse>>(
`/client/${params.clientId}`,
`/org/${params.orgId}/client/${params.niceId}`,
await authCookieHeader()
);
client = res.data.data;
@@ -37,11 +37,11 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
const navItems = [
{
title: t("general"),
href: `/{orgId}/settings/clients/machine/{clientId}/general`
href: `/{orgId}/settings/clients/machine/{niceId}/general`
},
{
title: t("credentials"),
href: `/{orgId}/settings/clients/machine/{clientId}/credentials`
href: `/{orgId}/settings/clients/machine/{niceId}/credentials`
}
];

View File

@@ -1,10 +1,10 @@
import { redirect } from "next/navigation";
export default async function ClientPage(props: {
params: Promise<{ orgId: string; clientId: number | string }>;
params: Promise<{ orgId: string; niceId: number | string }>;
}) {
const params = await props.params;
redirect(
`/${params.orgId}/settings/clients/machine/${params.clientId}/general`
`/${params.orgId}/settings/clients/machine/${params.niceId}/general`
);
}

View File

@@ -276,7 +276,7 @@ export default function Page() {
if (res && res.status === 201) {
const data = res.data.data;
router.push(`/${orgId}/settings/clients/machine/${data.clientId}`);
router.push(`/${orgId}/settings/clients/machine/${data.niceId}`);
}
setCreateLoading(false);

View File

@@ -56,7 +56,8 @@ export default async function ClientsPage(props: ClientsPageProps) {
olmUpdateAvailable: client.olmUpdateAvailable || false,
userId: client.userId,
username: client.username,
userEmail: client.userEmail
userEmail: client.userEmail,
niceId: client.niceId
};
};

View File

@@ -66,7 +66,8 @@ export default async function ClientResourcesPage(
destination: siteResource.destination,
// destinationPort: siteResource.destinationPort,
alias: siteResource.alias || null,
siteNiceId: siteResource.siteNiceId
siteNiceId: siteResource.siteNiceId,
niceId: siteResource.niceId
};
}
);

View File

@@ -19,25 +19,27 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
return (
<Alert>
<AlertDescription>
<InfoSections cols={2}>
<>
<InfoSection>
<InfoSectionTitle>{t("status")}</InfoSectionTitle>
<InfoSectionContent>
{client.online ? (
<div className="text-green-500 flex items-center space-x-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span>{t("online")}</span>
</div>
) : (
<div className="text-neutral-500 flex items-center space-x-2">
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
<span>{t("offline")}</span>
</div>
)}
</InfoSectionContent>
</InfoSection>
</>
<InfoSections cols={3}>
<InfoSection>
<InfoSectionTitle>{t("identifier")}</InfoSectionTitle>
<InfoSectionContent>{client.niceId}</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>{t("status")}</InfoSectionTitle>
<InfoSectionContent>
{client.online ? (
<div className="text-green-500 flex items-center space-x-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span>{t("online")}</span>
</div>
) : (
<div className="text-neutral-500 flex items-center space-x-2">
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
<span>{t("offline")}</span>
</div>
)}
</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>{t("address")}</InfoSectionTitle>
<InfoSectionContent>

View File

@@ -25,32 +25,6 @@ import EditInternalResourceDialog from "@app/components/EditInternalResourceDial
import { orgQueries } from "@app/lib/queries";
import { useQuery } from "@tanstack/react-query";
export type TargetHealth = {
targetId: number;
ip: string;
port: number;
enabled: boolean;
healthStatus?: "healthy" | "unhealthy" | "unknown";
};
export type ResourceRow = {
id: number;
nice: string | null;
name: string;
orgId: string;
domain: string;
authState: string;
http: boolean;
protocol: string;
proxyPort: number | null;
enabled: boolean;
domainId?: string;
ssl: boolean;
targetHost?: string;
targetPort?: number;
targets?: TargetHealth[];
};
export type InternalResourceRow = {
id: number;
name: string;
@@ -66,6 +40,7 @@ export type InternalResourceRow = {
destination: string;
// destinationPort: number | null;
alias: string | null;
niceId: string;
};
type ClientResourcesTableProps = {
@@ -158,6 +133,28 @@ export default function ClientResourcesTable({
);
}
},
{
id: "niceId",
accessorKey: "niceId",
friendlyName: t("identifier"),
enableHiding: true,
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("identifier")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
return <span>{row.original.niceId || "-"}</span>;
}
},
{
accessorKey: "siteName",
friendlyName: t("site"),

View File

@@ -40,6 +40,7 @@ export type ClientRow = {
userId: string | null;
username: string | null;
userEmail: string | null;
niceId: string;
};
type ClientTableProps = {
@@ -66,7 +67,8 @@ export default function MachineClientsTable({
const defaultMachineColumnVisibility = {
client: false,
subnet: false,
userId: false
userId: false,
niceId: false
};
const refreshData = () => {
@@ -129,8 +131,8 @@ export default function MachineClientsTable({
}
},
{
accessorKey: "userId",
friendlyName: "User",
accessorKey: "niceId",
friendlyName: "Identifier",
header: ({ column }) => {
return (
<Button
@@ -141,54 +143,12 @@ export default function MachineClientsTable({
)
}
>
User
{t("identifier")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const r = row.original;
return r.userId ? (
<Link
href={`/${r.orgId}/settings/access/users/${r.userId}`}
>
<Button variant="outline">
{r.userEmail || r.username || r.userId}
<ArrowUpRight className="ml-2 h-4 w-4" />
</Button>
</Link>
) : (
"-"
);
}
},
// {
// accessorKey: "siteName",
// header: ({ column }) => {
// return (
// <Button
// variant="ghost"
// onClick={() =>
// column.toggleSorting(column.getIsSorted() === "asc")
// }
// >
// Site
// <ArrowUpDown className="ml-2 h-4 w-4" />
// </Button>
// );
// },
// cell: ({ row }) => {
// const r = row.original;
// return (
// <Link href={`/${r.orgId}/settings/sites/${r.siteId}`}>
// <Button variant="outline">
// {r.siteName}
// <ArrowUpRight className="ml-2 h-4 w-4" />
// </Button>
// </Link>
// );
// }
// },
{
accessorKey: "online",
friendlyName: "Connectivity",
@@ -369,7 +329,7 @@ export default function MachineClientsTable({
</DropdownMenuContent>
</DropdownMenu>
<Link
href={`/${clientRow.orgId}/settings/clients/machine/${clientRow.id}`}
href={`/${clientRow.orgId}/settings/clients/machine/${clientRow.niceId}`}
>
<Button variant={"outline"}>
{t("edit")}

View File

@@ -319,7 +319,7 @@ export default function ProxyResourcesTable({
{
id: "niceId",
accessorKey: "nice",
friendlyName: t("niceId"),
friendlyName: t("identifier"),
enableHiding: true,
header: ({ column }) => {
return (
@@ -329,7 +329,7 @@ export default function ProxyResourcesTable({
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("niceId")}
{t("identifier")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);

View File

@@ -128,7 +128,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
{
id: "niceId",
accessorKey: "nice",
friendlyName: t("niceId"),
friendlyName: t("identifier"),
enableHiding: true,
header: ({ column }) => {
return (
@@ -138,7 +138,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("niceId")}
{t("identifier")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);