121 lines
4.0 KiB
QML
121 lines
4.0 KiB
QML
pragma Singleton
|
|
|
|
// Redacted Home Assistant configuration state. The helper is the only object
|
|
// that touches the private env file; QML never receives the stored token.
|
|
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import QtQuick
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
readonly property string helperPath: Quickshell.shellDir + "/scripts/panama-home-assistant-config"
|
|
|
|
property string url: ""
|
|
property var entities: []
|
|
property bool tokenConfigured: false
|
|
property bool configured: false
|
|
property string lastError: ""
|
|
property string pendingPayload: ""
|
|
readonly property bool busy: statusProc.running || writeProc.running
|
|
|
|
signal configurationSaved
|
|
|
|
function errorMessage(code: string): string {
|
|
switch (code) {
|
|
case "invalid-url": return "Enter an HTTP or HTTPS Home Assistant URL.";
|
|
case "invalid-token": return "The access token contains unsupported characters.";
|
|
case "invalid-entities": return "Entity IDs must look like light.living_room.";
|
|
case "read-failed": return "The private configuration file could not be read.";
|
|
case "write-failed": return "The private configuration file could not be saved.";
|
|
case "invalid-payload": return "The configuration could not be validated.";
|
|
default: return code ? "Home Assistant configuration is unavailable." : "";
|
|
}
|
|
}
|
|
|
|
function applyResult(text: string, saved: bool): void {
|
|
let result = null;
|
|
try {
|
|
result = JSON.parse(text);
|
|
} catch (error) {
|
|
result = { ok: false, error: "invalid-response" };
|
|
}
|
|
if (result.ok !== true) {
|
|
root.lastError = root.errorMessage(String(result.error || "invalid-response"));
|
|
return;
|
|
}
|
|
root.url = String(result.url || "");
|
|
root.entities = Array.isArray(result.entities) ? result.entities : [];
|
|
root.tokenConfigured = result.tokenConfigured === true;
|
|
root.configured = result.configured === true;
|
|
root.lastError = "";
|
|
if (saved) {
|
|
root.configurationSaved();
|
|
HomeAssistant.refresh();
|
|
}
|
|
}
|
|
|
|
function refresh(): void {
|
|
if (!statusProc.running && !writeProc.running)
|
|
statusProc.running = true;
|
|
}
|
|
|
|
// An empty token means "keep the stored token". Clearing is an explicit
|
|
// separate action so editing the URL can never erase a secret by accident.
|
|
function save(url: string, entitiesText: string, token: string): bool {
|
|
if (root.busy)
|
|
return false;
|
|
const payload = { url: url, entities: entitiesText };
|
|
if (token.trim() !== "")
|
|
payload.token = token;
|
|
return root.startWrite(payload);
|
|
}
|
|
|
|
function clearToken(): bool {
|
|
if (root.busy || !root.tokenConfigured)
|
|
return false;
|
|
return root.startWrite({ token: "" });
|
|
}
|
|
|
|
function startWrite(payload: var): bool {
|
|
root.lastError = "";
|
|
root.pendingPayload = JSON.stringify(payload);
|
|
writeProc.running = true;
|
|
return true;
|
|
}
|
|
|
|
Process {
|
|
id: statusProc
|
|
command: [root.helperPath, "status"]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.applyResult(this.text, false)
|
|
}
|
|
onExited: (code, status) => {
|
|
if (code !== 0 && root.lastError === "")
|
|
root.lastError = "Home Assistant configuration could not be loaded.";
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: writeProc
|
|
command: [root.helperPath, "write"]
|
|
stdinEnabled: true
|
|
stdout: StdioCollector {
|
|
id: writeOutput
|
|
onStreamFinished: root.applyResult(this.text, true)
|
|
}
|
|
onStarted: {
|
|
writeProc.write(root.pendingPayload + "\n");
|
|
root.pendingPayload = "";
|
|
}
|
|
onExited: (code, status) => {
|
|
root.pendingPayload = "";
|
|
if (code !== 0 && root.lastError === "")
|
|
root.lastError = "Home Assistant configuration could not be saved.";
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: root.refresh()
|
|
}
|