Let admins hide the Proxmox-link card per server
Not every registered server is a Proxmox VM/LXC -- bare-metal boxes and other hosts had no reason to show a "link to Proxmox" option, but it appeared unconditionally on every server's detail page. New hideProxmoxLink column on servers (default false, so existing behavior is unchanged until someone opts in). A "Not a VM? Hide this" link in the card's header sets it; once hidden, a small "+ Show Proxmox link options" link takes its place so it's still reachable, not buried in a settings form. The card always shows regardless of this flag once a server IS actually linked, so unlinking never becomes unreachable by hiding the card out from under an active link. Verified against a temp SQLite DB with real migrations: a new server defaults to false, and toggling true/false both persist correctly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f2a6253ddf
commit
08a984719f
8 files changed
+1193
-2
No files matched your search
@@ -54,7 +54,10 @@ All modules from the original plan are built:
|
|||||||
detail page. The detail page also has an **Admin Links** section
|
detail page. The detail page also has an **Admin Links** section
|
||||||
(operator/admin to add/edit/remove) for bookmarking that server's own
|
(operator/admin to add/edit/remove) for bookmarking that server's own
|
||||||
admin UIs — Dockge, Webmin, Cockpit, Portainer, or anything else reachable
|
admin UIs — Dockge, Webmin, Cockpit, Portainer, or anything else reachable
|
||||||
by URL.
|
by URL. Since not every server is a Proxmox VM, an admin can hide the
|
||||||
|
"Proxmox link" card per server ("Not a VM? Hide this" / "+ Show Proxmox
|
||||||
|
link options") — it stays visible regardless once a server actually is
|
||||||
|
linked, so unlinking is always reachable.
|
||||||
- **Tailscale**, **Proxmox**, **Synology**, **Semaphore**, **Gitea**,
|
- **Tailscale**, **Proxmox**, **Synology**, **Semaphore**, **Gitea**,
|
||||||
and **Docker** each get their own top-level page (backed by the
|
and **Docker** each get their own top-level page (backed by the
|
||||||
matching integration) instead of living inside a shared Integrations
|
matching integration) instead of living inside a shared Integrations
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `servers` ADD `hide_proxmox_link` integer DEFAULT false NOT NULL;
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -36,6 +36,13 @@
|
|||||||
"when": 1789677988329,
|
"when": 1789677988329,
|
||||||
"tag": "0004_brown_gambit",
|
"tag": "0004_brown_gambit",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1789759852933,
|
||||||
|
"tag": "0005_bent_violations",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -197,6 +197,13 @@ export const servers = sqliteTable("servers", {
|
|||||||
proxmoxNode: text("proxmox_node"),
|
proxmoxNode: text("proxmox_node"),
|
||||||
proxmoxGuestType: text("proxmox_guest_type").$type<ProxmoxGuestTypeCol>(),
|
proxmoxGuestType: text("proxmox_guest_type").$type<ProxmoxGuestTypeCol>(),
|
||||||
proxmoxVmid: integer("proxmox_vmid"),
|
proxmoxVmid: integer("proxmox_vmid"),
|
||||||
|
|
||||||
|
// Not every server is a Proxmox guest (bare-metal boxes, other hosts) — an
|
||||||
|
// admin can hide the "Proxmox link" card on this server's detail page
|
||||||
|
// rather than seeing an irrelevant option on every server. Ignored (the
|
||||||
|
// card always shows) once a server IS actually linked, so unlinking stays
|
||||||
|
// reachable.
|
||||||
|
hideProxmoxLink: integer("hide_proxmox_link", { mode: "boolean" }).notNull().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const scheduledTasks = sqliteTable("scheduled_tasks", {
|
export const scheduledTasks = sqliteTable("scheduled_tasks", {
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ const updateServerSchema = z
|
|||||||
proxmoxNode: z.string().nullable().optional(),
|
proxmoxNode: z.string().nullable().optional(),
|
||||||
proxmoxGuestType: z.enum(["qemu", "lxc"]).nullable().optional(),
|
proxmoxGuestType: z.enum(["qemu", "lxc"]).nullable().optional(),
|
||||||
proxmoxVmid: z.number().int().nullable().optional(),
|
proxmoxVmid: z.number().int().nullable().optional(),
|
||||||
|
hideProxmoxLink: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ export interface ServerRecord {
|
|||||||
proxmoxNode: string | null;
|
proxmoxNode: string | null;
|
||||||
proxmoxGuestType: "qemu" | "lxc" | null;
|
proxmoxGuestType: "qemu" | "lxc" | null;
|
||||||
proxmoxVmid: number | null;
|
proxmoxVmid: number | null;
|
||||||
|
hideProxmoxLink: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerHardware {
|
export interface ServerHardware {
|
||||||
@@ -271,6 +272,7 @@ export interface ServerUpdateInput {
|
|||||||
proxmoxNode?: string | null;
|
proxmoxNode?: string | null;
|
||||||
proxmoxGuestType?: "qemu" | "lxc" | null;
|
proxmoxGuestType?: "qemu" | "lxc" | null;
|
||||||
proxmoxVmid?: number | null;
|
proxmoxVmid?: number | null;
|
||||||
|
hideProxmoxLink?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScheduleType = "cron" | "systemd_timer" | "docker" | "backup" | "update" | "n8n_workflow" | "manual";
|
export type ScheduleType = "cron" | "systemd_timer" | "docker" | "backup" | "update" | "n8n_workflow" | "manual";
|
||||||
|
|||||||
@@ -175,6 +175,15 @@ export default function ServerDetail({ user }: { user: CurrentUser }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleHideProxmoxLink(hide: boolean) {
|
||||||
|
try {
|
||||||
|
await api.servers.update(serverId, { hideProxmoxLink: hide });
|
||||||
|
await loadDetail();
|
||||||
|
} catch (err) {
|
||||||
|
setLinkError(err instanceof Error ? err.message : String(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function openAddTask() {
|
function openAddTask() {
|
||||||
setEditingTask(null);
|
setEditingTask(null);
|
||||||
setTaskForm({ ...emptyTaskForm });
|
setTaskForm({ ...emptyTaskForm });
|
||||||
@@ -397,10 +406,17 @@ export default function ServerDetail({ user }: { user: CurrentUser }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isAdmin && (
|
{isAdmin && (server.proxmoxIntegrationId || !server.hideProxmoxLink) ? (
|
||||||
<div className="card mb-3">
|
<div className="card mb-3">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<h3 className="card-title">Proxmox link</h3>
|
<h3 className="card-title">Proxmox link</h3>
|
||||||
|
{!server.proxmoxIntegrationId && (
|
||||||
|
<div className="card-actions">
|
||||||
|
<button className="btn btn-link btn-sm p-0" onClick={() => toggleHideProxmoxLink(true)}>
|
||||||
|
Not a VM? Hide this
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{server.proxmoxIntegrationId ? (
|
{server.proxmoxIntegrationId ? (
|
||||||
@@ -465,6 +481,14 @@ export default function ServerDetail({ user }: { user: CurrentUser }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
isAdmin && (
|
||||||
|
<div className="mb-3">
|
||||||
|
<button className="btn btn-link btn-sm p-0" onClick={() => toggleHideProxmoxLink(false)}>
|
||||||
|
+ Show Proxmox link options
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="row row-cards mb-3">
|
<div className="row row-cards mb-3">
|
||||||
|
|||||||
Reference in new issue
Block a user