diff --git a/assets/sai.css b/assets/sai.css
index bdbf454..564f857 100644
--- a/assets/sai.css
+++ b/assets/sai.css
@@ -1700,3 +1700,39 @@ div.ibuil.vm-builder {
background-color: var(--term-cursor-bg);
color: var(--term-cursor-fg);
}
+
+/* Color the avatar silhouette based on grant level */
+.login-status-container.grant-admin img,
+.login-status-container.grant-admin svg,
+.login-status-container.grant-admin .avatar {
+ filter: drop-shadow(0 0 2px rgba(46, 204, 64, 0.4));
+ border-color: #2ecc40 !important;
+}
+
+.login-status-container.grant-admin svg path {
+ fill: #2ecc40 !important;
+}
+
+.login-status-container.grant-user img,
+.login-status-container.grant-user svg,
+.login-status-container.grant-user .avatar {
+ filter: drop-shadow(0 0 2px rgba(243, 156, 18, 0.4));
+ border-color: #f39c12 !important;
+}
+
+.login-status-container.grant-user svg path {
+ fill: #f39c12 !important;
+}
+
+.login-status-container.grant-none img,
+.login-status-container.grant-none svg,
+.login-status-container.grant-none .avatar {
+ filter: drop-shadow(0 0 2px rgba(231, 76, 60, 0.4));
+ border-color: #e74c3c !important;
+ opacity: 0.6;
+}
+
+.login-status-container.grant-none svg path {
+ fill: #e74c3c !important;
+}
+
diff --git a/assets/sai.js b/assets/sai.js
index 1907408..4e3071c 100644
--- a/assets/sai.js
+++ b/assets/sai.js
@@ -394,7 +394,14 @@ var lang_zhs = "{" +
"\"%{pf}前创建, 创作时间: %{ct}ms \"" +
"}}";
-var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, auth_is_admin = 0, auth_grant_level = -1, exptimer, auth_user = "",
+const SaiAuthState = {
+ NOT_LOGGED_IN: 0,
+ LOGGED_IN_NO_GRANT: 1,
+ LOGGED_IN_GRANT_USER: 2, // < :2
+ LOGGED_IN_GRANT_ADMIN: 3 // >= :2
+};
+
+var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, auth_is_admin = 0, auth_grant_level = -1, auth_state = SaiAuthState.NOT_LOGGED_IN, exptimer, auth_user = "",
active_terminals = {};
logAnsiState = {}, logs_pending = "", lines_pending = "", times_pending = "",
ongoing_task_activities = {}, last_log_timestamp = 0, spreadsheet_data_cache = {}, loadreport_data_cache = {},
@@ -1079,10 +1086,10 @@ function sai_taskinfo_render(t, now_ut)
sai_event_render(t, now_ut, 0) + "</table></td><td class=\"ti\">" +
"<span class=\"ti1\">" + sai_plat_icon(t.t.platform, 2) +
san(t.t.platform) + "</span> ";
- if (auth_is_admin && t.t.state != 0 && t.t.state != 3 && t.t.state != 4 && t.t.state != 5)
+ if (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN && t.t.state != 0 && t.t.state != 3 && t.t.state != 4 && t.t.state != 5)
s += "<img class=\"rebuild\" alt=\"stop build\" src=\"stop.svg\" " +
"id=\"stop-" + san(t.t.uuid) + "\"> ";
- if (auth_is_admin)
+ if (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN)
s += "<img class=\"rebuild\" alt=\"rebuild\" src=\"rebuild.png\" " +
"id=\"rebuild-" + san(t.t.uuid) + "\"> ";
s += sai_stateful_taskname(t.t.state, t.t.taskname, 1);
@@ -1308,7 +1315,7 @@ function sai_event_summary_render(o, now_ut, reset_all_icon)
s += "<div class=\"" + cl + "\"><img src=\"/sai/failed.svg\"></div>";
s += "</a>";
- if (reset_all_icon && !gitohashi_integ && auth_is_admin) {
+ if (reset_all_icon && !gitohashi_integ && auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN) {
s += "<br><img class=\"rebuild\" alt=\"rebuild all\" src=\"/sai/rebuild.png\" " +
"id=\"rebuild-ev-" + san(e.uuid) + "\"> ";
s += "<img class=\"rebuild\" alt=\"delete event\" src=\"/sai/delete.png\" " +
@@ -1625,7 +1632,7 @@ function createBuilderDiv(plat) {
platDiv.className += " powering-down";
platDiv.id = "binfo-" + plat.name;
- platDiv.title = plat.platform + "@" + plat.name.split('.')[0] + (authd && auth_is_admin && plat.peer_ip ? " / " + plat.peer_ip : "");
+ platDiv.title = plat.platform + "@" + plat.name.split('.')[0] + (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN && plat.peer_ip ? " / " + plat.peer_ip : "");
let plat_parts = plat.platform.split('/');
let plat_os = plat_parts[0] || 'generic';
@@ -1646,7 +1653,7 @@ 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>`;
- if (authd && auth_is_admin && plat.peer_ip)
+ if (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN && plat.peer_ip)
innerHTML += `<div class="plat-peer-ip">${hsanitize(plat.peer_ip)}</div>`;
innerHTML += `</td></tr></tbody></table>`;
@@ -1666,7 +1673,7 @@ function createBuilderDiv(plat) {
{ label: `<b>LWS:</b> ${plat.lws_hash}` },
];
- if (authd && auth_is_admin && !plat.online) {
+ if (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN && !plat.online) {
menuItems.push({
label: "<span class='builder-delete-btn'>Delete Builder</span>",
callback: () => {
@@ -1681,7 +1688,7 @@ function createBuilderDiv(plat) {
});
}
- if (authd && auth_is_admin) {
+ if (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN) {
menuItems.push({
label: "<span class='builder-shell-btn'>Open Shell</span>",
callback: () => {
@@ -1895,7 +1902,7 @@ function createPconDiv(pcon) {
{ label: `<b>PCON:</b> ${pcon.name}` }
];
- if (authd) {
+ if (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN) {
if (isActuallyOn) {
menuItems.push({
label: "Turn Off",
@@ -3268,11 +3275,33 @@ window.addEventListener("load", function() {
.then(function(res) { return res.json(); })
.then(function(data) {
console.log("LOGIN STATUS DEBUG:", data);
- if (data.logged_in && data.has_grant) {
- authd = 1;
- auth_grant_level = data.grant_level !== undefined ? data.grant_level : -1;
- if (data.is_admin || auth_grant_level >= 2)
- auth_is_admin = 1;
+ auth_state = SaiAuthState.NOT_LOGGED_IN;
+ if (data.logged_in) {
+ if (data.has_grant) {
+ authd = 1;
+ auth_grant_level = data.grant_level !== undefined ? data.grant_level : -1;
+ const isAdmin = data.is_admin === true || data.is_admin === 1 || data.is_admin === "true" || data.is_admin === "1";
+ if (auth_grant_level >= 2 || (auth_grant_level === -1 && isAdmin)) {
+ auth_state = SaiAuthState.LOGGED_IN_GRANT_ADMIN;
+ auth_is_admin = 1;
+ } else {
+ auth_state = SaiAuthState.LOGGED_IN_GRANT_USER;
+ }
+ } else {
+ auth_state = SaiAuthState.LOGGED_IN_NO_GRANT;
+ }
+
+ const container = document.getElementById('lws-login-status-container');
+ if (container) {
+ container.classList.remove('grant-admin', 'grant-user', 'grant-none');
+ if (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN) {
+ container.classList.add('grant-admin');
+ } else if (auth_state === SaiAuthState.LOGGED_IN_GRANT_USER) {
+ container.classList.add('grant-user');
+ } else if (auth_state === SaiAuthState.LOGGED_IN_NO_GRANT) {
+ container.classList.add('grant-none');
+ }
+ }
}
})
.catch(function(err) {
@@ -3310,7 +3339,7 @@ window.addEventListener("load", function() {
target = target.parentElement;
}
- if (taskDiv && auth_is_admin) {
+ if (taskDiv && auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN) {
event.preventDefault();
const taskUuid = taskDiv.id.substring(10);
diff --git a/src/web/w-comms.c b/src/web/w-comms.c
index 7eb11d2..82fab82 100644
--- a/src/web/w-comms.c
+++ b/src/web/w-comms.c
@@ -505,16 +505,24 @@ http_resp:
}
#endif
- pss->authorized = 0;
+ pss->auth_state = SAI_AUTH_STATE_NOT_LOGGED_IN;
if (vhd->has_jwk) {
#if defined(LWS_WITH_JOSE)
const char *reason = "unknown";
struct lws_jwt_auth *ja = lws_jwt_auth_create(wsi, &vhd->jwk, vhd->cookie_name, NULL, NULL, &reason);
if (ja) {
- if (lws_jwt_auth_query_grant(ja, "*") >= 1 || lws_jwt_auth_query_grant(ja, "com.warmcat.sai") >= 1) {
- pss->authorized = 1;
+ int grant = (int)lws_jwt_auth_query_grant(ja, "com.warmcat.sai");
+ int grant_all = (int)lws_jwt_auth_query_grant(ja, "*");
+ int max_grant = grant > grant_all ? grant : grant_all;
+
+ if (max_grant >= 2) {
+ pss->auth_state = SAI_AUTH_STATE_LOGGED_IN_GRANT_ADMIN;
lwsl_wsi_notice(wsi, "Authorized WebSocket connection (admin/grant)");
+ } else if (max_grant >= 1) {
+ pss->auth_state = SAI_AUTH_STATE_LOGGED_IN_GRANT_USER;
+ lwsl_wsi_notice(wsi, "Authorized WebSocket connection (user/grant)");
} else {
+ pss->auth_state = SAI_AUTH_STATE_LOGGED_IN_NO_GRANT;
lwsl_wsi_err(wsi, "JWT validation passed, but no grant found");
}
lws_jwt_auth_destroy(&ja);
diff --git a/src/web/w-private.h b/src/web/w-private.h
index 2e9e057..b9e9ecf 100644
--- a/src/web/w-private.h
+++ b/src/web/w-private.h
@@ -59,6 +59,14 @@ enum {
SAIM_SPECIFIC_TASK,
};
+typedef enum {
+ SAI_AUTH_STATE_NOT_LOGGED_IN,
+ SAI_AUTH_STATE_LOGGED_IN_NO_GRANT,
+ SAI_AUTH_STATE_LOGGED_IN_GRANT_USER, /* < :2 */
+ SAI_AUTH_STATE_LOGGED_IN_GRANT_ADMIN /* >= :2 */
+} sai_auth_state_t;
+
+
struct pss {
struct vhd *vhd;
struct lws *wsi;
@@ -124,7 +132,7 @@ struct pss {
unsigned int announced:1;
unsigned int bulk_binary_data:1;
unsigned int toggle_favour_sch:1;
- unsigned int authorized:1;
+ sai_auth_state_t auth_state;
};
struct vhd {
diff --git a/src/web/w-ws-browser.c b/src/web/w-ws-browser.c
index 295ed0b..fdc352b 100644
--- a/src/web/w-ws-browser.c
+++ b/src/web/w-ws-browser.c
@@ -645,7 +645,7 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf,
* matched on
*/
- if (!pss->authorized && (
+ if (pss->auth_state != SAI_AUTH_STATE_LOGGED_IN_GRANT_ADMIN && (
a.top_schema_index == SAIM_WS_BROWSER_RX_TASKRESET ||
a.top_schema_index == SAIM_WS_BROWSER_RX_TASKREMOVEALLTRIES ||
a.top_schema_index == SAIM_WS_BROWSER_RX_TASKREBUILDLASTSTEP ||
@@ -654,6 +654,8 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf,
a.top_schema_index == SAIM_WS_BROWSER_RX_TASKCANCEL ||
a.top_schema_index == SAIM_WS_BROWSER_RX_REBUILD ||
a.top_schema_index == SAIM_WS_BROWSER_RX_PLATRESET ||
+ a.top_schema_index == SAIM_WS_BROWSER_RX_STAY ||
+ a.top_schema_index == SAIM_WS_BROWSER_RX_PCON_CONTROL ||
a.top_schema_index == SAIM_WS_BROWSER_RX_BUILDERDELETE ||
a.top_schema_index == SAIM_WS_BROWSER_RX_OPENSHELL ||
a.top_schema_index == SAIM_WS_BROWSER_RX_CLOSESHELL ||