more mac imporvements and refresh

This commit is contained in:
test
2026-06-04 20:54:33 -05:00
parent a35de5d129
commit 23158f3b83
12 changed files with 195 additions and 18 deletions
+38
View File
@@ -2613,6 +2613,44 @@ tbody tr:hover {
.cv-perm-chip:hover { filter: brightness(1.08); }
.cv-perm-chip.is-ok { background: rgba(16, 185, 129, 0.1); color: #86efac; border-color: rgba(16, 185, 129, 0.25); }
.cv-perm-chip.is-missing { background: rgba(244, 63, 94, 0.1); color: #fda4af; border-color: rgba(244, 63, 94, 0.25); }
.cv-perm-freshness {
font-size: 10px;
color: var(--cv-text-dim);
white-space: nowrap;
}
.cv-perm-readiness,
.cv-perm-actions {
display: inline-flex;
align-items: center;
gap: 5px;
flex-wrap: wrap;
}
.cv-perm-readiness {
width: 100%;
}
.cv-ready-chip,
.cv-perm-tool {
display: inline-flex;
align-items: center;
gap: 5px;
min-height: 22px;
padding: 2px 7px;
border-radius: 4px;
border: 1px solid transparent;
font-size: 11px;
white-space: nowrap;
}
.cv-ready-chip.is-ok { background: rgba(16, 185, 129, 0.08); color: #86efac; border-color: rgba(16, 185, 129, 0.2); }
.cv-ready-chip.is-warn { background: rgba(245, 158, 11, 0.1); color: #fcd34d; border-color: rgba(245, 158, 11, 0.24); }
.cv-perm-tool {
cursor: pointer;
font: inherit;
appearance: none;
background: rgba(148, 163, 184, 0.08);
color: var(--cv-text);
border-color: rgba(148, 163, 184, 0.18);
}
.cv-perm-tool:hover { background: rgba(148, 163, 184, 0.14); }
.cv-group {
display: inline-flex;
+40 -9
View File
@@ -184,12 +184,13 @@ function macPermissionLabels(keys = []) {
const labels = {
accessibility: "Accessibility",
screenRecording: "Screen Recording",
inputMonitoring: "Input Monitoring",
fullDiskAccess: "Full Disk Access",
};
return keys.map((key) => labels[key] || key).join(", ");
}
async function requestMacPermissions(clientId, permissionKey = "") {
async function requestMacPermissions(clientId, permissionKey = "", { refreshOnly = false } = {}) {
if (!clientId) return;
if (!isClientOnline(clientId)) {
alert("Client is offline. macOS permissions can only be requested while the client is online.");
@@ -200,14 +201,20 @@ async function requestMacPermissions(clientId, permissionKey = "") {
return;
}
const requested = permissionKey ? [permissionKey] : ["accessibility", "screenRecording", "fullDiskAccess"];
const requested = permissionKey
? [permissionKey]
: refreshOnly
? ["accessibility", "screenRecording", "fullDiskAccess"]
: ["accessibility", "screenRecording", "inputMonitoring", "fullDiskAccess"];
const label = macPermissionLabels(requested) || "macOS permissions";
const proceed = confirm(
`Request ${label} from ${clientId}?\n\n` +
"This can show a macOS prompt on the target Mac. " +
"Full Disk Access must be granted in System Settings; the agent will open the Privacy pane when possible.",
);
if (!proceed) return;
if (!refreshOnly) {
const proceed = confirm(
`Request ${label} from ${clientId}?\n\n` +
"This can show a macOS prompt on the target Mac. " +
"Full Disk Access must be granted in System Settings; the agent will open the Privacy pane when possible.",
);
if (!proceed) return;
}
try {
const res = await fetch(`/api/clients/${encodeURIComponent(clientId)}/command`, {
@@ -216,6 +223,7 @@ async function requestMacPermissions(clientId, permissionKey = "") {
body: JSON.stringify({
action: "darwin_request_permissions",
permissions: requested,
refreshOnly,
}),
});
const data = await res.json().catch(() => ({}));
@@ -225,7 +233,9 @@ async function requestMacPermissions(clientId, permissionKey = "") {
}
const missing = Array.isArray(data.missing) ? data.missing : [];
const stillMissingRequested = missing.filter((key) => requested.includes(key));
if (stillMissingRequested.length) {
if (refreshOnly) {
window.showToast?.("macOS permissions refreshed", "success", 2500);
} else if (stillMissingRequested.length) {
alert(`Permission request completed, but still missing: ${macPermissionLabels(stillMissingRequested)}`);
} else {
alert(`${label} is granted.`);
@@ -236,6 +246,25 @@ async function requestMacPermissions(clientId, permissionKey = "") {
}
}
async function applyMacPermissionChanges(clientId) {
if (!clientId) return;
if (!isClientOnline(clientId)) {
alert("Client is offline.");
return;
}
const proceed = confirm(
`Reconnect ${clientId} to apply macOS permission changes?\n\n` +
"The client will reconnect and report fresh permission state.",
);
if (!proceed) return;
markManualDisconnect(clientId);
const ok = await sendCommand(clientId, "reconnect");
if (ok) {
window.showToast?.("Reconnect requested", "success", 2500);
setTimeout(() => loadWithOptions({ force: true }), 1200);
}
}
window.__uninstallingClientIds = window.__uninstallingClientIds || new Set();
function flipFallAndRemove(clientId, { delayMs = 0 } = {}) {
@@ -910,6 +939,8 @@ function initializeRenderer() {
requestThumbnail,
pingClient: (id) => sendCommand(id, "ping"),
onMacPermissionRequest: (id, _card, permissionKey) => requestMacPermissions(id, permissionKey),
onMacPermissionRefresh: (id) => requestMacPermissions(id, "", { refreshOnly: true }),
onMacPermissionApply: (id) => applyMacPermissionChanges(id),
userRole: currentUser?.role,
getServerVersion: () => currentServerVersion,
getDisplayFields: () => displayFields,
+43 -2
View File
@@ -208,6 +208,7 @@ function isMacClient(client) {
const MAC_PERMISSION_LABELS = [
["accessibility", "Accessibility"],
["screenRecording", "Screen Recording"],
["inputMonitoring", "Input Monitoring"],
["fullDiskAccess", "Full Disk Access"],
];
@@ -234,12 +235,36 @@ function macPermissionDetailHtml(client) {
if (!state) return "";
const ok = state.missing.length === 0;
const summary = ok ? "All required permissions granted" : `Missing: ${state.missing.map((item) => item.label).join(", ")}`;
const granted = Object.fromEntries(state.items.map((item) => [item.key, item.granted]));
const featureItems = [
{
label: "Remote Desktop",
ready: granted.screenRecording && granted.accessibility,
missing: ["screenRecording", "accessibility"].filter((key) => !granted[key]),
},
{
label: "Keylogger",
ready: granted.accessibility && granted.inputMonitoring,
missing: ["accessibility", "inputMonitoring"].filter((key) => !granted[key]),
},
{
label: "Files",
ready: granted.fullDiskAccess,
missing: granted.fullDiskAccess ? [] : ["fullDiskAccess"],
},
];
const labelFor = (key) => MAC_PERMISSION_LABELS.find(([k]) => k === key)?.[1] || key;
const chips = state.items.map((item) => `
<button type="button" class="cv-perm-chip ${item.granted ? "is-ok" : "is-missing"}" data-mac-permission-key="${escapeHtml(item.key)}" title="Request ${escapeHtml(item.label)} permission">
<button type="button" class="cv-perm-chip ${item.granted ? "is-ok" : "is-missing"}" data-mac-permission-key="${escapeHtml(item.key)}" title="${escapeHtml(item.key === "inputMonitoring" ? "Request Input Monitoring permission; reconnect required after granting" : `Request ${item.label} permission`)}">
<i class="fa-solid ${item.granted ? "fa-check" : "fa-xmark"}"></i>${escapeHtml(item.label)}
</button>
`).join("");
return `<div class="cv-field cv-field-wide cv-field-macperms"><span class="cv-field-label">macOS Permissions</span><span class="cv-field-value cv-perm-list"><span class="cv-perm-summary ${ok ? "is-ok" : "is-warn"}">${escapeHtml(summary)}</span>${chips}</span></div>`;
const readiness = featureItems.map((item) => `
<span class="cv-ready-chip ${item.ready ? "is-ok" : "is-warn"}" title="${escapeHtml(item.ready ? "Ready" : `Missing ${item.missing.map(labelFor).join(", ")}`)}">
<i class="fa-solid ${item.ready ? "fa-circle-check" : "fa-circle-exclamation"}"></i>${escapeHtml(item.label)}
</span>
`).join("");
return `<div class="cv-field cv-field-wide cv-field-macperms"><span class="cv-field-label">macOS Permissions</span><span class="cv-field-value cv-perm-list"><span class="cv-perm-summary ${ok ? "is-ok" : "is-warn"}">${escapeHtml(summary)}</span>${chips}<span class="cv-perm-freshness">Checked ${escapeHtml(formatAgo(client.lastSeen))}; Input Monitoring updates after reconnect</span><span class="cv-perm-readiness">${readiness}</span><span class="cv-perm-actions"><button type="button" class="cv-perm-tool" data-mac-permission-refresh="1" title="Re-check permissions except Input Monitoring"><i class="fa-solid fa-rotate"></i>Refresh</button><button type="button" class="cv-perm-tool" data-mac-permission-apply="1" title="Reconnect agent to apply newly granted permissions"><i class="fa-solid fa-power-off"></i>Apply</button></span></span></div>`;
}
function shortOsLabel(osRaw = "") {
@@ -282,6 +307,8 @@ export function createRenderer({
requestThumbnail,
pingClient,
onMacPermissionRequest,
onMacPermissionRefresh,
onMacPermissionApply,
userRole,
getServerVersion,
getDisplayFields,
@@ -390,6 +417,20 @@ export function createRenderer({
return;
}
if (e.target.closest("[data-mac-permission-refresh]")) {
e.preventDefault();
e.stopPropagation();
if (onMacPermissionRefresh) onMacPermissionRefresh(clientId, card);
return;
}
if (e.target.closest("[data-mac-permission-apply]")) {
e.preventDefault();
e.stopPropagation();
if (onMacPermissionApply) onMacPermissionApply(clientId, card);
return;
}
const copyBtn = e.target.closest(".copy-id-btn");
if (copyBtn) {
e.stopPropagation();