diff --git a/assets/404.html b/assets/404.html
index 3e5a14b..9d856c9 100644
--- a/assets/404.html
+++ b/assets/404.html
@@ -1,5 +1,5 @@
<meta charset="UTF-8">
-<html>
+<html lang="en">
<body>
<img src="libwebsockets.org-logo.svg"><br>
<h1>404</h1>
diff --git a/assets/failed-login.html b/assets/failed-login.html
index 9ab065b..66bfe9c 100644
--- a/assets/failed-login.html
+++ b/assets/failed-login.html
@@ -1,3 +1,3 @@
-<html>
+<html lang="en">
This is an example destination that will appear after a failed login
</html>
diff --git a/assets/gs/admin-login.html b/assets/gs/admin-login.html
index 113df9c..fa36384 100644
--- a/assets/gs/admin-login.html
+++ b/assets/gs/admin-login.html
@@ -1,4 +1,4 @@
-<html>
+<html lang="en">
This is an example destination that will appear after successful Admin login.
This URL cannot be served if you're not logged in as admin.
diff --git a/assets/gs/index.html b/assets/gs/index.html
index d40904a..a0445a0 100644
--- a/assets/gs/index.html
+++ b/assets/gs/index.html
@@ -1,4 +1,4 @@
-<html>
+<html lang="en">
<head>
<meta charset="UTF-8">
<script src="/lws-common.js"></script>
diff --git a/assets/gs/post-forgot-fail.html b/assets/gs/post-forgot-fail.html
index ead3d13..ed575a6 100644
--- a/assets/gs/post-forgot-fail.html
+++ b/assets/gs/post-forgot-fail.html
@@ -1,4 +1,4 @@
-<html>
+<html lang="en">
Sorry, something went wrong.
Click <a href="../">here</a> to continue.
diff --git a/assets/gs/post-forgot-ok.html b/assets/gs/post-forgot-ok.html
index 3e8e9cf..bfec1e0 100644
--- a/assets/gs/post-forgot-ok.html
+++ b/assets/gs/post-forgot-ok.html
@@ -1,4 +1,4 @@
-<html>
+<html lang="en">
This is a one-time password recovery login.
Please click <a href="./">here</a> and click your username at the top to reset your password.
diff --git a/assets/gs/post-verify-fail.html b/assets/gs/post-verify-fail.html
index d1d89ca..d0e5b3c 100644
--- a/assets/gs/post-verify-fail.html
+++ b/assets/gs/post-verify-fail.html
@@ -1,4 +1,4 @@
-<html>
+<html lang="en">
<head>
<script src="lwsgs.js"></script>
</head>
diff --git a/assets/gs/post-verify-ok.html b/assets/gs/post-verify-ok.html
index ae647fc..918b713 100644
--- a/assets/gs/post-verify-ok.html
+++ b/assets/gs/post-verify-ok.html
@@ -1,4 +1,4 @@
-<html>
+<html lang="en">
<head>
<script src="lwsgs.js"></script>
</head>
diff --git a/assets/gs/sent-forgot-fail.html b/assets/gs/sent-forgot-fail.html
index ead3d13..ed575a6 100644
--- a/assets/gs/sent-forgot-fail.html
+++ b/assets/gs/sent-forgot-fail.html
@@ -1,4 +1,4 @@
-<html>
+<html lang="en">
Sorry, something went wrong.
Click <a href="../">here</a> to continue.
diff --git a/assets/gs/successful-login.html b/assets/gs/successful-login.html
index dfc25cf..39a47d5 100644
--- a/assets/gs/successful-login.html
+++ b/assets/gs/successful-login.html
@@ -1,4 +1,4 @@
-<html>
+<html lang="en">
This is an example destination that will appear after successful non-Admin login
</html>
diff --git a/assets/post-register-ok.html b/assets/post-register-ok.html
index f5d70b8..b1bddd5 100644
--- a/assets/post-register-ok.html
+++ b/assets/post-register-ok.html
@@ -1,4 +1,4 @@
-<html>
+<html lang="en">
<body>
<table>
<tr>
diff --git a/assets/sai.css b/assets/sai.css
index 60109bf..189afb3 100644
--- a/assets/sai.css
+++ b/assets/sai.css
@@ -1287,3 +1287,7 @@ canvas.power-graph {
background: #fff;
box-sizing: border-box;
}
+
+.pcon-header-on {
+ background-color: #79da80;
+}
diff --git a/assets/sai.js b/assets/sai.js
index 2aa519d..dd78704 100644
--- a/assets/sai.js
+++ b/assets/sai.js
@@ -394,7 +394,7 @@ var lang_zhs = "{" +
"\"%{pf}前创建, 创作时间: %{ct}ms \"" +
"}}";
-var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, exptimer, auth_user = "",
+var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, auth_is_admin = 0, exptimer, auth_user = "",
logAnsiState = {}, logs_pending = "", lines_pending = "", times_pending = "",
ongoing_task_activities = {}, last_log_timestamp = 0, spreadsheet_data_cache = {}, loadreport_data_cache = {},
fadingTasks = new Map();
@@ -426,7 +426,7 @@ function createPconDiv(pcon) {
const header = document.createElement("div");
header.className = "pcon-header";
- let stateClass = pcon.on ? "pcon-on" : "pcon-off";
+ let stateClass = (pcon.on === 1) ? "pcon-on" : "pcon-off";
let type = pcon.type ? `(${pcon.type})` : "";
header.innerHTML = `<span class="${stateClass}">⏻</span> <b>${hsanitize(pcon.name)}</b> <span class="pcon-type">${hsanitize(type)}</span>`;
@@ -1429,11 +1429,13 @@ function createBuilderDiv(plat) {
else {
if (!plat.power_managed)
platDiv.className += " power-unmanaged";
- else
- if (plat.stay_on !== 0)
+ else {
+ let pcon = pcon_topology[plat.pcon];
+ if (pcon && pcon.manual_on)
platDiv.className += " power-stay";
else
platDiv.className += " power-stay-dep";
+ }
}
if (plat.powering_up)
platDiv.className += " powering-up";
@@ -1462,7 +1464,8 @@ function createBuilderDiv(plat) {
`<div class="res-bar"><div class="res-bar-inner res-bar-ram w-0"></div></div>` +
`<div class="res-bar"><div class="res-bar-inner res-bar-disk w-0"></div></div>` +
`</div>`;
- innerHTML += `${hsanitize(plat.peer_ip)}` + " " + hsanitize(plat.stay_on);
+ if (authd && auth_is_admin && plat.peer_ip)
+ innerHTML += `<span class="plat-peer-ip" style="font-size: 0.8em; color: #555;">${hsanitize(plat.peer_ip)}</span>`;
innerHTML += `</td></tr></tbody></table>`;
platDiv.innerHTML = innerHTML;
@@ -1481,34 +1484,6 @@ function createBuilderDiv(plat) {
{ label: `<b>LWS:</b> ${plat.lws_hash}` },
];
- if (plat.power_managed && authd) {
- if (plat.stay_on !== 0) {
- menuItems.push({
- label: "Release Stay",
- callback: () => {
- const stayMsg = {
- schema: "com.warmcat.sai.stay",
- builder_name: plat.name.split('.')[0],
- stay_on: 0
- };
- sai.send(JSON.stringify(stayMsg));
- }
- });
- } else {
- menuItems.push({
- label: "Stay On",
- callback: () => {
- const stayMsg = {
- schema: "com.warmcat.sai.stay",
- builder_name: plat.name.split('.')[0],
- stay_on: 1
- };
- sai.send(JSON.stringify(stayMsg));
- }
- });
- }
- }
-
platDiv.addEventListener("contextmenu", function(event) {
if (!authd)
return;
@@ -1619,7 +1594,27 @@ function createPconDiv(pcon) {
const header = document.createElement("div");
header.className = "pcon-header";
- let stateClass = pcon.on ? "pcon-on" : "pcon-off";
+ const myBuilders = last_builder_list.filter(b => b.pcon === pcon.name);
+ let anyConnected = myBuilders.some(b => b.online === 1);
+ let anyPoweringUp = myBuilders.some(b => b.powering_up === 1);
+
+ let isActuallyOn = false;
+ let stateClass = "pcon-off";
+
+ if (pcon_energy_cache[pcon.name]) {
+ const d = pcon_energy_cache[pcon.name];
+ let hasPower = d.voltage_v >= 70 && d.active_power_w > 0;
+ isActuallyOn = anyConnected || anyPoweringUp || hasPower;
+ stateClass = hasPower ? "pcon-on" : "pcon-off";
+ } else {
+ isActuallyOn = anyConnected || (pcon.on === 1 && !anyPoweringUp);
+ stateClass = (pcon.on === 1) ? "pcon-on" : "pcon-off";
+ }
+
+ if (isActuallyOn) {
+ header.className += " pcon-header-on";
+ }
+
let type = pcon.type ? `(${pcon.type})` : "";
header.innerHTML = `<span class="${stateClass}">⏻</span> <b>${hsanitize(pcon.name)}</b> <span class="pcon-type">${hsanitize(type)}</span>`;
@@ -1649,7 +1644,7 @@ function createPconDiv(pcon) {
];
if (authd) {
- if (pcon.on) {
+ if (isActuallyOn) {
menuItems.push({
label: "Turn Off",
callback: () => {
@@ -2038,7 +2033,7 @@ function ws_open_sai()
if (b.pcon && !pcon_topology[b.pcon]) {
pcon_topology[b.pcon] = {
name: b.pcon,
- on: 1, /* Default to on so it shows green if unknown */
+ on: 0, /* Default to off so it shows grey until we get real state */
type: "auto-discovered",
depends_on: "",
children: []
@@ -2090,13 +2085,30 @@ function ws_open_sai()
}
const d = item;
-// stats.textContent = `${d.voltage_v}V ${d.active_power_w}W ${d.current_ma}mA today:${(d.energy_today_wh/1000).toFixed(3)}kWh`;
+ let hasPower = false;
if (d.voltage_v < 70)
stats.textContent = "unpowered";
else if (!d.active_power_w)
stats.textContent = "OFF";
- else
+ else {
stats.textContent = `${d.active_power_w}W`;
+ hasPower = true;
+ }
+
+ let plugIcon = header.querySelector("span");
+ if (plugIcon) {
+ plugIcon.className = hasPower ? "pcon-on" : "pcon-off";
+ }
+
+ const myBuilders = last_builder_list.filter(b => b.pcon === item.name);
+ let anyConnected = myBuilders.some(b => b.online === 1);
+ let anyPoweringUp = myBuilders.some(b => b.powering_up === 1);
+
+ if (anyConnected || anyPoweringUp || hasPower) {
+ header.classList.add("pcon-header-on");
+ } else {
+ header.classList.remove("pcon-header-on");
+ }
}
});
@@ -2788,16 +2800,20 @@ window.addEventListener("load", function() {
fetch('.lws-login-status')
.then(function(res) { return res.json(); })
.then(function(data) {
- if (data.logged_in && data.has_grant)
+ if (data.logged_in && data.has_grant) {
authd = 1;
+ if (data.is_admin)
+ auth_is_admin = 1;
+ }
})
.catch(function(err) {
console.log('lws-login auth fetch failed: ', err);
+ })
+ .finally(function() {
+ ws_open_sai();
+ aging();
});
- ws_open_sai();
- aging();
-
setInterval(function() {
update_task_activities();
diff --git a/src/builder/b-nspawn.c b/src/builder/b-nspawn.c
index 024cf4c..ad60019 100644
--- a/src/builder/b-nspawn.c
+++ b/src/builder/b-nspawn.c
@@ -137,8 +137,13 @@ callback_sai_stdwsi(struct lws *wsi, enum lws_callback_reasons reason,
return -1;
}
- if (saib_log_chunk_create(op->ns, buf, len, lws_spawn_get_stdfd(wsi)))
- return -1;
+ {
+ int ch = lws_spawn_get_stdfd(wsi);
+ if (ch == 0)
+ ch = 1;
+ if (saib_log_chunk_create(op->ns, buf, len, ch))
+ return -1;
+ }
return lws_ss_request_tx(op->ns->spm->ss) ? -1 : 0;
@@ -612,6 +617,7 @@ saib_spawn_script(struct sai_nspawn *ns)
info.max_log_lines = 10000;
info.timeout_us = 30 * 60 * LWS_US_PER_SEC;
info.reap_cb = sai_lsp_reap_cb;
+ info.pty_mode = 1;
memset(&ns->res, 0, sizeof(ns->res));
info.res = &ns->res;
#if defined(__linux__)
diff --git a/src/common/include/private.h b/src/common/include/private.h
index 92aee13..222cedc 100644
--- a/src/common/include/private.h
+++ b/src/common/include/private.h
@@ -531,9 +531,9 @@ typedef struct sai_plat {
unsigned int avail_mem_kib;
unsigned int avail_sto_kib;
- char windows;
- char power_managed;
- char stay_on;
+ unsigned int windows;
+ unsigned int power_managed;
+ unsigned int stay_on;
char busy;
int index; /* used to create unique build dir path */
@@ -604,7 +604,7 @@ typedef struct sai_stay {
lws_dll2_t list;
char builder_name[64];
char pcon_name[64];
- char stay_on; /* 0 = release, 1 = set */
+ unsigned int stay_on; /* 0 = release, 1 = set */
} sai_stay_t;
typedef struct sai_controlled_builder {
@@ -619,14 +619,15 @@ typedef struct sai_power_controller {
char name[64];
char type[32];
char depends_on[64];
- char on;
+ unsigned int on;
+ unsigned int manual_on;
} sai_power_controller_t;
/* sai-power -> sai-server, tells it the builders it can manage */
typedef struct sai_power_managed_builder {
lws_dll2_t list;
char name[64];
- char stay_on;
+ unsigned int stay_on;
} sai_power_managed_builder_t;
typedef struct sai_power_managed_builders {
@@ -639,7 +640,7 @@ typedef struct sai_power_managed_builders {
typedef struct sai_stay_state_update {
lws_dll2_t list;
char builder_name[64];
- char stay_on;
+ unsigned int stay_on;
} sai_stay_state_update_t;
/* Builder -> sai-power registration */
@@ -699,7 +700,7 @@ extern const lws_struct_map_t
lsm_power_managed_builder[2],
lsm_power_managed_builders_list[2],
lsm_schema_power_managed_builders[1],
- lsm_power_controller[5],
+ lsm_power_controller[6],
lsm_schema_json_map_task[],
lsm_schema_sq3_map_task[],
lsm_schema_sq3_map_event[],
diff --git a/src/common/struct-metadata.c b/src/common/struct-metadata.c
index 845c29c..85b6f9a 100644
--- a/src/common/struct-metadata.c
+++ b/src/common/struct-metadata.c
@@ -324,6 +324,7 @@ const lws_struct_map_t lsm_power_controller[] = {
LSM_CARRAY(sai_power_controller_t, type, "type"),
LSM_CARRAY(sai_power_controller_t, depends_on, "depends_on"),
LSM_UNSIGNED(sai_power_controller_t, on, "on"),
+ LSM_UNSIGNED(sai_power_controller_t, manual_on, "manual_on"),
LSM_LIST(sai_power_controller_t, controlled_builders_owner,
sai_controlled_builder_t, list, NULL,
lsm_controlled_builder, "controlled_builders"),
diff --git a/src/power/p-http-api.c b/src/power/p-http-api.c
index b1db6c8..45d70ee 100644
--- a/src/power/p-http-api.c
+++ b/src/power/p-http-api.c
@@ -140,7 +140,7 @@ saip_notify_server_stay_state(const char *builder_name, int stay_on)
memset(&ssu, 0, sizeof(ssu));
lws_strncpy(ssu.builder_name, builder_name, sizeof(ssu.builder_name));
- ssu.stay_on = (char)stay_on;
+ ssu.stay_on = (uint8_t)stay_on;
sai_ss_serialize_queue_helper(sps->ss, &m->bl_pwr_to_srv,
lsm_schema_stay_state_update,
diff --git a/src/power/p-ws-server.c b/src/power/p-ws-server.c
index 6ad2e64..99f69e3 100644
--- a/src/power/p-ws-server.c
+++ b/src/power/p-ws-server.c
@@ -149,7 +149,8 @@ saip_queue_stay_info(saip_server_t *sps)
if (pc1 && pc->name) {
lws_strncpy(pc1->name, pc->name, sizeof(pc1->name));
- pc1->on = pc->on;
+ pc1->on = (unsigned int)pc->on;
+ pc1->manual_on = !!(pc->flags & SAIP_PCON_F_MANUAL_STAY);
if (pc->depends_on)
lws_strncpy(pc1->depends_on, pc->depends_on, sizeof(pc1->depends_on));
if (pc->type)
diff --git a/src/server/s-central.c b/src/server/s-central.c
index 802a062..122204b 100644
--- a/src/server/s-central.c
+++ b/src/server/s-central.c
@@ -161,6 +161,11 @@ sais_ensure_tables(struct vhd *vhd)
/* lwsl_notice("%s: (stateless) %s\n", __func__, err); */
sqlite3_free(err);
}
+ sqlite3_exec(vhd->server.pdb, "ALTER TABLE power_controllers ADD COLUMN manual_on integer;", NULL, NULL, &err);
+ if (err) {
+ /* lwsl_notice("%s: (stateless) %s\n", __func__, err); */
+ sqlite3_free(err);
+ }
sai_sqlite3_statement(vhd->server.pdb,
"CREATE TABLE IF NOT EXISTS power_controllers ("
@@ -168,7 +173,8 @@ sais_ensure_tables(struct vhd *vhd)
" type varchar(32),"
" url varchar(128),"
" depends_on varchar(64),"
- " state integer"
+ " state integer,"
+ " manual_on integer"
");", "create pcon table");
sai_sqlite3_statement(vhd->server.pdb,
diff --git a/src/server/s-power.c b/src/server/s-power.c
index 0546b7e..dac5982 100644
--- a/src/server/s-power.c
+++ b/src/server/s-power.c
@@ -151,8 +151,8 @@ sais_power_rx(struct vhd *vhd, struct pss *pss, uint8_t *buf,
/* Insert PCON */
lws_snprintf(q, sizeof(q),
- "INSERT OR REPLACE INTO power_controllers (name, type, url, depends_on, state) VALUES ('%s', '%s', '', '%s', %d)",
- pc->name, pc->type, pc->depends_on, pc->on);
+ "INSERT OR REPLACE INTO power_controllers (name, type, url, depends_on, state, manual_on) VALUES ('%s', '%s', '', '%s', %d, %d)",
+ pc->name, pc->type, pc->depends_on, pc->on, pc->manual_on);
sai_sqlite3_statement(vhd->server.pdb, q, "insert pcon");
/* Insert Controlled Builders */
diff --git a/src/server/s-ws-web.c b/src/server/s-ws-web.c
index 0584aa5..70377d9 100644
--- a/src/server/s-ws-web.c
+++ b/src/server/s-ws-web.c
@@ -192,7 +192,7 @@ sais_list_pcons(struct vhd *vhd)
* complex inner/outer lws_struct_sq3 memory management edge cases that truncates rows.
*/
lws_snprintf(pcon_query, sizeof(pcon_query),
- "SELECT name, type, depends_on, state FROM power_controllers ORDER BY name LIMIT 100");
+ "SELECT name, type, depends_on, state, manual_on FROM power_controllers ORDER BY name LIMIT 100");
if (sqlite3_prepare_v2(vhd->server.pdb, pcon_query, -1, &pcon_stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(pcon_stmt) == SQLITE_ROW) {
@@ -208,7 +208,8 @@ sais_list_pcons(struct vhd *vhd)
const char *depends_on = (const char *)sqlite3_column_text(pcon_stmt, 2);
if (depends_on) lws_strncpy(pc->depends_on, depends_on, sizeof(pc->depends_on));
- pc->on = (char)sqlite3_column_int(pcon_stmt, 3);
+ pc->on = (unsigned int)sqlite3_column_int(pcon_stmt, 3);
+ pc->manual_on = (unsigned int)sqlite3_column_int(pcon_stmt, 4);
lws_dll2_add_tail(&pc->list, &pmb.power_controllers);
}
@@ -350,7 +351,8 @@ sais_list_builders(struct vhd *vhd)
if ((!strncmp(sp->name, ps->host, host_len) &&
sp->name[host_len] == '.') || (pl > host_len &&
- !strncmp(sp->name + (pl - host_len), ps->host, host_len)))
+ !strncmp(sp->name + (pl - host_len), ps->host, host_len)) ||
+ (sp->pcon && !strcmp(sp->pcon, ps->host)))
{
lwsl_notice("%s: %s vs %s, sp->online %d, pup %d, pdwn %d\n", __func__, sp->name, ps->host, sp->online, ps->powering_up, ps->powering_down);
/*