mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-18 05:12:02 +00:00
improve unix group and sudo commands inputs
This commit is contained in:
@@ -2160,10 +2160,10 @@
|
|||||||
"sshSudoModeCommandsDescription": "User can run only the specified commands with sudo.",
|
"sshSudoModeCommandsDescription": "User can run only the specified commands with sudo.",
|
||||||
"sshSudo": "Allow sudo",
|
"sshSudo": "Allow sudo",
|
||||||
"sshSudoCommands": "Sudo Commands",
|
"sshSudoCommands": "Sudo Commands",
|
||||||
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
|
"sshSudoCommandsDescription": "List of commands the user is allowed to run with sudo, separated by commas, spaces, or new lines. Absolute paths must be used.",
|
||||||
"sshCreateHomeDir": "Create Home Directory",
|
"sshCreateHomeDir": "Create Home Directory",
|
||||||
"sshUnixGroups": "Unix Groups",
|
"sshUnixGroups": "Unix Groups",
|
||||||
"sshUnixGroupsDescription": "Comma separated Unix groups to add the user to on the target host.",
|
"sshUnixGroupsDescription": "Unix groups to add the user to on the target host, separated by commas, spaces, or new lines.",
|
||||||
"retryAttempts": "Retry Attempts",
|
"retryAttempts": "Retry Attempts",
|
||||||
"expectedResponseCodes": "Expected Response Codes",
|
"expectedResponseCodes": "Expected Response Codes",
|
||||||
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",
|
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ import type { CreateRoleBody, CreateRoleResponse } from "@server/routers/role";
|
|||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useTransition } from "react";
|
import { useTransition } from "react";
|
||||||
import { RoleForm, type RoleFormValues } from "./RoleForm";
|
import {
|
||||||
|
parseSudoCommands,
|
||||||
|
parseUnixGroups,
|
||||||
|
RoleForm,
|
||||||
|
type RoleFormValues
|
||||||
|
} from "./RoleForm";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
|
||||||
type CreateRoleFormProps = {
|
type CreateRoleFormProps = {
|
||||||
@@ -53,16 +58,10 @@ export default function CreateRoleForm({
|
|||||||
payload.sshSudoCommands =
|
payload.sshSudoCommands =
|
||||||
values.sshSudoMode === "commands" &&
|
values.sshSudoMode === "commands" &&
|
||||||
values.sshSudoCommands?.trim()
|
values.sshSudoCommands?.trim()
|
||||||
? values.sshSudoCommands
|
? parseSudoCommands(values.sshSudoCommands)
|
||||||
.split(",")
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
: [];
|
: [];
|
||||||
if (values.sshUnixGroups?.trim()) {
|
if (values.sshUnixGroups?.trim()) {
|
||||||
payload.sshUnixGroups = values.sshUnixGroups
|
payload.sshUnixGroups = parseUnixGroups(values.sshUnixGroups);
|
||||||
.split(",")
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const res = await api
|
const res = await api
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ import type { UpdateRoleBody, UpdateRoleResponse } from "@server/routers/role";
|
|||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useTransition } from "react";
|
import { useTransition } from "react";
|
||||||
import { RoleForm, type RoleFormValues } from "./RoleForm";
|
import {
|
||||||
|
parseSudoCommands,
|
||||||
|
parseUnixGroups,
|
||||||
|
RoleForm,
|
||||||
|
type RoleFormValues
|
||||||
|
} from "./RoleForm";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
|
||||||
type EditRoleFormProps = {
|
type EditRoleFormProps = {
|
||||||
@@ -56,16 +61,10 @@ export default function EditRoleForm({
|
|||||||
payload.sshSudoCommands =
|
payload.sshSudoCommands =
|
||||||
values.sshSudoMode === "commands" &&
|
values.sshSudoMode === "commands" &&
|
||||||
values.sshSudoCommands?.trim()
|
values.sshSudoCommands?.trim()
|
||||||
? values.sshSudoCommands
|
? parseSudoCommands(values.sshSudoCommands)
|
||||||
.split(",")
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
: [];
|
: [];
|
||||||
if (values.sshUnixGroups !== undefined) {
|
if (values.sshUnixGroups !== undefined) {
|
||||||
payload.sshUnixGroups = values.sshUnixGroups
|
payload.sshUnixGroups = parseUnixGroups(values.sshUnixGroups);
|
||||||
.split(",")
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const res = await api
|
const res = await api
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
FormMessage
|
FormMessage
|
||||||
} from "@app/components/ui/form";
|
} from "@app/components/ui/form";
|
||||||
import { Input } from "@app/components/ui/input";
|
import { Input } from "@app/components/ui/input";
|
||||||
|
import { Textarea } from "@app/components/ui/textarea";
|
||||||
import {
|
import {
|
||||||
OptionSelect,
|
OptionSelect,
|
||||||
type OptionSelectOption
|
type OptionSelectOption
|
||||||
@@ -46,15 +47,34 @@ function toSshSudoMode(value: string | null | undefined): SshSudoMode {
|
|||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasOnlyAbsoluteSudoCommands(value: string | undefined): boolean {
|
export function parseUnixGroups(value: string | undefined): string[] {
|
||||||
if (!value?.trim()) return true;
|
if (!value?.trim()) return [];
|
||||||
|
|
||||||
const commands = value
|
return value
|
||||||
.split(",")
|
.split(/[,\s\n]+/)
|
||||||
.map((command) => command.trim())
|
.map((group) => group.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
return commands.every((command) => {
|
export function parseSudoCommands(value: string | undefined): string[] {
|
||||||
|
if (!value?.trim()) return [];
|
||||||
|
|
||||||
|
const commands: string[] = [];
|
||||||
|
for (const segment of value.split(/[,\n]+/)) {
|
||||||
|
const trimmed = segment.trim();
|
||||||
|
if (!trimmed) continue;
|
||||||
|
|
||||||
|
for (const part of trimmed.split(/ (?=\/)/)) {
|
||||||
|
const command = part.trim();
|
||||||
|
if (command) commands.push(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasOnlyAbsoluteSudoCommands(value: string | undefined): boolean {
|
||||||
|
return parseSudoCommands(value).every((command) => {
|
||||||
const executable = command.split(/\s+/)[0];
|
const executable = command.split(/\s+/)[0];
|
||||||
return executable.startsWith("/");
|
return executable.startsWith("/");
|
||||||
});
|
});
|
||||||
@@ -125,10 +145,10 @@ export function RoleForm({
|
|||||||
(role as Role & { allowSsh?: boolean }).allowSsh ?? false,
|
(role as Role & { allowSsh?: boolean }).allowSsh ?? false,
|
||||||
sshSudoMode: toSshSudoMode(role.sshSudoMode),
|
sshSudoMode: toSshSudoMode(role.sshSudoMode),
|
||||||
sshSudoCommands: parseRoleJsonArray(role.sshSudoCommands).join(
|
sshSudoCommands: parseRoleJsonArray(role.sshSudoCommands).join(
|
||||||
", "
|
"\n"
|
||||||
),
|
),
|
||||||
sshCreateHomeDir: role.sshCreateHomeDir ?? false,
|
sshCreateHomeDir: role.sshCreateHomeDir ?? false,
|
||||||
sshUnixGroups: parseRoleJsonArray(role.sshUnixGroups).join(", ")
|
sshUnixGroups: parseRoleJsonArray(role.sshUnixGroups).join("\n")
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
name: "",
|
name: "",
|
||||||
@@ -156,10 +176,10 @@ export function RoleForm({
|
|||||||
(role as Role & { allowSsh?: boolean }).allowSsh ?? false,
|
(role as Role & { allowSsh?: boolean }).allowSsh ?? false,
|
||||||
sshSudoMode: toSshSudoMode(role.sshSudoMode),
|
sshSudoMode: toSshSudoMode(role.sshSudoMode),
|
||||||
sshSudoCommands: parseRoleJsonArray(role.sshSudoCommands).join(
|
sshSudoCommands: parseRoleJsonArray(role.sshSudoCommands).join(
|
||||||
", "
|
"\n"
|
||||||
),
|
),
|
||||||
sshCreateHomeDir: role.sshCreateHomeDir ?? false,
|
sshCreateHomeDir: role.sshCreateHomeDir ?? false,
|
||||||
sshUnixGroups: parseRoleJsonArray(role.sshUnixGroups).join(", ")
|
sshUnixGroups: parseRoleJsonArray(role.sshUnixGroups).join("\n")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [variant, role, form]);
|
}, [variant, role, form]);
|
||||||
@@ -421,9 +441,10 @@ export function RoleForm({
|
|||||||
{t("sshSudoCommands")}
|
{t("sshSudoCommands")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Textarea
|
||||||
{...field}
|
{...field}
|
||||||
disabled={sshDisabled}
|
disabled={sshDisabled}
|
||||||
|
className="h-20 min-h-20"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
@@ -446,9 +467,10 @@ export function RoleForm({
|
|||||||
{t("sshUnixGroups")}
|
{t("sshUnixGroups")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Textarea
|
||||||
{...field}
|
{...field}
|
||||||
disabled={sshDisabled}
|
disabled={sshDisabled}
|
||||||
|
className="h-20 min-h-20"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
|
|||||||
Reference in New Issue
Block a user