add a pill to show if a user is admin or not
This commit is contained in:
@@ -612,6 +612,7 @@ func runSession(ctx context.Context, cancel context.CancelFunc, conn *websocket.
|
|||||||
hello.CPU = hw.CPU
|
hello.CPU = hw.CPU
|
||||||
hello.GPU = hw.GPU
|
hello.GPU = hw.GPU
|
||||||
hello.RAM = hw.RAM
|
hello.RAM = hw.RAM
|
||||||
|
hello.IsAdmin = sysinfo.IsAdmin()
|
||||||
|
|
||||||
if err := wire.WriteMsg(ctx, env.Conn, hello); err != nil {
|
if err := wire.WriteMsg(ctx, env.Conn, hello); err != nil {
|
||||||
return fmt.Errorf("send hello: %w", err)
|
return fmt.Errorf("send hello: %w", err)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package sysinfo
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// root == admin
|
||||||
|
func IsAdmin() bool {
|
||||||
|
return os.Getuid() == 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package sysinfo
|
||||||
|
|
||||||
|
import "golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
// check current process token to see if we're admin asf
|
||||||
|
func IsAdmin() bool {
|
||||||
|
token := windows.GetCurrentProcessToken()
|
||||||
|
return token.IsElevated()
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ type Hello struct {
|
|||||||
CPU string `msgpack:"cpu,omitempty"`
|
CPU string `msgpack:"cpu,omitempty"`
|
||||||
GPU string `msgpack:"gpu,omitempty"`
|
GPU string `msgpack:"gpu,omitempty"`
|
||||||
RAM string `msgpack:"ram,omitempty"`
|
RAM string `msgpack:"ram,omitempty"`
|
||||||
|
IsAdmin bool `msgpack:"isAdmin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnrollmentChallenge struct {
|
type EnrollmentChallenge struct {
|
||||||
|
|||||||
@@ -429,6 +429,11 @@ p {
|
|||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
border-color: rgba(71, 85, 105, 0.55);
|
border-color: rgba(71, 85, 105, 0.55);
|
||||||
}
|
}
|
||||||
|
.pill-admin {
|
||||||
|
background: rgba(234, 179, 8, 0.18);
|
||||||
|
color: #fef08a;
|
||||||
|
border-color: rgba(234, 179, 8, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
/* Hardware info toggle & panel */
|
/* Hardware info toggle & panel */
|
||||||
.pill-hw {
|
.pill-hw {
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ export function createRenderer({
|
|||||||
card.dataset.nickname = String(client.nickname || "");
|
card.dataset.nickname = String(client.nickname || "");
|
||||||
card.dataset.customTag = String(client.customTag || "");
|
card.dataset.customTag = String(client.customTag || "");
|
||||||
card.dataset.bookmarked = String(!!client.bookmarked);
|
card.dataset.bookmarked = String(!!client.bookmarked);
|
||||||
|
card.dataset.admin = String(!!client.isAdmin);
|
||||||
card._customTagNote = String(client.customTagNote || "");
|
card._customTagNote = String(client.customTagNote || "");
|
||||||
const os = osBadge(client.os || "unknown");
|
const os = osBadge(client.os || "unknown");
|
||||||
const arch = archBadge(client.arch || "");
|
const arch = archBadge(client.arch || "");
|
||||||
@@ -266,6 +267,7 @@ export function createRenderer({
|
|||||||
<i class="fa-solid fa-circle"></i>
|
<i class="fa-solid fa-circle"></i>
|
||||||
${client.online ? "Online" : "Offline"}
|
${client.online ? "Online" : "Offline"}
|
||||||
</span>
|
</span>
|
||||||
|
${client.isAdmin ? `<span class="pill pill-admin"><i class="fa-solid fa-shield-halved"></i> Admin</span>` : ""}
|
||||||
${!client.online && client.disconnectReason && client.disconnectReason !== "normal" ? (() => {
|
${!client.online && client.disconnectReason && client.disconnectReason !== "normal" ? (() => {
|
||||||
const iconMap = { panic: "fa-skull-crossbones", crash: "fa-skull", timeout: "fa-clock", network: "fa-plug-circle-xmark" };
|
const iconMap = { panic: "fa-skull-crossbones", crash: "fa-skull", timeout: "fa-clock", network: "fa-plug-circle-xmark" };
|
||||||
const colorMap = { panic: "text-red-400", crash: "text-red-400", timeout: "text-amber-400", network: "text-slate-400" };
|
const colorMap = { panic: "text-red-400", crash: "text-red-400", timeout: "text-amber-400", network: "text-slate-400" };
|
||||||
|
|||||||
@@ -100,6 +100,7 @@
|
|||||||
<option value="ping_desc">Ping ↓</option>
|
<option value="ping_desc">Ping ↓</option>
|
||||||
<option value="country_asc">Country A→Z</option>
|
<option value="country_asc">Country A→Z</option>
|
||||||
<option value="country_desc">Country Z→A</option>
|
<option value="country_desc">Country Z→A</option>
|
||||||
|
<option value="admin_first">Admin first</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
id="select-all"
|
id="select-all"
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ try {
|
|||||||
try {
|
try {
|
||||||
db.run(`ALTER TABLE clients ADD COLUMN ram TEXT`);
|
db.run(`ALTER TABLE clients ADD COLUMN ram TEXT`);
|
||||||
} catch {}
|
} catch {}
|
||||||
|
try {
|
||||||
|
db.run(`ALTER TABLE clients ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0`);
|
||||||
|
} catch {}
|
||||||
db.run(
|
db.run(
|
||||||
`CREATE INDEX IF NOT EXISTS idx_clients_public_key ON clients(public_key);`,
|
`CREATE INDEX IF NOT EXISTS idx_clients_public_key ON clients(public_key);`,
|
||||||
);
|
);
|
||||||
@@ -250,8 +253,8 @@ export function upsertClientRow(
|
|||||||
) {
|
) {
|
||||||
const now = partial.lastSeen ?? Date.now();
|
const now = partial.lastSeen ?? Date.now();
|
||||||
db.run(
|
db.run(
|
||||||
`INSERT INTO clients (id, hwid, role, ip, host, os, arch, version, user, nickname, custom_tag, custom_tag_note, monitors, country, last_seen, online, ping_ms, enrollment_status, public_key, key_fingerprint, cpu, gpu, ram)
|
`INSERT INTO clients (id, hwid, role, ip, host, os, arch, version, user, nickname, custom_tag, custom_tag_note, monitors, country, last_seen, online, ping_ms, enrollment_status, public_key, key_fingerprint, cpu, gpu, ram, is_admin)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, COALESCE(?, 0))
|
||||||
ON CONFLICT(id) DO UPDATE SET
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
hwid=COALESCE(excluded.hwid, clients.hwid),
|
hwid=COALESCE(excluded.hwid, clients.hwid),
|
||||||
role=COALESCE(excluded.role, clients.role),
|
role=COALESCE(excluded.role, clients.role),
|
||||||
@@ -274,7 +277,8 @@ export function upsertClientRow(
|
|||||||
key_fingerprint=COALESCE(excluded.key_fingerprint, clients.key_fingerprint),
|
key_fingerprint=COALESCE(excluded.key_fingerprint, clients.key_fingerprint),
|
||||||
cpu=COALESCE(excluded.cpu, clients.cpu),
|
cpu=COALESCE(excluded.cpu, clients.cpu),
|
||||||
gpu=COALESCE(excluded.gpu, clients.gpu),
|
gpu=COALESCE(excluded.gpu, clients.gpu),
|
||||||
ram=COALESCE(excluded.ram, clients.ram)
|
ram=COALESCE(excluded.ram, clients.ram),
|
||||||
|
is_admin=COALESCE(excluded.is_admin, clients.is_admin)
|
||||||
`,
|
`,
|
||||||
partial.id,
|
partial.id,
|
||||||
partial.hwid ?? partial.id,
|
partial.hwid ?? partial.id,
|
||||||
@@ -299,6 +303,7 @@ export function upsertClientRow(
|
|||||||
partial.cpu ?? null,
|
partial.cpu ?? null,
|
||||||
partial.gpu ?? null,
|
partial.gpu ?? null,
|
||||||
partial.ram ?? null,
|
partial.ram ?? null,
|
||||||
|
partial.isAdmin !== undefined ? (partial.isAdmin ? 1 : 0) : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (partial.hwid) {
|
if (partial.hwid) {
|
||||||
@@ -539,6 +544,8 @@ export function listClients(filters: ListFilters): ListResult {
|
|||||||
return `ORDER BY ${online}, ${bookmark}, LOWER(COALESCE(country, 'zz')) ASC, id ASC`;
|
return `ORDER BY ${online}, ${bookmark}, LOWER(COALESCE(country, 'zz')) ASC, id ASC`;
|
||||||
case "country_desc":
|
case "country_desc":
|
||||||
return `ORDER BY ${online}, ${bookmark}, LOWER(COALESCE(country, 'zz')) DESC, id ASC`;
|
return `ORDER BY ${online}, ${bookmark}, LOWER(COALESCE(country, 'zz')) DESC, id ASC`;
|
||||||
|
case "admin_first":
|
||||||
|
return `ORDER BY ${online}, ${bookmark}, is_admin DESC, id ASC`;
|
||||||
default:
|
default:
|
||||||
return `ORDER BY ${online}, ${bookmark}, last_seen DESC, id ASC`;
|
return `ORDER BY ${online}, ${bookmark}, last_seen DESC, id ASC`;
|
||||||
}
|
}
|
||||||
@@ -556,7 +563,7 @@ export function listClients(filters: ListFilters): ListResult {
|
|||||||
|
|
||||||
const rows = db
|
const rows = db
|
||||||
.query<any>(
|
.query<any>(
|
||||||
`SELECT id, hwid, role, host, os, arch, version, user, nickname, custom_tag as customTag, custom_tag_note as customTagNote, monitors, country, last_seen as lastSeen, online, ping_ms as pingMs, bookmarked, enrollment_status as enrollmentStatus, public_key as publicKey, key_fingerprint as keyFingerprint, cpu, gpu, ram, disconnect_reason as disconnectReason, disconnect_detail as disconnectDetail
|
`SELECT id, hwid, role, host, os, arch, version, user, nickname, custom_tag as customTag, custom_tag_note as customTagNote, monitors, country, last_seen as lastSeen, online, ping_ms as pingMs, bookmarked, enrollment_status as enrollmentStatus, public_key as publicKey, key_fingerprint as keyFingerprint, cpu, gpu, ram, is_admin as isAdmin, disconnect_reason as disconnectReason, disconnect_detail as disconnectDetail
|
||||||
FROM clients
|
FROM clients
|
||||||
${whereSql}
|
${whereSql}
|
||||||
${orderBy}
|
${orderBy}
|
||||||
@@ -588,6 +595,7 @@ export function listClients(filters: ListFilters): ListResult {
|
|||||||
cpu: c.cpu || null,
|
cpu: c.cpu || null,
|
||||||
gpu: c.gpu || null,
|
gpu: c.gpu || null,
|
||||||
ram: c.ram || null,
|
ram: c.ram || null,
|
||||||
|
isAdmin: c.isAdmin === 1,
|
||||||
disconnectReason: c.disconnectReason || null,
|
disconnectReason: c.disconnectReason || null,
|
||||||
disconnectDetail: c.disconnectDetail || null,
|
disconnectDetail: c.disconnectDetail || null,
|
||||||
thumbnail: getThumbnail(c.id),
|
thumbnail: getThumbnail(c.id),
|
||||||
|
|||||||
@@ -25,11 +25,14 @@ export function handleClientsRequest(req: Request): Response {
|
|||||||
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, enrollmentFilter: "approved" });
|
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, enrollmentFilter: "approved" });
|
||||||
const items = result.items.map((item) => {
|
const items = result.items.map((item) => {
|
||||||
const live = clientManager.getClient(item.id);
|
const live = clientManager.getClient(item.id);
|
||||||
if (live?.monitorInfo?.length) {
|
if (live) {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
monitors: live.monitorInfo.length,
|
isAdmin: live.isAdmin ?? item.isAdmin,
|
||||||
monitorInfo: live.monitorInfo,
|
...(live.monitorInfo?.length ? {
|
||||||
|
monitors: live.monitorInfo.length,
|
||||||
|
monitorInfo: live.monitorInfo,
|
||||||
|
} : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export type ClientInfo = {
|
|||||||
cpu?: string;
|
cpu?: string;
|
||||||
gpu?: string;
|
gpu?: string;
|
||||||
ram?: string;
|
ram?: string;
|
||||||
|
isAdmin?: boolean;
|
||||||
enrollmentStatus?: EnrollmentStatus;
|
enrollmentStatus?: EnrollmentStatus;
|
||||||
publicKey?: string;
|
publicKey?: string;
|
||||||
keyFingerprint?: string;
|
keyFingerprint?: string;
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ export function handleHello(
|
|||||||
info.monitors = payload.monitors;
|
info.monitors = payload.monitors;
|
||||||
info.monitorInfo = (payload as any).monitorInfo || info.monitorInfo;
|
info.monitorInfo = (payload as any).monitorInfo || info.monitorInfo;
|
||||||
info.inMemory = !!(payload as any).inMemory;
|
info.inMemory = !!(payload as any).inMemory;
|
||||||
|
info.isAdmin = !!(payload as any).isAdmin;
|
||||||
info.cpu = (payload as any).cpu || info.cpu;
|
info.cpu = (payload as any).cpu || info.cpu;
|
||||||
info.gpu = (payload as any).gpu || info.gpu;
|
info.gpu = (payload as any).gpu || info.gpu;
|
||||||
info.ram = (payload as any).ram || info.ram;
|
info.ram = (payload as any).ram || info.ram;
|
||||||
@@ -117,6 +118,7 @@ export function handleHello(
|
|||||||
cpu: info.cpu,
|
cpu: info.cpu,
|
||||||
gpu: info.gpu,
|
gpu: info.gpu,
|
||||||
ram: info.ram,
|
ram: info.ram,
|
||||||
|
isAdmin: info.isAdmin,
|
||||||
lastSeen: info.lastSeen,
|
lastSeen: info.lastSeen,
|
||||||
online: 1,
|
online: 1,
|
||||||
});
|
});
|
||||||
@@ -135,6 +137,7 @@ export function handlePing(info: ClientInfo, payload: WireMessage, ws: any) {
|
|||||||
id: info.id,
|
id: info.id,
|
||||||
lastSeen: info.lastSeen,
|
lastSeen: info.lastSeen,
|
||||||
online: 1,
|
online: 1,
|
||||||
|
isAdmin: info.isAdmin,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ws.send(encodeMessage({ type: "pong", ts: payload.ts || Date.now() }));
|
ws.send(encodeMessage({ type: "pong", ts: payload.ts || Date.now() }));
|
||||||
@@ -191,6 +194,7 @@ export function handlePong(info: ClientInfo, payload: WireMessage) {
|
|||||||
pingMs: info.pingMs,
|
pingMs: info.pingMs,
|
||||||
lastSeen: info.lastSeen,
|
lastSeen: info.lastSeen,
|
||||||
online: 1,
|
online: 1,
|
||||||
|
isAdmin: info.isAdmin,
|
||||||
});
|
});
|
||||||
lastClientDbSync.set(info.id, nowTs);
|
lastClientDbSync.set(info.id, nowTs);
|
||||||
|
|
||||||
@@ -201,6 +205,7 @@ export function handlePong(info: ClientInfo, payload: WireMessage) {
|
|||||||
id: info.id,
|
id: info.id,
|
||||||
lastSeen: info.lastSeen,
|
lastSeen: info.lastSeen,
|
||||||
online: 1,
|
online: 1,
|
||||||
|
isAdmin: info.isAdmin,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,7 +256,7 @@ export function handleFrame(info: ClientInfo, payload: any) {
|
|||||||
info.lastSeen = now;
|
info.lastSeen = now;
|
||||||
info.online = true;
|
info.online = true;
|
||||||
if (shouldSyncClientToDb(info.id, now)) {
|
if (shouldSyncClientToDb(info.id, now)) {
|
||||||
queueClientDbUpdate({ id: info.id, lastSeen: now, online: 1 });
|
queueClientDbUpdate({ id: info.id, lastSeen: now, online: 1, isAdmin: info.isAdmin });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user