fix: rbac implementation general issues (local squash)

This commit is contained in:
LukeGus
2025-12-27 03:04:17 -06:00
parent 4b257dc21c
commit 8af1911358
29 changed files with 2206 additions and 251 deletions

View File

@@ -18,6 +18,7 @@ export function HostManager({
isTopbarOpen,
initialTab = "host_viewer",
hostConfig,
_updateTimestamp,
rightSidebarOpen = false,
rightSidebarWidth = 400,
}: HostManagerProps): React.ReactElement {
@@ -36,20 +37,39 @@ export function HostManager({
const ignoreNextHostConfigChangeRef = useRef<boolean>(false);
const lastProcessedHostIdRef = useRef<number | undefined>(undefined);
// Sync state when tab is updated externally (via updateTab or addTab)
useEffect(() => {
if (initialTab) {
setActiveTab(initialTab);
}
}, [initialTab]);
// Always sync on timestamp changes
if (_updateTimestamp !== undefined) {
// Update activeTab if initialTab has changed
if (initialTab && initialTab !== activeTab) {
setActiveTab(initialTab);
}
// Update editingHost when hostConfig changes
useEffect(() => {
if (hostConfig) {
setEditingHost(hostConfig);
setActiveTab("add_host");
lastProcessedHostIdRef.current = hostConfig.id;
// Update editingHost if hostConfig has changed
if (hostConfig && hostConfig.id !== editingHost?.id) {
setEditingHost(hostConfig);
lastProcessedHostIdRef.current = hostConfig.id;
} else if (!hostConfig && editingHost) {
// Clear editingHost if hostConfig is now undefined
setEditingHost(null);
}
// Clear editingCredential if switching away from add_credential
if (initialTab !== "add_credential" && editingCredential) {
setEditingCredential(null);
}
} else {
// Initial mount - set state from props
if (initialTab) {
setActiveTab(initialTab);
}
if (hostConfig) {
setEditingHost(hostConfig);
lastProcessedHostIdRef.current = hostConfig.id;
}
}
}, [hostConfig?.id]);
}, [_updateTimestamp, initialTab, hostConfig?.id]);
const handleEditHost = (host: SSHHost) => {
setEditingHost(host);

View File

@@ -1465,6 +1465,7 @@ export function HostManagerEditor({
<Tabs
value={authTab}
onValueChange={(value) => {
if (editingHost?.isShared) return;
const newAuthType = value as
| "password"
| "key"
@@ -1478,25 +1479,29 @@ export function HostManagerEditor({
<TabsList className="bg-button border border-edge-medium">
<TabsTrigger
value="password"
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium"
disabled={editingHost?.isShared}
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium disabled:opacity-50 disabled:cursor-not-allowed"
>
{t("hosts.password")}
</TabsTrigger>
<TabsTrigger
value="key"
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium"
disabled={editingHost?.isShared}
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium disabled:opacity-50 disabled:cursor-not-allowed"
>
{t("hosts.key")}
</TabsTrigger>
<TabsTrigger
value="credential"
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium"
disabled={editingHost?.isShared}
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium disabled:opacity-50 disabled:cursor-not-allowed"
>
{t("hosts.credential")}
</TabsTrigger>
<TabsTrigger
value="none"
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium"
disabled={editingHost?.isShared}
className="bg-button data-[state=active]:bg-elevated data-[state=active]:border data-[state=active]:border-edge-medium disabled:opacity-50 disabled:cursor-not-allowed"
>
{t("hosts.none")}
</TabsTrigger>
@@ -1709,26 +1714,34 @@ export function HostManagerEditor({
name="credentialId"
render={({ field }) => (
<FormItem>
<CredentialSelector
value={field.value}
onValueChange={field.onChange}
onCredentialSelect={(credential) => {
if (
credential &&
!form.getValues(
"overrideCredentialUsername",
)
) {
form.setValue(
"username",
credential.username,
);
}
}}
/>
<FormDescription>
{t("hosts.credentialDescription")}
</FormDescription>
{editingHost?.isShared ? (
<div className="text-sm text-muted-foreground p-3 bg-base border border-edge-medium rounded-md">
{t("hosts.cannotChangeAuthAsSharedUser")}
</div>
) : (
<CredentialSelector
value={field.value}
onValueChange={field.onChange}
onCredentialSelect={(credential) => {
if (
credential &&
!form.getValues(
"overrideCredentialUsername",
)
) {
form.setValue(
"username",
credential.username,
);
}
}}
/>
)}
{!editingHost?.isShared && (
<FormDescription>
{t("hosts.credentialDescription")}
</FormDescription>
)}
</FormItem>
)}
/>
@@ -3769,7 +3782,7 @@ export function HostManagerEditor({
</ScrollArea>
<footer className="shrink-0 w-full pb-0">
<Separator className="p-0.25" />
{!(editingHost?.permissionLevel === "view") && (
{!editingHost?.isShared && (
<Button className="translate-y-2" type="submit" variant="outline">
{editingHost
? editingHost.id

View File

@@ -76,10 +76,8 @@ interface User {
is_admin: boolean;
}
const PERMISSION_LEVELS = [
{ value: "view", labelKey: "rbac.view" },
{ value: "manage", labelKey: "rbac.manage" },
];
// Only view permission is supported (manage removed due to encryption constraints)
const PERMISSION_LEVELS = [{ value: "view", labelKey: "rbac.view" }];
export function HostSharingTab({
hostId,
@@ -430,26 +428,12 @@ export function HostSharingTab({
</TabsContent>
</Tabs>
{/* Permission Level */}
{/* Permission Level - Always "view" (read-only) */}
<div className="space-y-2">
<Label htmlFor="permission-level">
{t("rbac.permissionLevel")}
</Label>
<Select
value={permissionLevel || "use"}
onValueChange={(v) => setPermissionLevel(v || "use")}
>
<SelectTrigger id="permission-level">
<SelectValue />
</SelectTrigger>
<SelectContent>
{PERMISSION_LEVELS.map((level) => (
<SelectItem key={level.value} value={level.value}>
{t(level.labelKey)}
</SelectItem>
))}
</SelectContent>
</Select>
<Label>{t("rbac.permissionLevel")}</Label>
<div className="text-sm text-muted-foreground">
{t("rbac.view")} - {t("rbac.viewDesc")}
</div>
</div>
{/* Expiration */}
@@ -496,7 +480,6 @@ export function HostSharingTab({
<TableHead>{t("rbac.permissionLevel")}</TableHead>
<TableHead>{t("rbac.grantedBy")}</TableHead>
<TableHead>{t("rbac.expires")}</TableHead>
<TableHead>{t("rbac.accessCount")}</TableHead>
<TableHead className="text-right">
{t("common.actions")}
</TableHead>
@@ -506,7 +489,7 @@ export function HostSharingTab({
{loading ? (
<TableRow>
<TableCell
colSpan={7}
colSpan={6}
className="text-center text-muted-foreground"
>
{t("common.loading")}
@@ -515,7 +498,7 @@ export function HostSharingTab({
) : accessList.length === 0 ? (
<TableRow>
<TableCell
colSpan={7}
colSpan={6}
className="text-center text-muted-foreground"
>
{t("rbac.noAccessRecords")}
@@ -582,7 +565,6 @@ export function HostSharingTab({
t("rbac.never")
)}
</TableCell>
<TableCell>{access.accessCount}</TableCell>
<TableCell className="text-right">
<Button
type="button"