// // System section: the container services, the builder, disk usage and any // local DNS domains. // const { Controller } = require("./Controller"); const { SystemTreeItem } = require("../TreeItems/SystemTreeItem"); const { ContainerCLI } = require("../../Interfaces/ContainerCLI"); const systemConfig = require("../../Utilities/SystemConfig"); const format = require("../../Utilities/Format"); const { Terminal } = require("../../Interfaces/Terminal"); const notify = require("../../Utilities/Notify"); const prompt = require("../../Utilities/Prompt"); class SystemController extends Controller { constructor(sidebar) { super("apple_container.system"); this.sidebar = sidebar; this.running = false; this.registerCommands(); } async fetch() { let status = await ContainerCLI.systemStatus(); let running = status.status === "running"; let rows = []; this.running = running; rows.push({ identifier: "system.services", label: "Services", running: running, contextValue: running ? "system.services.running" : "system.services.stopped", description: running ? version(status.apiServerVersion) : "stopped", tooltip: statusTooltip(status), copy: status.apiServerVersion || "", children: [] }); if (!running) { return rows; } let [builder, usage, domains] = await Promise.all([ ContainerCLI.builderRunning(), ContainerCLI.systemDiskUsage(), ContainerCLI.dnsDomains() ]); rows.push({ identifier: "system.builder", label: "Builder", running: builder, contextValue: builder ? "system.builder.running" : "system.builder.stopped", description: builder ? "running" : "stopped", tooltip: "The builder container backing `container build`.", copy: "builder", children: [] }); if (usage) { rows.push({ identifier: "system.disk", label: "Disk Usage", contextValue: "system.disk", image: "icons/system.png", description: usageSummary(usage), tooltip: usageTooltip(usage), copy: usageSummary(usage), children: [] }); } // The domain containers are named under, which is separate from the // domains macOS has been told to resolve. let hostnameDomain = systemConfig.defaultDomain(); let resolved = hostnameDomain !== null && domains.includes(hostnameDomain); rows.push({ identifier: "system.dns.default", label: "Hostname Domain", contextValue: "system.dns.default", image: hostnameDomain && !resolved ? "icons/status/warning.png" : "icons/network.png", description: hostnameDomain ? `.${hostnameDomain}` : "not set", tooltip: hostnameTooltip(hostnameDomain, resolved), copy: hostnameDomain || "", children: [] }); for (let domain of domains) { rows.push({ identifier: `system.dns.${domain}`, label: domain, contextValue: "system.dns", image: "icons/network.png", description: "resolved by macOS", tooltip: `macOS resolves *.${domain} through Apple Container's DNS service.`, copy: domain, children: [] }); } return rows; } signature(rows) { return JSON.stringify(rows.map((row) => [row.identifier, row.description, row.running])); } getTreeItem(element) { return new SystemTreeItem(element); } registerCommands() { this.register("apple_container.system.df", async () => { try { let result = await ContainerCLI.run(["system", "df", "--format", "json"]); nova.workspace.openNewTextDocument({ content: result.stdout, syntax: "json" }); } catch (error) { notify.failure("Unable to read disk usage", error, "apple-container-df"); } }); this.register("apple_container.dns.unregister", async () => { let row = this.selection[0]; if (!row || !row.copy) { return; } if ( !(await prompt.confirm( `Stop macOS resolving *.${row.copy}? This runs sudo in your terminal.`, "Unregister" )) ) { return; } await Terminal.run( `sudo ${Terminal.command([ContainerCLI.path(), "system", "dns", "delete", row.copy])}` ); }); this.register("apple_container.system.copy", () => { let row = this.selection[0]; if (row && row.copy) { nova.clipboard.writeText(row.copy); } }); } } function hostnameTooltip(domain, resolved) { if (!domain) { return [ "Containers have unqualified hostnames.", "Set a domain to reach them as .." ].join("\n"); } if (resolved) { return `Containers are reachable as .${domain} from this Mac.`; } return [ `Containers are named .${domain}, but macOS does not resolve that domain yet.`, "Use Set Up Container Hostnames to register it." ].join("\n"); } function version(text) { if (!text) { return "running"; } let match = String(text).match(/version\s+(\S+)/); return match ? `running ${match[1]}` : "running"; } function statusTooltip(status) { if (status.status !== "running") { return "Container services are not running.\nUse Start Services to launch them."; } return [ `Status:\t${status.status}`, status.apiServerVersion ? `Version:\t${status.apiServerVersion}` : "", status.appRoot ? `App root:\t${status.appRoot}` : "", status.installRoot ? `Install:\t${status.installRoot}` : "" ] .filter((line) => line.length > 0) .join("\n"); } function usageSummary(usage) { let total = ["images", "containers", "volumes"].reduce( (sum, key) => sum + ((usage[key] && usage[key].sizeInBytes) || 0), 0 ); let reclaimable = ["images", "containers", "volumes"].reduce( (sum, key) => sum + ((usage[key] && usage[key].reclaimable) || 0), 0 ); return `${format.bytes(total)} ${format.bytes(reclaimable)} reclaimable`; } function usageTooltip(usage) { return ["images", "containers", "volumes"] .map((key) => { let entry = usage[key] || {}; let name = key.charAt(0).toUpperCase() + key.slice(1); return `${name}:\t${entry.total || 0}\t${format.bytes(entry.sizeInBytes || 0)}`; }) .join("\n"); } module.exports = { SystemController };