diff --git a/assets/sai.css b/assets/sai.css
index 03cccc9..a3f9fd2 100644
--- a/assets/sai.css
+++ b/assets/sai.css
@@ -777,7 +777,7 @@ div.bdr {
img.branch {
display: inline-block;
float:left;
- background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyNi43NDkgMjcuMjIyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImIiIHg9Ii0uMDcwOCIgeT0iLS4wNzA4IiB3aWR0aD0iMS4xNDE2IiBoZWlnaHQ9IjEuMTQxNiIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMC4yNTQyNzk1MyIvPgo8L2ZpbHRlcj4KPGZpbHRlciBpZD0iYSIgeD0iLS4wOTA1NjgiIHk9Ii0uMDQ2MjE0IiB3aWR0aD0iMS4xODExIiBoZWlnaHQ9IjEuMDkyNCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMC40Nzk4NDA4OSIvPgo8L2ZpbHRlcj4KPC9kZWZzPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNzAuNzQ3IC02OS40NTQpIj4KPGNpcmNsZSBjeD0iODQuMTIxIiBjeT0iODMuMDIyIiByPSIxMy4zNzQiIGZpbGw9Im5vbmUiLz4KPHBhdGggZD0ibTgzLjc3MyA3MC42MDZjLTEuMTg2MiAwLjAxNTQtMi4zNDE4IDAuODQ2ODgtMi40Nzc0IDEuOTk4OGwtMC4wMTI0IDAuMDQ5MWMtMC4xNDk5IDUuNjExMi0wLjA0NzQgMTEuMjUyLTAuMTExNjIgMTYuODc0IDAuMDU4MSAxLjg3NzctMC40NDMzNyAzLjg0NDEgMC44NjgxNyA1LjIwMTIgMS43NDYyIDEuNzQ0NSA0LjIzMTcgMC40OTM5MyA0LjE0MjQtMi41MTU2IDAuMDQ1NS0wLjkyODQyIDAuMDExNy0xLjQwMiAwLjExMDA3LTMuMjM0OSAxLjUwNTIgMS4xMzQ4IDIuOTYwNCAyLjk5NjYgNC44MTk5IDMuNTIzOCAyLjAzNzEgMC4zMTUyOCAzLjQ3NDgtMi4yMDU1IDIuMzEyLTMuODU1MS0yLjI5OTItMi4yMDI1LTQuODIwMi00LjA1Ny03LjE2Ni02LjIyODYgMC4xMzg5LTMuMyAwLjAxNzYtNi4xNDIgMC4wNDY1LTkuNjA2Ny0wLjQxNDMtMS41OTgtMS40ODQ5LTIuMjE5Ni0yLjUzMTYtMi4yMDYxem0wLjE4NDQ5IDExLjg4M2EyLjA3ODkgMi4wNzg5IDAgMCAxIDIuMDc4OSAyLjA3ODkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODkgMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODQtMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMSAyLjA3ODQtMi4wNzg5eiIgZmlsdGVyPSJ1cmwoI2EpIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iLjI2NDU4cHgiLz4KPHBhdGggZD0ibTgzLjUxOCA3MC4yODNjLTEuMTg2MiAwLjAxNTQtMi4zNDE4IDAuODQ2ODgtMi40Nzc0IDEuOTk4OGwtMC4wMTI0IDAuMDQ5MWMtMC4xNDk5IDUuNjExMi0wLjA0NzQgMTEuMjUyLTAuMTExNjIgMTYuODc0IDAuMDU4MSAxLjg3NzctMC40NDMzNyAzLjg0NDEgMC44NjgxNyA1LjIwMTIgMS43NDYyIDEuNzQ0NSA0LjIzMTcgMC40OTM5MyA0LjE0MjQtMi41MTU2IDAuMDQ1NS0wLjkyODQyIDAuMDExNy0xLjQwMiAwLjExMDA3LTMuMjM0OSAxLjUwNTIgMS4xMzQ4IDIuOTYwNCAyLjk5NjYgNC44MTk5IDMuNTIzOCAyLjAzNzEgMC4zMTUyOCAzLjQ3NDgtMi4yMDU1IDIuMzEyLTMuODU1MS0yLjI5OTItMi4yMDI1LTQuODIwMi00LjA1Ny03LjE2Ni02LjIyODYgMC4xMzg5LTMuMyAwLjAxNzYtNi4xNDIgMC4wNDY1LTkuNjA2Ny0wLjQxNDMtMS41OTgtMS40ODQ5LTIuMjE5Ni0yLjUzMTYtMi4yMDYxem0wLjE4NDQ5IDExLjg4M2EyLjA3ODkgMi4wNzg5IDAgMCAxIDIuMDc4OSAyLjA3ODkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODkgMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODQtMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMSAyLjA3ODQtMi4wNzg5eiIgZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9Ii4yNjQ1OHB4Ii8+CjxwYXRoIGQ9Im04My43MTYgODAuMThhNC4zMDk3IDQuMzA5NyAwIDAgMC00LjMwOTggNC4zMDk4IDQuMzA5NyA0LjMwOTcgMCAwIDAgNC4zMDk4IDQuMzA5OCA0LjMwOTcgNC4zMDk3IDAgMCAwIDQuMzA5OC00LjMwOTggNC4zMDk3IDQuMzA5NyAwIDAgMC00LjMwOTgtNC4zMDk4em0wLjAzMzYgMi4xMDQ4YTIuMTcxNiAyLjE3MTYgMCAwIDEgMi4xNzE1IDIuMTcxNSAyLjE3MTYgMi4xNzE2IDAgMCAxLTIuMTcxNSAyLjE3MTQgMi4xNzE2IDIuMTcxNiAwIDAgMS0yLjE3MTQtMi4xNzE0IDIuMTcxNiAyLjE3MTYgMCAwIDEgMi4xNzE0LTIuMTcxNXoiIGZpbGw9IiNmZmYiIGZpbHRlcj0idXJsKCNiKSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9Ii41Ii8+CjxwYXRoIGQ9Im04My42NjkgODAuMDYyYTQuMzA5NyA0LjMwOTcgMCAwIDAtNC4zMDk4IDQuMzA5OCA0LjMwOTcgNC4zMDk3IDAgMCAwIDQuMzA5OCA0LjMwOTggNC4zMDk3IDQuMzA5NyAwIDAgMCA0LjMwOTgtNC4zMDk4IDQuMzA5NyA0LjMwOTcgMCAwIDAtNC4zMDk4LTQuMzA5OHptMC4wMzM2IDIuMTA0OGEyLjE3MTYgMi4xNzE2IDAgMCAxIDIuMTcxNSAyLjE3MTQgMi4xNzE2IDIuMTcxNiAwIDAgMS0yLjE3MTUgMi4xNzE0IDIuMTcxNiAyLjE3MTYgMCAwIDEtMi4xNzE0LTIuMTcxNCAyLjE3MTYgMi4xNzE2IDAgMCAxIDIuMTcxNC0yLjE3MTR6IiBmaWxsPSIjZmZmIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iLjYiLz4KPC9nPgo8L3N2Zz4K");
+ background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyNi43NDkgMjcuMjIyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImIiIHg9Ii0uMDcwOCIgeT0iLS4wNzA4IiB3aWR0aD0iMS4xNDE2IiBoZWlnaHQ9IjEuMTQxNiIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMC4yNTQyNzk1MyIvPgo8L2ZpbHRlcj4KPGZpbHRlciBpZD0iYSIgeD0iLS4wOTA1NjgiIHk9Ii0uMDQ2MjE0IiB3aWR0aD0iMS4xODExIiBoZWlnaHQ9IjEuMDkyNCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMC40Nzk4NDA4OSIvPgo8L2ZpbHRlcj4KPC9kZWZzPgo8bWV0YWRhdGE+CjxyZGY6UkRGPgo8Y2M6V29yayByZGY6YWJvdXQ9IiI+CjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pgo8ZGM6dHlwZSByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIi8+CjxkYzp0aXRsZS8+CjwvY2M6V29yaz4KPC9yZGY6UkRGPgo8L21ldGFkYXRhPgo8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNzAuNzQ3IC02OS40NTQpIj4KPGNpcmNsZSBjeD0iODQuMTIxIiBjeT0iODMuMDIyIiByPSIxMy4zNzQiIGZpbGw9Im5vbmUiLz4KPHBhdGggZD0ibTgzLjc3MyA3MC42MDZjLTEuMTg2MiAwLjAxNTQtMi4zNDE4IDAuODQ2ODgtMi40Nzc0IDEuOTk4OGwtMC4wMTI0IDAuMDQ5MWMtMC4xNDk5IDUuNjExMi0wLjA0NzQgMTEuMjUyLTAuMTExNjIgMTYuODc0IDAuMDU4MSAxLjg3NzctMC40NDMzNyAzLjg0NDEgMC44NjgxNyA1LjIwMTIgMS43NDYyIDEuNzQ0NSA0LjIzMTcgMC40OTM5MyA0LjE0MjQtMi41MTU2IDAuMDQ1NS0wLjkyODQyIDAuMDExNy0xLjQwMiAwLjExMDA3LTMuMjM0OSAxLjUwNTIgMS4xMzQ4IDIuOTYwNCAyLjk5NjYgNC44MTk5IDMuNTIzOCAyLjAzNzEgMC4zMTUyOCAzLjQ3NDgtMi4yMDU1IDIuMzEyLTMuODU1MS0yLjI5OTItMi4yMDI1LTQuODIwMi00LjA1Ny03LjE2Ni02LjIyODYgMC4xMzg5MyA0LjMwOTcgMC4wMTc2LTYuMTQyIDAuMDQ2NS05LjYwNjctMC40MTQzLTEuNTk4LTEuNDg0OS0yLjIxOTYtMi41MzE2LTIuMjA2MXem0wLjE4NDQ5IDExLjg4M2EyLjA3ODkgMi4wNzg5IDAgMCAxIDIuMDc4OSAyLjA3ODkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODkgMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODQtMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMSAyLjA3ODQtMi4wNzg5eiIgZmlsdGVyPSJ1cmwoI2EpIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iLjI2NDU4cHgiLz4KPHBhdGggZD0ibTgzLjUxOCA3MC4yODNjLTEuMTg2MiAwLjAxNTQtMi4zNDE4IDAuODQ2ODgtMi40Nzc0IDEuOTk4OGwtMC4wMTI0IDAuMDQ5MWMtMC4xNDk5IDUuNjExMi0wLjA0NzQgMTEuMjUyLTAuMTExNjIgMTYuODc0IDAuMDU4MSAxLjg3NzctMC40NDMzNyAzLjg0NDEgMC44NjgxNyA1LjIwMTIgMS43NDYyIDEuNzQ0NSA0LjIzMTcgMC40OTM5MyA0LjE0MjQtMi41MTU2IDAuMDQ1NS0wLjkyODQyIDAuMDExNy0xLjQwMiAwLjExMDA3LTMuMjM0OSAxLjUwNTIgMS4xMzQ4IDIuOTYwNCAyLjk5NjYgNC44MTk5IDMuNTIzOCAyLjAzNzEgMC4zMTUyOCAzLjQ3NDgtMi4yMDU1IDIuMzEyLTMuODU1MS0yLjI5OTItMi4yMDI1LTQuODIwMi00LjA1Ny03LjE2Ni02LjIyODYgMC4xMzg5MyA0LjMwOTcgMC4wMTc2LTYuMTQyIDAuMDQ2NS05LjYwNjctMC40MTQzLTEuNTk4LTEuNDg0OS0yLjIxOTYtMi41MzE2LTIuMjA2MXem0wLjE4NDQ5IDExLjg4M2EyLjA3ODkgMi4wNzg5IDAgMCAxIDIuMDc4OSAyLjA3ODkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODkgMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMS0yLjA3ODQtMi4wNzkgMi4wNzg5IDIuMDc4OSAwIDAgMSAyLjA3ODQtMi4wNzg5eiIgZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9Ii4yNjQ1OHB4Ii8+CjxwYXRoIGQ9Im04My43MTYgODAuMThhNC4zMDk3IDQuMzA5NyAwIDAgMC00LjMwOTggNC4zMDk4IDQuMzA5NyA0LjMwOTcgMCAwIDAgNC4zMDk4IDQuMzA5OCA0LjMwOTcgNC4zMDk3IDAgMCAwIDQuMzA5OC00LjMwOTggNC4zMDk3IDQuMzA5NyAwIDAgMC00LjMwOTgtNC4zMDk4em0wLjAzMzYgMi4xMDQ4YTIuMTcxNiAyLjE3MTYgMCAwIDEgMi4xNzE1IDIuMTcxNSAyLjE3MTYgMi4xNzE2IDAgMCAxLTIuMTcxNSAyLjE3MTQgMi4xNzE2IDIuMTcxNiAwIDAgMS0yLjE3MTQtMi4xNzE0IDIuMTcxNiAyLjE3MTYgMCAwIDEgMi4xNzE0LTIuMTcxNXoiIGZpbGw9IiNmZmYiIGZpbHRlcj0idXJsKCNiKSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9Ii41Ii8+CjxwYXRoIGQ9Im04My42NjkgODAuMDYyYTQuMzA5NyA0LjMwOTcgMCAwIDAtNC4zMDk4IDQuMzA5OCA0LjMwOTcgNC4zMDk3IDAgMCAwIDQuMzA5OCA0LjMwOTggNC4zMDk3IDQuMzA5NyAwIDAgMCA0LjMwOTgtNC4zMDk4IDQuMzA5NyA0LjMwOTcgMCAwIDAtNC4zMDk4LTQuMzA5OHptMC4wMzM2IDIuMTA0OGEyLjE3MTYgMi4xNzE2IDAgMCAxIDIuMTcxNSAyLjE3MTQgMi4xNzE2IDIuMTcxNiAwIDAgMS0yLjE3MTUgMi4xNzE0IDIuMTcxNiAyLjE3MTYgMCAwIDEtMi4xNzE0LTIuMTcxNCAyLjE3MTYgMi4xNzE2IDAgMCAxIDIuMTcxNC0yLjE3MTR6IiBmaWxsPSIjZmZmIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iLjYiLz4KPC9nPgo8L3N2Zz4K");
width:0px;
height:0px;
cursor:pointer;
@@ -1078,3 +1078,47 @@ body.overlay-active {
width: 24px;
margin-bottom: 1px;
}
+
+/* PCON Styles */
+
+.pcon {
+ margin-bottom: 5px;
+ padding-left: 5px;
+}
+
+.pcon-header {
+ font-weight: bold;
+ font-size: 1.0em;
+ padding: 4px;
+ background-color: #e0e0e0;
+ border-radius: 3px;
+ display: flex;
+ align-items: center;
+}
+
+.pcon-on {
+ color: #2ecc40; /* Green */
+ margin-right: 5px;
+}
+
+.pcon-off {
+ color: #aaaaaa; /* Grey */
+ margin-right: 5px;
+}
+
+.pcon-type {
+ font-weight: normal;
+ font-size: 0.8em;
+ color: #666;
+ margin-left: 5px;
+}
+
+.pcon-children {
+ padding-left: 10px;
+ border-left: 2px solid #ddd;
+ margin-top: 2px;
+}
+
+.pcon-orphans .pcon-header {
+ background-color: #ffe0e0;
+}
diff --git a/assets/sai.js b/assets/sai.js
index 14f6457..1741428 100644
--- a/assets/sai.js
+++ b/assets/sai.js
@@ -22,7 +22,7 @@ const SAI_JS_API_VERSION = 3;
}
Translator.prototype.translate = function(text, defaultNumOrFormatting,
- numOrFormattingOrContext, formattingOrContext, context) {
+ numOrFormattingOrContext, formattingOrContext, context) {
var defaultText, formatting, isObject, num;
if (context == null) {
@@ -75,10 +75,10 @@ const SAI_JS_API_VERSION = 3;
_ref = d.values;
var k;
for (k in _ref) {
- if ({}.hasOwnProperty.call(_ref, k)) {
+ if ({}.hasOwnProperty.call(_ref, k)) {
v = _ref[k];
this.data.values[k] = v;
- }
+ }
}
}
if ((d.contexts != null)) {
@@ -123,18 +123,18 @@ const SAI_JS_API_VERSION = 3;
var k, v;
for (k in hash) {
- if ({}.hasOwnProperty.call(hash, k)) {
+ if ({}.hasOwnProperty.call(hash, k)) {
v = hash[k];
if (typeof v === "string") {
hash[k] = this.translateText(v, null, null, context);
}
- }
+ }
}
return hash;
};
Translator.prototype.translateText = function(text, num, formatting,
- context, defaultText) {
+ context, defaultText) {
var contextData, result;
if (context == null) {
@@ -146,11 +146,11 @@ const SAI_JS_API_VERSION = 3;
contextData = this.getContextData(this.data, context);
if (contextData != null) {
result = this.findTranslation(text, num, formatting, contextData.values,
- defaultText);
+ defaultText);
}
if (result == null) {
result = this.findTranslation(text, num, formatting, this.data.values,
- defaultText);
+ defaultText);
}
if (result == null) {
return this.useOriginalText(defaultText || text, num, formatting);
@@ -176,9 +176,9 @@ const SAI_JS_API_VERSION = 3;
if ((num >= triple[0] || triple[0] === null) &&
(num <= triple[1] || triple[1] === null)) {
result = this.applyFormatting(triple[2].replace("-%n",
- String(-num)), num, formatting);
+ String(-num)), num, formatting);
return this.applyFormatting(result.replace("%n",
- String(num)), num, formatting);
+ String(num)), num, formatting);
}
}
}
@@ -198,10 +198,10 @@ const SAI_JS_API_VERSION = 3;
equal = true;
_ref1 = c.matches;
for (key in _ref1) {
- if ({}.hasOwnProperty.call(_ref1, key)) {
- value = _ref1[key];
- equal = equal && value === context[key];
- }
+ if ({}.hasOwnProperty.call(_ref1, key)) {
+ value = _ref1[key];
+ equal = equal && value === context[key];
+ }
}
if (equal) {
return c;
@@ -215,17 +215,17 @@ const SAI_JS_API_VERSION = 3;
return this.applyFormatting(text, num, formatting);
}
return this.applyFormatting(text.replace("%n", String(num)),
- num, formatting);
+ num, formatting);
};
Translator.prototype.applyFormatting = function(text, num, formatting) {
var ind, regex;
for (ind in formatting) {
- if ({}.hasOwnProperty.call(formatting, ind)) {
+ if ({}.hasOwnProperty.call(formatting, ind)) {
regex = new RegExp("%{" + ind + "}", "g");
text = text.replace(regex, formatting[ind]);
- }
+ }
}
return text;
};
@@ -251,8 +251,8 @@ const SAI_JS_API_VERSION = 3;
return trans.translate;
};
- (typeof module !== "undefined" && module !== null ? module.exports = i18n : void 0) ||
- (this.i18n = i18n);
+ (typeof module !== "undefined" && module !== null ? module.exports = i18n : void 0) ||
+ (this.i18n = i18n);
}.call(this));
@@ -391,7 +391,7 @@ var lang_zhs = "{" +
"\"页面%{pf}前获取, 创作时间: %{ct}ms " +
"(vhost etag 缓存命中: %{ve}%, 缓存命中: %{ch}%)\"," +
"\"Created %{pf} ago, creation time: %{ct}ms \":" +
- "\"%{pf}前创建, 创作时间: %{ct}ms \"" +
+ "\"%{pf}前创建, 创作时间: %{ct}ms \"" +
"}}";
var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, exptimer, auth_user = "",
@@ -399,6 +399,118 @@ var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, exptimer, auth_user
ongoing_task_activities = {}, last_log_timestamp = 0, spreadsheet_data_cache = {}, loadreport_data_cache = {},
fadingTasks = new Map();
+/* Global caches for reconcilation */
+var pcon_topology = {};
+var last_builder_list = [];
+
+function createPconDiv(pcon) {
+ const pconDiv = document.createElement("div");
+ pconDiv.className = "pcon";
+ pconDiv.id = "pcon-" + pcon.name;
+ pconDiv.style.marginLeft = "10px";
+ pconDiv.style.borderLeft = "1px solid #ccc";
+ pconDiv.style.paddingLeft = "5px";
+
+ const header = document.createElement("div");
+ header.className = "pcon-header";
+
+ let stateClass = pcon.on ? "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>`;
+ pconDiv.appendChild(header);
+
+ const childrenDiv = document.createElement("div");
+ childrenDiv.className = "pcon-children";
+ pconDiv.appendChild(childrenDiv);
+
+ return pconDiv;
+}
+
+function renderPconHierarchy(container) {
+ if (!container) return;
+
+ /* Clear and redraw for now to ensure structure is correct */
+ container.innerHTML = "";
+
+ const pcons = Object.values(pcon_topology);
+ /* Build map for dependency resolution */
+ const pconMap = {};
+ pcons.forEach(p => {
+ p.children = []; /* Reset children */
+ pconMap[p.name] = p;
+ });
+
+ /* Link PCONs */
+ const roots = [];
+ pcons.forEach(p => {
+ if (p.depends_on && pconMap[p.depends_on]) {
+ pconMap[p.depends_on].children.push(p);
+ } else {
+ roots.push(p);
+ }
+ });
+
+ /* Sort roots and children by name */
+ const sortByName = (a, b) => a.name.localeCompare(b.name);
+ roots.sort(sortByName);
+ pcons.forEach(p => p.children.sort(sortByName));
+
+ /* Helper to recursively render PCONs and their builders */
+ function renderPcon(pcon, parentDiv) {
+ const div = createPconDiv(pcon);
+ parentDiv.appendChild(div);
+ const childrenContainer = div.querySelector(".pcon-children");
+
+ /* Render builders belonging to this PCON */
+ /* We search the global builder list for those matching this pcon */
+ const myBuilders = last_builder_list.filter(b => b.pcon === pcon.name);
+ myBuilders.sort((a, b) => a.name.localeCompare(b.name));
+
+ if (myBuilders.length > 0) {
+ const table = document.createElement("table");
+ table.className = "builders";
+ const tbody = document.createElement("tbody");
+ table.appendChild(tbody);
+ myBuilders.forEach(b => {
+ tbody.appendChild(createBuilderRow(b));
+ });
+ childrenContainer.appendChild(table);
+ }
+
+ /* Render child PCONs */
+ pcon.children.forEach(child => {
+ renderPcon(child, childrenContainer);
+ });
+ }
+
+ roots.forEach(root => {
+ renderPcon(root, container);
+ });
+
+ /* Render orphan builders (no pcon or unknown pcon) */
+ const orphanBuilders = last_builder_list.filter(b => !b.pcon || !pcon_topology[b.pcon]);
+ if (orphanBuilders.length > 0) {
+ const orphanDiv = document.createElement("div");
+ orphanDiv.className = "pcon-orphans";
+ orphanDiv.innerHTML = "<div class='pcon-header'><b>Unmanaged Builders</b></div>";
+ const childrenContainer = document.createElement("div");
+ childrenContainer.className = "pcon-children";
+ orphanDiv.appendChild(childrenContainer);
+
+ const table = document.createElement("table");
+ table.className = "builders";
+ const tbody = document.createElement("tbody");
+ table.appendChild(tbody);
+ orphanBuilders.forEach(b => {
+ tbody.appendChild(createBuilderRow(b));
+ });
+ childrenContainer.appendChild(table);
+
+ container.appendChild(orphanDiv);
+ }
+}
+
function update_task_activities() {
for (const uuid in ongoing_task_activities) {
const el = document.getElementById("taskstate_" + uuid);
@@ -411,33 +523,33 @@ function update_task_activities() {
}
}
}
-
+
function expiry()
{
location.reload();
}
-
+
function san(s)
{
if (s.search("<") !== -1)
return "invalid string";
-
+
return s;
}
function humanize(s)
{
var i = parseInt(s, 10);
-
+
if (i >= (1024 * 1024 * 1024))
return (i / (1024 * 1024 * 1024)).toFixed(3) + "Gi";
-
+
if (i >= (1024 * 1024))
return (i / (1024 * 1024)).toFixed(3) + "Mi";
-
+
if (i > 1024)
return (i / 1024).toFixed(3) + "Ki";
-
+
return s;
}
@@ -503,7 +615,7 @@ function hsanitize(s)
'\'': 'apos',
'&': 'amp'
};
-
+
return s.toString().replace(/[<>"'&]/g, function(chr) {
return '&' + table[chr] + ';';
}).replace(/\r\n/g, '\n').replace(/\n/g, '<br>');
@@ -647,13 +759,13 @@ var age_upd = [ 5, 10, 300, 1800, 3600, 12 * 3600, 12 * 3600 ];
function agify(now, secs)
{
var d = now - secs, n;
-
+
if (!secs)
return "";
-
+
if (secs > now)
d = secs - now;
-
+
for (n = 0; n < age_names.length; n++)
if (d < age_limit[n] || age_limit[n] === 0)
return "<span class='age-" + n + "' ut='" + secs +
@@ -706,17 +818,17 @@ var sai, jso, s, sai_arts = "";
function sai_plat_icon(plat, size)
{
var s, s1 = "";
-
+
s = plat.split('/');
if (s[0]) {
// console.log("plat " + plat + " plat[0] " + s[0]);
s1 = "<img class=\"ip" + size + " zup\" src=\"/sai/" + san(s[0]) +
".svg\">";
-
+
if (s[1])
s1 += "<img class=\"ip" + size + " tread1\" src=\"/sai/arch-" + san(s[1]) + ".svg\">";
}
-
+
if (s[2]) {
s1 += "<img class=\"ip" + size + " tread2\" src=\"/sai/tc-" + san(s[2]) + ".svg\">";
}
@@ -730,18 +842,18 @@ function sai_stateful_taskname(state, nm, sf)
if (sf)
return "<span id=\"taskstate\" class=\"ti2 taskstate" +
state + "\"> " + san(nm) + " </span>";
-
+
if (state == 4 || state == 6)
tp = " ov_bad";
-
- return "<span id=\"taskstate\" class=\"ti2 " + tp + "\">" + san(nm) + "</span>";
+
+ return "<span id=\"taskstate\" class=\"ti2 " + tp + "\">" + san(nm) + "</span>";
}
function sai_taskinfo_render(t, now_ut)
{
var now_ut = Math.round((new Date().getTime() / 1000));
var s = "";
-
+
s = "<table><tr class=\"nomar\"><td class=\"atop\"><table>" +
sai_event_render(t, now_ut, 0) + "</table></td><td class=\"ti\">" +
"<span class=\"ti1\">" + sai_plat_icon(t.t.platform, 2) +
@@ -753,7 +865,7 @@ function sai_taskinfo_render(t, now_ut)
s += "<img class=\"rebuild\" alt=\"rebuild\" src=\"rebuild.png\" " +
"id=\"rebuild-" + san(t.t.uuid) + "\"> " +
sai_stateful_taskname(t.t.state, t.t.taskname, 1);
-
+
if (t.t.builder_name) {
var now_ut = Math.round((new Date().getTime() / 1000));
@@ -764,15 +876,15 @@ function sai_taskinfo_render(t, now_ut)
s += ", <span class=\"ti5\"> " +
agify(now_ut, t.t.started) + " ago, Dur: " +
(t.t.duration ? t.t.duration / 1000000 :
- now_ut - t.t.started).toFixed(1) +
+ now_ut - t.t.started).toFixed(1) +
"s</span><div id=\"sai_arts\"></div><div id=\"metrics-summary-" + san(t.t.uuid) + "\"></div>";
sai_arts = "";
}
s += "</td></tr>";
-
+
s += "</td></tr></table></table>";
-
+
return s;
}
@@ -835,10 +947,10 @@ function summarize_build_situation(event_uuid)
var good = 0, bad = 0, total = 0, ongoing = 0, pending = 0,
roo = document.getElementById("taskcont-" + event_uuid),
same;
-
- if (!roo)
+
+ if (!roo)
return { text: "" };
-
+
same = roo.querySelectorAll(".taskstate");
if (same)
total = same.length;
@@ -918,7 +1030,7 @@ function sai_event_summary_render(o, now_ut, reset_all_icon)
s += "<div class=\"" + cl + "\"><img src=\"/sai/passed.svg\"></div>";
if (e.state == 4)
s += "<div class=\"" + cl + "\"><img src=\"/sai/failed.svg\"></div>";
-
+
s += "</a>";
if (reset_all_icon && !gitohashi_integ && authd) {
s += "<br><img class=\"rebuild\" alt=\"rebuild all\" src=\"/sai/rebuild.png\" " +
@@ -936,7 +1048,7 @@ function sai_event_summary_render(o, now_ut, reset_all_icon)
if (e.sec)
s += " <img class=\"bico\" src=\"/sai/locked.svg\">";
s += "</span></td></tr><tr><td class=\"nomar\" colspan=2><span class=\"e2\">";
-
+
if (e.ref.substr(0, 11) === "refs/heads/") {
s += "<img class=\"branch\">" +
san(e.ref.substr(11));
@@ -946,7 +1058,7 @@ function sai_event_summary_render(o, now_ut, reset_all_icon)
san(e.ref.substr(10));
} else
s += san(e.ref);
-
+
s += "</span></td></tr><tr><td class=\"nomar e6\">" +
san(e.hash.substr(0, 8)) +
"</td><td class=\"e6 nomar\">" +
@@ -959,14 +1071,14 @@ function sai_event_summary_render(o, now_ut, reset_all_icon)
"</table></td>";
}
s += "</tr><tr><td class=\"nomar e6\" colspan=\"2\" id=\"sumbs-" + e.uuid +"\"></td></tr></table>";
-
+
return s;
}
function sai_event_render(o, now_ut, reset_all_icon)
{
var s, q, ctn = "", wai, s1 = "", n, e = o.e;
-
+
s = "<tr><td class=\"waiting\"";
if (gitohashi_integ)
s += " id=\"gitohashi_sai_icon\"";
@@ -981,7 +1093,7 @@ function sai_event_render(o, now_ut, reset_all_icon)
for (q = 0; q < o.t.length; q++) {
var t = o.t[q];
-
+
if (t.taskname !== ctn) {
if (ctn !== "") {
s += "<div class=\"ib\"><table class=\"nomar\">" +
@@ -992,7 +1104,7 @@ function sai_event_render(o, now_ut, reset_all_icon)
}
ctn = t.taskname;
}
-
+
s1 += "<div id=\"taskstate_" + t.uuid + "\" class=\"taskstate taskstate" + t.state +
"\" data-event-uuid=\"" + san(e.uuid) + "\" data-platform=\"" + san(t.platform) +
"\" data-rebuildable=\"" + t.rebuildable + "\">";
@@ -1015,7 +1127,7 @@ function sai_event_render(o, now_ut, reset_all_icon)
}
s += "</tr>";
-
+
return s;
}
@@ -1035,7 +1147,7 @@ function getBuilderGroupKey(platName) {
function refresh_state(task_uuid, task_state)
{
var tsi = document.getElementById("taskstate_" + task_uuid);
-
+
if (tsi) {
tsi.classList.remove("taskstate0");
tsi.classList.remove("taskstate1");
@@ -1236,7 +1348,7 @@ function updateSpreadsheetCell(cell, platName) {
}
if (best_match_key) {
- cell.innerHTML = renderSpreadsheet(spreadsheet_data_cache[best_match_key]);
+ updateSpreadsheetDOM(cell, spreadsheet_data_cache[best_match_key]);
aging();
} else {
cell.innerHTML = ""; // Clear it if no data
@@ -1275,12 +1387,134 @@ function updateBuilderRow(row, plat) {
updateSpreadsheetCell(tdSpreadsheet, plat.name);
}
+/* Global caches for reconcilation */
+var pcon_topology = {};
+var last_builder_list = [];
+
+function createPconDiv(pcon) {
+ const pconDiv = document.createElement("div");
+ pconDiv.className = "pcon";
+ pconDiv.id = "pcon-" + pcon.name;
+ pconDiv.style.marginLeft = "10px";
+ pconDiv.style.borderLeft = "1px solid #ccc";
+ pconDiv.style.paddingLeft = "5px";
+
+ const header = document.createElement("div");
+ header.className = "pcon-header";
+
+ let stateClass = pcon.on ? "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>`;
+ pconDiv.appendChild(header);
+
+ /* Context menu for PCON */
+ const menuItems = [
+ { label: `<b>PCON:</b> ${pcon.name}` }
+ ];
+
+ header.addEventListener("contextmenu", function(event) {
+ if (!authd) return;
+ createContextMenu(event, menuItems);
+ });
+
+ const childrenDiv = document.createElement("div");
+ childrenDiv.className = "pcon-children";
+ pconDiv.appendChild(childrenDiv);
+
+ return pconDiv;
+}
+
+function renderPconHierarchy(container) {
+ if (!container) return;
+
+ /* Clear and redraw for now to ensure structure is correct */
+ container.innerHTML = "";
+
+ const pcons = Object.values(pcon_topology);
+ /* Build map for dependency resolution */
+ const pconMap = {};
+ pcons.forEach(p => {
+ p.children = []; /* Reset children */
+ pconMap[p.name] = p;
+ });
+
+ /* Link PCONs */
+ const roots = [];
+ pcons.forEach(p => {
+ if (p.depends_on && pconMap[p.depends_on]) {
+ pconMap[p.depends_on].children.push(p);
+ } else {
+ roots.push(p);
+ }
+ });
+
+ /* Sort roots and children by name */
+ const sortByName = (a, b) => a.name.localeCompare(b.name);
+ roots.sort(sortByName);
+ pcons.forEach(p => p.children.sort(sortByName));
+
+ /* Helper to recursively render PCONs and their builders */
+ function renderPcon(pcon, parentDiv) {
+ const div = createPconDiv(pcon);
+ parentDiv.appendChild(div);
+ const childrenContainer = div.querySelector(".pcon-children");
+
+ /* Render builders belonging to this PCON */
+ /* We search the global builder list for those matching this pcon */
+ const myBuilders = last_builder_list.filter(b => b.pcon === pcon.name);
+ myBuilders.sort((a, b) => a.name.localeCompare(b.name));
+
+ if (myBuilders.length > 0) {
+ const table = document.createElement("table");
+ table.className = "builders";
+ const tbody = document.createElement("tbody");
+ table.appendChild(tbody);
+ myBuilders.forEach(b => {
+ tbody.appendChild(createBuilderRow(b));
+ });
+ childrenContainer.appendChild(table);
+ }
+
+ /* Render child PCONs */
+ pcon.children.forEach(child => {
+ renderPcon(child, childrenContainer);
+ });
+ }
+
+ roots.forEach(root => {
+ renderPcon(root, container);
+ });
+
+ /* Render orphan builders (no pcon or unknown pcon) */
+ const orphanBuilders = last_builder_list.filter(b => !b.pcon || !pcon_topology[b.pcon]);
+ if (orphanBuilders.length > 0) {
+ const orphanDiv = document.createElement("div");
+ orphanDiv.className = "pcon-orphans";
+ orphanDiv.innerHTML = "<div class='pcon-header'><b>Unmanaged Builders</b></div>";
+ const childrenContainer = document.createElement("div");
+ childrenContainer.className = "pcon-children";
+ orphanDiv.appendChild(childrenContainer);
+
+ const table = document.createElement("table");
+ table.className = "builders";
+ const tbody = document.createElement("tbody");
+ table.appendChild(tbody);
+ orphanBuilders.forEach(b => {
+ tbody.appendChild(createBuilderRow(b));
+ });
+ childrenContainer.appendChild(table);
+
+ container.appendChild(orphanDiv);
+ }
+}
+
function ws_open_sai()
-{
+{
var s = "", q, qa, qi, q5, q5s;
if (document.getElementById("apirev"))
- document.getElementById("apirev").innerHTML = "API rev " + SAI_JS_API_VERSION;
+ document.getElementById("apirev").innerHTML = "API rev " + SAI_JS_API_VERSION;
q = window.location.href;
console.log(q);
@@ -1300,12 +1534,12 @@ function ws_open_sai()
* gitohashi has ?h=branch and ?id=hash possible
*/
qa = q.split("?");
- if (qa[1])
+ if (qa[1])
s += "?" + qa[1];
console.log(s);
gitohashi_integ = 1;
}
-
+
qi = q.indexOf("?task=");
if (qi != -1) {
/*
@@ -1313,7 +1547,7 @@ function ws_open_sai()
*/
s += "/specific?task=" + q.substring(qi + 6);
}
-
+
var s1 = get_appropriate_ws_url() + "/sai/browse" + s;
// if (s1.split("?"))
// s1 = s1.split("?")[0];
@@ -1332,50 +1566,50 @@ function ws_open_sai()
tid, eid;
tid = par.get('task');
eid = par.get('event');
-
-
+
+
if (tid) {
/*
* We're being the page monitoring / reporting
* on what happened with a specific task... ask
* about the specific task on the ws link
*/
-
+
console.log("tid " + tid);
-
+
sai.send("{\"schema\":" +
"\"com.warmcat.sai.taskinfo\"," +
"\"js_api_version\": " + SAI_JS_API_VERSION + "," +
"\"logs\": 1," +
"\"last_log_ts\":" + last_log_timestamp + "," +
"\"task_hash\":" +
- JSON.stringify(tid) + "}");
-
+ JSON.stringify(tid) + "}");
+
return;
}
-
+
if (eid) {
/*
* We're being the page monitoring / reporting
* on what happened with a specific event... ask
* about the specific event on the ws link
*/
-
+
console.log("eid " + eid);
-
+
sai.send("{\"schema\":" +
"\"com.warmcat.sai.eventinfo\"," +
"\"js_api_version\": " + SAI_JS_API_VERSION + "," +
"\"event_hash\":" +
- JSON.stringify(eid) + "}");
-
+ JSON.stringify(eid) + "}");
+
return;
}
-
+
/*
* request the overview schema
*/
-
+
sai.send("{\"schema\":" +
"\"com.warmcat.sai.taskinfo\", \"js_api_version\": " + SAI_JS_API_VERSION + "}");
};
@@ -1389,10 +1623,10 @@ function ws_open_sai()
// return;
jso = JSON.parse(msg.data);
// console.log(jso.schema);
-
+
if (jso.alang) {
var a = jso.alang.split(","), n;
-
+
for (n = 0; n < a.length; n++) {
var b = a[n].split(";");
switch (b[0]) {
@@ -1436,69 +1670,31 @@ function ws_open_sai()
console.log(jso.schema);
- switch (jso.schema) {
-
- case "com.warmcat.sai.builders":
- const buildersContainer = document.getElementById("sai_builders");
- if (!buildersContainer) { break; }
+ switch (jso.schema) {
+ case "com.warmcat.sai.builders":
+ /* Update builder list */
let platformsArray = (jso.platforms && Array.isArray(jso.platforms)) ? jso.platforms :
(jso.builders && Array.isArray(jso.builders)) ? jso.builders : null;
- if (!platformsArray) {
- buildersContainer.innerHTML = ""; // Clear display if data is invalid
- break;
- }
-
- platformsArray.sort((a, b) => (a.name && b.name) ? a.name.localeCompare(b.name) : 0);
-
- let table = buildersContainer.querySelector("table.builders");
- if (!table) {
- buildersContainer.innerHTML = ""; // Clear placeholder
- table = document.createElement("table");
- table.className = "builders";
- table.appendChild(document.createElement("tbody"));
- buildersContainer.appendChild(table);
- }
- const tbody = table.querySelector("tbody");
-
- // --- Reconciliation Logic ---
- let platIdx = 0;
- let currentRow = tbody.firstChild;
-
- while (platIdx < platformsArray.length) {
- const plat = platformsArray[platIdx];
-
- if (!currentRow) {
- tbody.appendChild(createBuilderRow(plat));
- platIdx++;
- continue;
- }
-
- const rowPlatName = currentRow.id.substring(4); // "row-..."
- const comparison = plat.name.localeCompare(rowPlatName);
-
- if (comparison === 0) {
- updateBuilderRow(currentRow, plat);
- platIdx++;
- currentRow = currentRow.nextSibling;
- } else if (comparison < 0) {
- tbody.insertBefore(createBuilderRow(plat), currentRow);
- platIdx++;
- } else { // comparison > 0
- const nextRow = currentRow.nextSibling;
- tbody.removeChild(currentRow);
- currentRow = nextRow;
- }
+ if (platformsArray) {
+ last_builder_list = platformsArray;
+ const container = document.getElementById("sai_builders");
+ if (container) renderPconHierarchy(container);
}
+ break;
- // Remove any remaining old rows at the end
- while (currentRow) {
- const nextRow = currentRow.nextSibling;
- tbody.removeChild(currentRow);
- currentRow = nextRow;
+ case "com.warmcat.sai.power_managed_builders":
+ /* Update PCON topology */
+ if (jso.power_controllers) {
+ jso.power_controllers.forEach(pc => {
+ pcon_topology[pc.name] = pc;
+ });
+ /* Trigger redraw if we have builders */
+ const container = document.getElementById("sai_builders");
+ if (container) renderPconHierarchy(container);
}
- break;
+ break;
case "com.warmcat.sai.build-metric":
var summaryDiv = document.getElementById("metrics-summary-" + jso.task_uuid);
@@ -1521,9 +1717,9 @@ function ws_open_sai()
* Sent with an array of e[] to start, but also
* can send a single e[] if it just changed
* state
- */
+ */
s = "<table>";
-
+
authd = jso.authorized;
if (jso.authorized === 0) {
if (document.getElementById("creds"))
@@ -1547,13 +1743,13 @@ function ws_open_sai()
san(auth_user) + " " + agify(now_ut, now_ut + jso.auth_secs);
}
}
-
+
/*
* Update existing?
*/
-
+
// console.log("jso.overview.length " + jso.overview.length);
-
+
if (jso.overview.length == 1 &&
document.getElementById("esr-" + jso.overview[0].e.uuid)) {
/* this is just the summary box, not the tasks */
@@ -1561,12 +1757,12 @@ function ws_open_sai()
sai_event_summary_render(jso.overview[0], now_ut, 1);
/* if the task status icons exist, update their state */
-
+
for (n = jso.overview[0].t.length - 1; n >= 0; n--)
refresh_state(jso.overview[0].t[n].uuid, jso.overview[0].t[n].state);
-
+
update_summary_and_progress(jso.overview[0].e.uuid);
-
+
aging();
} else
{
@@ -1576,12 +1772,12 @@ function ws_open_sai()
if (jso.overview.length) {
for (n = jso.overview.length - 1; n >= 0; n--)
s += sai_event_render(jso.overview[n], now_ut, 1);
-
+
s = s + "</table>";
-
+
if (document.getElementById("sai_sticky"))
document.getElementById("sai_sticky").innerHTML = s;
-
+
for (n = jso.overview.length - 1; n >= 0; n--) {
document.getElementById("esr-" + jso.overview[n].e.uuid).innerHTML =
sai_event_summary_render(jso.overview[n], now_ut, 1);
@@ -1590,13 +1786,13 @@ function ws_open_sai()
}
aging();
}
-
+
if (gitohashi_integ && document.getElementById("gitohashi_sai_icon")) {
var integ_state = 0;
document.getElementById("gitohashi_sai_icon").addEventListener("mouseenter", function( event ) {
- document.getElementById("gitohashi_sai_icon").style.zIndex = 1999;
+ document.getElementById("gitohashi_sai_icon").style.zIndex = 1999;
document.getElementById("gitohashi_sai_details").style.zIndex = 2000;
- document.getElementById("gitohashi_sai_details").style.opacity = 1.0;
+ document.getElementById("gitohashi_sai_details").style.opacity = 1.0;
integ_state = 1;
}, false);
@@ -1612,13 +1808,13 @@ function ws_open_sai()
}
document.getElementById("gitohashi_sai_details").style.opacity = 0.0;
document.getElementById("gitohashi_sai_details").style.zIndex = -1;
- document.getElementById("gitohashi_sai_icon").style.zIndex = 2001;
+ document.getElementById("gitohashi_sai_icon").style.zIndex = 2001;
}, true);
-
+
aging();
}
}
-
+
if (jso.overview.length)
for (n = jso.overview.length - 1; n >= 0; n--) {
if (document.getElementById("rebuild-ev-" + san(jso.overview[n].e.uuid)))
@@ -1628,10 +1824,10 @@ function ws_open_sai()
var rs= "{\"schema\":" +
"\"com.warmcat.sai.eventreset\"," +
"\"uuid\": " +
- JSON.stringify(san(e.srcElement.id.substring(11))) + "}";
-
- console.log(rs);
- sai.send(rs);
+ JSON.stringify(san(e.srcElement.id.substring(11))) + "}";
+
+ console.log(rs);
+ sai.send(rs);
});
if (document.getElementById("delete-ev-" + san(jso.overview[n].e.uuid)))
document.getElementById("delete-ev-" + san(jso.overview[n].e.uuid)).
@@ -1640,15 +1836,15 @@ function ws_open_sai()
var rs= "{\"schema\":" +
"\"com.warmcat.sai.eventdelete\"," +
"\"uuid\": " +
- JSON.stringify(san(e.srcElement.id.substring(10))) + "}";
-
- console.log(rs);
- sai.send(rs);
+ JSON.stringify(san(e.srcElement.id.substring(10))) + "}";
+
+ console.log(rs);
+ sai.send(rs);
setTimeout(after_delete, 750);
});
}
break;
-
+
case "com.warmcat.sai.taskinfo":
if (!jso.t)
@@ -1677,7 +1873,7 @@ function ws_open_sai()
san(auth_user) + " " + agify(now_ut, now_ut + jso.auth_secs);
}
}
-
+
/*
* We get told about changes to any task state,
* it's up to us to figure out if the page we
@@ -1691,17 +1887,17 @@ function ws_open_sai()
* First see if it appears as a tuple, and if
* so, let's just update that
*/
-
+
if (document.getElementById("taskstate_" + jso.t.uuid)) {
console.log("found taskstate_" + jso.t.uuid);
refresh_state(jso.t.uuid, jso.t.state);
-
+
update_summary_and_progress(jso.t.uuid.substring(0, 32));
} else
-
+
/* update task summary if shown anywhere */
-
+
if (document.getElementById("taskinfo-" + jso.t.uuid)) {
console.log("FOUND taskinfo-" + jso.t.uuid);
document.getElementById("taskinfo-" + jso.t.uuid).innerHTML = sai_taskinfo_render(jso);
@@ -1711,20 +1907,20 @@ function ws_open_sai()
update_summary_and_progress(jso.e.uuid);
} else {
-
+
console.log("NO taskinfo- or taskstate_" + jso.t.uuid);
-
- /*
+
+ /*
* Last chance if we might be
* on a task-specific page, and
* want to show the task info
* at the top
*/
-
+
const urlParams = new URLSearchParams(window.location.search);
const url_task_uuid = urlParams.get('task');
-
+
if (url_task_uuid === jso.t.uuid &&
document.getElementById("sai_sticky"))
document.getElementById("sai_sticky").innerHTML =
@@ -1732,8 +1928,8 @@ function ws_open_sai()
san(jso.t.uuid) + "\">" +
sai_taskinfo_render(jso) +
"</div>";
-
-
+
+
s = "<table><td colspan=\"3\"><pre><table class=\"scrollogs\"><tr>" +
"<td class=\"atop\">" +
"<div id=\"dlogsn\" class=\"dlogsn\">" + lines + "</div></td>" +
@@ -1741,16 +1937,16 @@ function ws_open_sai()
"<div id=\"dlogst\" class=\"dlogst\">" + times + "</div></td>" +
"<td class=\"atop\"><div id=\"dlogs\" class=\"dlogs\">" +
"<span id=\"logs\" class=\"nowrap\">" + logs +
- "</span>"+
+ "</span>"+
"</div></td></tr></table></pre>";
-
+
if (document.getElementById("sai_overview")) {
document.getElementById("sai_overview").innerHTML = s;
-
+
if (document.getElementById("esr-" + jso.e.uuid))
document.getElementById("esr-" + jso.e.uuid).innerHTML =
sai_event_summary_render(jso, now_ut, 1);
-
+
}
update_summary_and_progress(jso.e.uuid);
@@ -1760,10 +1956,10 @@ function ws_open_sai()
var rs= "{\"schema\":" +
"\"com.warmcat.sai.taskreset\"," +
"\"uuid\": " +
- JSON.stringify(san(e.srcElement.id.substring(8))) + "}";
-
- console.log(rs);
- sai.send(rs);
+ JSON.stringify(san(e.srcElement.id.substring(8))) + "}";
+
+ console.log(rs);
+ sai.send(rs);
/*
* and immediately re-request the task info, so we can get
@@ -1781,29 +1977,29 @@ function ws_open_sai()
console.log(rq);
sai.send(rq);
- document.getElementById("dlogsn").innerHTML = "";
- document.getElementById("dlogst").innerHTML = "";
- document.getElementById("logs").innerHTML = "";
- lines = times = logs = "";
+ document.getElementById("dlogsn").innerHTML = "";
+ document.getElementById("dlogst").innerHTML = "";
+ document.getElementById("logs").innerHTML = "";
+ lines = times = logs = "";
logAnsiState = {};
- tfirst = 0;
- lli = 1;
+ tfirst = 0;
+ lli = 1;
last_log_timestamp = 0;
- });
+ });
}
-
+
if (document.getElementById("stop-" + san(jso.t.uuid))) {
document.getElementById("stop-" + san(jso.t.uuid)).
addEventListener("click", function(e) {
var rs= "{\"schema\":" +
"\"com.warmcat.sai.taskcan\"," +
"\"task_uuid\": " +
- JSON.stringify(san(e.srcElement.id.substring(5))) + "}";
- console.log(rs);
- sai.send(rs);
- });
+ JSON.stringify(san(e.srcElement.id.substring(5))) + "}";
+ console.log(rs);
+ sai.send(rs);
+ });
}
-
+
aging();
}
break;
@@ -1880,17 +2076,17 @@ function ws_open_sai()
case "com-warmcat-sai-artifact":
console.log(jso);
-
+
sai_arts += "<div class=\"sai_arts\"><img src=\"artifact.svg\"> <a href=\"artifacts/" +
san(jso.task_uuid) + "/" +
san(jso.artifact_down_nonce) + "/" +
san(jso.blob_filename) + "\">" +
san(jso.blob_filename) + "</a> " +
humanize(jso.len) + "B </div>";
-
+
if (document.getElementById("sai_arts"))
document.getElementById("sai_arts").innerHTML = sai_arts;
-
+
break;
case "com.warmcat.sai.taskactivity":
@@ -1918,14 +2114,14 @@ function ws_open_sai()
} catch (e) {
break;
}
-
+
if (!tfirst)
tfirst = jso.timestamp;
last_log_timestamp = jso.timestamp;
-
+
li = (s1.match(/\n/g)||[]).length;
-
+
switch (jso.channel) {
case 1:
logs += s;
@@ -1948,13 +2144,13 @@ function ws_open_sai()
}
-
+
if (cont && !cont[jso.channel] && jso.len)
tn = ((jso.timestamp - tfirst) / 1000000).toFixed(4);
if (cont)
cont[jso.channel] = (li == 0);
-
+
while (li--) {
en += "<a id=\"#sn" + lli +
"\" href=\"#sn" + lli + "\">" +
@@ -1962,10 +2158,10 @@ function ws_open_sai()
tn += "<br>"
lli++;
}
-
+
lines += en;
times += tn;
-
+
if (!redpend) {
redpend = 1;
setTimeout(function() {
@@ -1992,7 +2188,7 @@ function ws_open_sai()
rightPane.clientHeight;
}, 500);
}
-
+
break;
} /* switch */
} /* onmessage */
@@ -2010,18 +2206,18 @@ function ws_open_sai()
myVar = setTimeout(ws_open_sai, 4000);
};
} catch(exception) {
- alert("<p>Error" + exception);
+ alert("<p>Error" + exception);
}
}
function post_login_form()
{
var xhr = new XMLHttpRequest(), s ="", q = window.location.pathname;
-
+
s = "----boundo\x0d\x0acontent-disposition: form-data; name=\"lname\"\x0d\x0a\x0d\x0a" +
document.getElementById("lname").value +
"\x0d\x0a----boundo\x0d\x0acontent-disposition: form-data; name=\"lpass\"\x0d\x0a\x0d\x0a" +
- document.getElementById("lpass").value +
+ document.getElementById("lpass").value +
"\x0d\x0a----boundo\x0d\x0acontent-disposition: form-data; name=\"success_redir\"\x0d\x0a\x0d\x0a" +
document.getElementById("success_redir").value +
"\x0d\x0a----boundo--";
@@ -2030,7 +2226,7 @@ function post_login_form()
q = q.substring(0, q.length - 10);
xhr.open("POST", q + "login", true);
xhr.setRequestHeader( 'content-type', "multipart/form-data; boundary=--boundo");
-
+
console.log(s.length +" " + s);
xhr.onload = function (e) {
@@ -2048,7 +2244,7 @@ function post_login_form()
};
xhr.send(s);
-
+
return false;
}
@@ -2083,7 +2279,7 @@ window.addEventListener("load", function() {
if (document.getElementById("noscript"))
document.getElementById("noscript").display = "none";
-
+
/* login form hidden success redirect */
if (document.getElementById("success_redir"))
document.getElementById("success_redir").value =
@@ -2095,16 +2291,16 @@ window.addEventListener("load", function() {
document.getElementById("login-button").addEventListener("click", post_login_form);
document.getElementById("logout-button").addEventListener("click", post_login_form);
}
-
+
setInterval(function() {
update_task_activities();
var locked = document.body.scrollHeight -
- document.body.clientHeight <= document.body.scrollTop + 1;
-
+ document.body.clientHeight <= document.body.scrollTop + 1;
+
if (locked)
document.body.scrollTop = document.body.scrollHeight -
- document.body.clientHeight;
+ document.body.clientHeight;
}, 500)
diff --git a/etc-sai-EXAMPLE/power/conf b/etc-sai-EXAMPLE/power/conf
index 515bea0..5573538 100644
--- a/etc-sai-EXAMPLE/power/conf
+++ b/etc-sai-EXAMPLE/power/conf
@@ -5,30 +5,19 @@
{
"url" : "wss://libwebsockets.org:4444/sai/builder",
- "platforms": [
+ "power_controllers": [
{
- "name": "rocky9/x86_64-amd/gcc",
- "power-on": {
- "type": "wol",
- "mac": "00:e0:4c:68:01:30"
- },
- "power-off": {
- "type": "suspend"
- }
+ "name": "pc1",
+ "type": "tasmota",
+ "url": "http://10.199.0.240"
},
{
- "name": "rocky9/aarch64-a72a55-rk3588/gcc",
- "power-on": {
- "type": "tasmota",
- "url": "http://10.199.0.240/cm?cmnd=Power%20On"
- },
- "power-off": {
- "type": "tasmota",
- "url": "http://10.199.0.240/cm?cmnd=Power%20Off"
- }
+ "name": "pc2",
+ "type": "tasmota",
+ "url": "http://10.199.0.241",
+ "depends_on": "pc1"
}
]
}
]
}
-
diff --git a/src/builder/b-conf.c b/src/builder/b-conf.c
index a887912..ce0d34a 100644
--- a/src/builder/b-conf.c
+++ b/src/builder/b-conf.c
@@ -37,6 +37,7 @@ static const char * const paths_global[] = {
"metrics_path",
"metrics_secret",
"sai-power",
+ "power_controller",
"power-on.type",
"power-on.url",
"power-on.mac",
@@ -54,6 +55,7 @@ enum enum_paths_global {
LEJPM_METRICS_PATH,
LEJPM_METRICS_SECRET,
LEJPM_SAI_POWER,
+ LEJPM_POWER_CONTROLLER,
LEJPM_POWER_ON_TYPE,
LEJPM_POWER_ON_URL,
LEJPM_POWER_ON_MAC,
@@ -278,6 +280,10 @@ saib_conf_global_cb(struct lejp_ctx *ctx, char reason)
pp = &a->builder->url_sai_power;
break;
+ case LEJPM_POWER_CONTROLLER:
+ pp = &a->builder->power_controller_name;
+ break;
+
case LEJPM_METRICS_URI:
pp = &a->builder->metrics_uri;
break;
diff --git a/src/builder/b-power.c b/src/builder/b-power.c
index d82be98..28aa0e2 100644
--- a/src/builder/b-power.c
+++ b/src/builder/b-power.c
@@ -30,6 +30,97 @@
#include "b-private.h"
extern struct lws_spawn_piped *lsp_suspender;
+struct lws_ss_handle *ss_power_client = NULL;
+
+/*
+ * Registration message logic
+ */
+
+LWS_SS_USER_TYPEDEF
+ char payload[200];
+ size_t size;
+ size_t pos;
+ struct lws_buflist *bl_tx;
+} saib_power_client_t;
+
+static lws_ss_state_return_t
+saib_power_client_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
+ size_t *len, int *flags)
+{
+ saib_power_client_t *g = (saib_power_client_t *)userobj;
+
+ return sai_ss_tx_from_buflist_helper(ss_power_client, &g->bl_tx,
+ buf, len, flags);
+}
+
+static lws_ss_state_return_t
+saib_power_client_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
+{
+ /* We don't really expect RX from sai-power on this link currently
+ * other than maybe stay info if we merged that logic.
+ * For now, ignore.
+ */
+ return LWSSSSRET_OK;
+}
+
+static lws_ss_state_return_t
+saib_power_client_state(void *userobj, void *sh, lws_ss_constate_t state,
+ lws_ss_tx_ordinal_t ack)
+{
+ saib_power_client_t *g = (saib_power_client_t *)userobj;
+ sai_builder_registration_t r;
+ struct lwsac *ac = NULL;
+
+ switch (state) {
+ case LWSSSCS_CONNECTED:
+ lwsl_notice("%s: Connected to sai-power, sending registration\n", __func__);
+
+ /* Prepare registration message */
+ memset(&r, 0, sizeof(r));
+ lws_strncpy(r.builder_name, builder.host, sizeof(r.builder_name));
+ if (builder.power_controller_name)
+ lws_strncpy(r.power_controller_name, builder.power_controller_name, sizeof(r.power_controller_name));
+ else
+ lws_strncpy(r.power_controller_name, "unknown", sizeof(r.power_controller_name));
+
+ /* Add platforms */
+ lws_start_foreach_dll(struct lws_dll2 *, d, builder.sai_plat_owner.head) {
+ sai_plat_t *sp = lws_container_of(d, sai_plat_t, sai_plat_list);
+ sai_builder_platform_t *bp = lwsac_use_zero(&ac, sizeof(*bp), 512);
+
+ if (bp) {
+ lws_strncpy(bp->name, sp->name, sizeof(bp->name));
+ lws_dll2_add_tail(&bp->list, &r.platforms_owner);
+ }
+ } lws_end_foreach_dll(d);
+
+ /* Send it */
+ sai_ss_serialize_queue_helper(ss_power_client, &g->bl_tx,
+ lsm_schema_builder_registration,
+ LWS_ARRAY_SIZE(lsm_schema_builder_registration),
+ &r);
+
+ lwsac_free(&ac);
+ break;
+
+ case LWSSSCS_DISCONNECTED:
+ lwsl_notice("%s: Disconnected from sai-power\n", __func__);
+ lws_buflist_destroy_all_segments(&g->bl_tx);
+ break;
+
+ default:
+ break;
+ }
+
+ return LWSSSSRET_OK;
+}
+
+LWS_SS_INFO("sai_power_client", saib_power_client_t)
+ .rx = saib_power_client_rx,
+ .tx = saib_power_client_tx,
+ .state = saib_power_client_state,
+};
+
int
saib_reassess_idle_situation()
@@ -392,6 +483,7 @@ saib_power_init(void)
* it's willing to auto power-off or not.
*/
+ /* Existing SS creation for power-off logic */
lwsl_notice("%s: creating sai-power ss...\n", __func__);
if (lws_ss_create(builder.context, 0, &ssi_saib_power_link_t,
@@ -402,6 +494,25 @@ saib_power_init(void)
saib_reassess_idle_situation();
+ /*
+ * NEW: Create the persistent WS connection for registration
+ * We reuse builder.url_sai_power.
+ * Note: builder.url_sai_power is a URL string. We need to extract the policy endpoint?
+ * The policy for sai-power client needs to be defined.
+ * 'sai_power' in policy covers it.
+ */
+
+ if (lws_ss_create(builder.context, 0, &ssi_saib_power_client_t,
+ NULL, &ss_power_client, NULL, NULL)) {
+ lwsl_err("%s: failed to create sai-power client ss\n", __func__);
+ } else {
+ /* Set metadata for URL? The policy handles 'sai_power' endpoint.
+ * We might need to ensure the policy matches what we expect.
+ * The existing code assumes 'sai_power' policy exists.
+ */
+ }
+
+
return 0;
}
diff --git a/src/builder/b-private.h b/src/builder/b-private.h
index 216f9c8..63ad022 100644
--- a/src/builder/b-private.h
+++ b/src/builder/b-private.h
@@ -129,6 +129,7 @@ struct sai_builder {
const char *metrics_secret;
const char *url_sai_power;
+ const char *power_controller_name;
const char *power_off_type;
const char *power_off_url;
diff --git a/src/common/include/private.h b/src/common/include/private.h
index 344c5cc..9adf49b 100644
--- a/src/common/include/private.h
+++ b/src/common/include/private.h
@@ -499,6 +499,7 @@ typedef struct sai_plat {
const char *name;
const char *platform;
+ const char *pcon; /* PCON name */
struct lejp_ctx ctx;
lws_dll2_owner_t nspawn_owner;
@@ -609,6 +610,8 @@ typedef struct sai_power_controller {
lws_dll2_t list;
lws_dll2_owner_t controlled_builders_owner;
char name[64];
+ char type[32];
+ char depends_on[64];
char on;
} sai_power_controller_t;
@@ -632,6 +635,21 @@ typedef struct sai_stay_state_update {
char stay_on;
} sai_stay_state_update_t;
+/* Builder -> sai-power registration */
+
+typedef struct sai_builder_platform {
+ lws_dll2_t list;
+ char name[64];
+} sai_builder_platform_t;
+
+typedef struct sai_builder_registration {
+ lws_dll2_t list;
+ lws_dll2_owner_t platforms_owner; /* sai_builder_platform_t */
+ char builder_name[64];
+ char power_controller_name[64];
+} sai_builder_registration_t;
+
+
/*
* Because the definitions of these arrays of map structs are mostly in
* common/struct-metadata.c, we are forced to repeat the length of the struct
@@ -647,7 +665,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[3],
+ lsm_power_controller[5],
lsm_schema_json_map_task[],
lsm_schema_sq3_map_task[],
lsm_schema_sq3_map_event[],
@@ -681,7 +699,12 @@ extern const lws_struct_map_t
lsm_stay_state_update[2],
lsm_schema_stay_state_update[1],
lsm_build_metric[14],
- lsm_plat[13];
+ lsm_plat[14], /* +1 for pcon */
+ lsm_builder_platform[1],
+ lsm_builder_registration[3],
+ lsm_schema_sq3_map_power_controller[1],
+ lsm_schema_sq3_map_controlled_builder[1],
+ lsm_schema_builder_registration[1];
extern const lws_ss_info_t ssi_said_logproxy;
extern struct lws_ss_handle *ssh[3];
diff --git a/src/common/struct-metadata.c b/src/common/struct-metadata.c
index 5f47757..516d39a 100644
--- a/src/common/struct-metadata.c
+++ b/src/common/struct-metadata.c
@@ -81,6 +81,7 @@ const lws_struct_map_t lsm_plat[] = {
LSM_UNSIGNED (sai_plat_t, uid, "uid"),
LSM_STRING_PTR (sai_plat_t, name, "name"),
LSM_STRING_PTR (sai_plat_t, platform, "platform"),
+ LSM_STRING_PTR (sai_plat_t, pcon, "pcon"),
LSM_JO_SIGNED (sai_plat_t, online, "online"),
LSM_UNSIGNED (sai_plat_t, last_seen, "last_seen"),
LSM_JO_SIGNED (sai_plat_t, powering_up, "powering_up"),
@@ -312,14 +313,29 @@ const lws_struct_map_t lsm_controlled_builder[] = {
LSM_CARRAY(sai_controlled_builder_t, name, "name"),
};
+/* SQLite specific map for controlled builder: map 'name' to 'builder_name' column */
+const lws_struct_map_t lsm_sq3_controlled_builder[] = {
+ LSM_CARRAY(sai_controlled_builder_t, name, "builder_name"),
+};
+
const lws_struct_map_t lsm_power_controller[] = {
LSM_CARRAY(sai_power_controller_t, name, "name"),
+ 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_LIST(sai_power_controller_t, controlled_builders_owner,
sai_controlled_builder_t, list, NULL,
lsm_controlled_builder, "controlled_builders"),
};
+/* SQLite specific map for power controller: 'on' -> 'state' and no list */
+const lws_struct_map_t lsm_sq3_power_controller[] = {
+ LSM_CARRAY(sai_power_controller_t, name, "name"),
+ 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, "state"),
+};
+
const lws_struct_map_t lsm_power_managed_builder[] = {
LSM_CARRAY(sai_power_managed_builder_t, name, "name"),
LSM_UNSIGNED(sai_power_managed_builder_t, stay_on, "stay_on"),
@@ -340,6 +356,16 @@ const lws_struct_map_t lsm_schema_power_managed_builders[] = {
"com.warmcat.sai.power_managed_builders"),
};
+const lws_struct_map_t lsm_schema_sq3_map_power_controller[] = {
+ LSM_SCHEMA_DLL2(sai_power_controller_t, list, NULL,
+ lsm_sq3_power_controller, "power_controllers"),
+};
+
+const lws_struct_map_t lsm_schema_sq3_map_controlled_builder[] = {
+ LSM_SCHEMA_DLL2(sai_controlled_builder_t, list, NULL,
+ lsm_sq3_controlled_builder, "pcon_builders"),
+};
+
const lws_struct_map_t lsm_stay_state_update[] = {
LSM_CARRAY(sai_stay_state_update_t, builder_name, "builder_name"),
LSM_UNSIGNED(sai_stay_state_update_t, stay_on, "stay_on"),
@@ -349,3 +375,21 @@ const lws_struct_map_t lsm_schema_stay_state_update[] = {
LSM_SCHEMA(sai_stay_state_update_t, NULL, lsm_stay_state_update,
"com.warmcat.sai.stay_state_update"),
};
+
+const lws_struct_map_t lsm_builder_platform[] = {
+ LSM_CARRAY(sai_builder_platform_t, name, "name"),
+};
+
+const lws_struct_map_t lsm_builder_registration[] = {
+ LSM_LIST(sai_builder_registration_t, platforms_owner,
+ sai_builder_platform_t, list, NULL,
+ lsm_builder_platform, "platforms"),
+ LSM_CARRAY(sai_builder_registration_t, builder_name, "builder_name"),
+ LSM_CARRAY(sai_builder_registration_t, power_controller_name, "power_controller_name"),
+};
+
+const lws_struct_map_t lsm_schema_builder_registration[] = {
+ LSM_SCHEMA(sai_builder_registration_t, NULL,
+ lsm_builder_registration,
+ "com.warmcat.sai.builder_registration"),
+};
diff --git a/src/power/p-conf.c b/src/power/p-conf.c
index 11ffd96..0e8da54 100644
--- a/src/power/p-conf.c
+++ b/src/power/p-conf.c
@@ -33,22 +33,18 @@ static const char * const paths_global[] = {
"perms",
"wol-if",
"servers[].url",
- "servers[].power-control[].name",
- "servers[].power-control[].type",
- "servers[].power-control[].url",
- "servers[].power-control[]",
- "servers[].platforms[].name",
- "servers[].platforms[].host",
- "servers[].platforms[].depends",
- "servers[].platforms[].power-on.type",
- "servers[].platforms[].power-on.mac",
- "servers[].platforms[].power-on.url",
- "servers[].platforms[].power-on",
- "servers[].platforms[].power-off.type",
- "servers[].platforms[].power-off.url",
- "servers[].platforms[].power-off",
- "servers[].platforms[].power-control",
- "servers[].platforms[]",
+ "servers[].power_controllers[].name",
+ "servers[].power_controllers[].type",
+ "servers[].power_controllers[].url",
+ "servers[].power_controllers[].mac",
+ "servers[].power_controllers[].depends_on",
+ "servers[].power_controllers[]",
+ "power_controllers[].name",
+ "power_controllers[].type",
+ "power_controllers[].url",
+ "power_controllers[].mac",
+ "power_controllers[].depends_on",
+ "power_controllers[]",
"servers[]"
};
@@ -56,22 +52,18 @@ enum enum_paths_global {
LEJPM_PERMS,
LEJPM_WOL_IF,
LEJPM_SERVERS_URL,
- LEJPM_SERVERS_POWER_CONTROL_NAME,
- LEJPM_SERVERS_POWER_CONTROL_TYPE,
- LEJPM_SERVERS_POWER_CONTROL_URL,
- LEJPM_SERVERS_POWER_CONTROL,
- LEJPM_SERVERS_PLATFORMS_NAME,
- LEJPM_SERVERS_PLATFORMS_HOST,
- LEJPM_SERVERS_PLATFORMS_DEPENDS,
- LEJPM_SERVERS_PLATFORMS_POWER_ON_TYPE,
- LEJPM_SERVERS_PLATFORMS_POWER_ON_MAC,
- LEJPM_SERVERS_PLATFORMS_POWER_ON_URL,
- LEJPM_SERVERS_PLATFORMS_POWER_ON,
- LEJPM_SERVERS_PLATFORMS_POWER_OFF_TYPE,
- LEJPM_SERVERS_PLATFORMS_POWER_OFF_URL,
- LEJPM_SERVERS_PLATFORMS_POWER_OFF,
- LEJPM_SERVERS_PLATFORMS_POWER_CONTROL,
- LEJPM_SERVERS_PLATFORMS,
+ LEJPM_SRV_PCON_NAME,
+ LEJPM_SRV_PCON_TYPE,
+ LEJPM_SRV_PCON_URL,
+ LEJPM_SRV_PCON_MAC,
+ LEJPM_SRV_PCON_DEPENDS_ON,
+ LEJPM_SRV_PCON,
+ LEJPM_PCON_NAME,
+ LEJPM_PCON_TYPE,
+ LEJPM_PCON_URL,
+ LEJPM_PCON_MAC,
+ LEJPM_PCON_DEPENDS_ON,
+ LEJPM_PCON,
LEJPM_SERVERS
};
@@ -79,18 +71,8 @@ static signed char
saip_conf_global_cb(struct lejp_ctx *ctx, char reason)
{
struct jpargs *a = (struct jpargs *)ctx->user;
- saip_pcon_t *pc;
const char **pp = NULL;
-#if 0
-// int n;
-
- lwsl_notice("%s: reason: %d, path: %s, match %d\n", __func__,
- reason, ctx->path, ctx->path_match);
-// for (n = 0; n < ctx->wildcount; n++)
-// lwsl_notice(" %d\n", ctx->wild[n]);
-#endif
-
if (reason == LEJPCB_OBJECT_START) {
switch (ctx->path_match - 1) {
@@ -111,32 +93,21 @@ saip_conf_global_cb(struct lejp_ctx *ctx, char reason)
&a->power->sai_server_owner);
break;
- case LEJPM_SERVERS_POWER_CONTROL:
+ case LEJPM_PCON:
+ case LEJPM_SRV_PCON:
/*
- * Create the saip_pcon_t object and bind to the server
+ * Create the saip_pcon_t object
*/
a->sai_pcon = lwsac_use_zero(&a->power->ac_conf_head,
sizeof(*a->sai_pcon), 4096);
if (!a->sai_pcon)
return -1;
+ /* We will link dependencies later */
lws_dll2_add_tail(&a->sai_pcon->list,
&a->power->sai_pcon_owner);
break;
- case LEJPM_SERVERS_PLATFORMS:
- /*
- * Create the saip_platform object and bind to the server
- */
- a->sai_server_plat = lwsac_use_zero(&a->power->ac_conf_head,
- sizeof(*a->sai_server_plat), 4096);
- if (!a->sai_server_plat)
- return -1;
-
- lws_dll2_add_tail(&a->sai_server_plat->list,
- &a->sai_server->sai_plat_owner);
- break;
-
default:
return 0;
}
@@ -161,63 +132,36 @@ saip_conf_global_cb(struct lejp_ctx *ctx, char reason)
pp = &a->power->wol_if;
break;
- case LEJPM_SERVERS_POWER_CONTROL_NAME:
+ case LEJPM_PCON_NAME:
+ case LEJPM_SRV_PCON_NAME:
pp = &a->sai_pcon->name;
break;
- case LEJPM_SERVERS_POWER_CONTROL_TYPE:
+ case LEJPM_PCON_TYPE:
+ case LEJPM_SRV_PCON_TYPE:
pp = &a->sai_pcon->type;
break;
- case LEJPM_SERVERS_POWER_CONTROL_URL:
+ case LEJPM_PCON_URL:
+ case LEJPM_SRV_PCON_URL:
pp = &a->sai_pcon->url;
break;
- case LEJPM_SERVERS_URL:
- pp = &a->sai_server->url;
- lwsl_user("%s: server url %.*s\n", __func__, ctx->npos, ctx->buf);
+ case LEJPM_PCON_MAC:
+ case LEJPM_SRV_PCON_MAC:
+ pp = &a->sai_pcon->mac;
break;
- case LEJPM_SERVERS_PLATFORMS_NAME:
- pp = &a->sai_server_plat->name;
+ case LEJPM_PCON_DEPENDS_ON:
+ case LEJPM_SRV_PCON_DEPENDS_ON:
+ pp = &a->sai_pcon->depends_on;
break;
- case LEJPM_SERVERS_PLATFORMS_HOST:
- pp = &a->sai_server_plat->host;
- break;
-
- case LEJPM_SERVERS_PLATFORMS_DEPENDS:
- pp = &a->sai_server_plat->depends;
- break;
-
- case LEJPM_SERVERS_PLATFORMS_POWER_ON_TYPE:
- pp = &a->sai_server_plat->power_on_type;
- break;
-
- case LEJPM_SERVERS_PLATFORMS_POWER_ON_MAC:
- pp = &a->sai_server_plat->power_on_mac;
- break;
-
- case LEJPM_SERVERS_PLATFORMS_POWER_ON_URL:
- pp = &a->sai_server_plat->power_on_url;
- break;
-
- case LEJPM_SERVERS_PLATFORMS_POWER_OFF_TYPE:
- pp = &a->sai_server_plat->power_off_type;
- break;
-
- case LEJPM_SERVERS_PLATFORMS_POWER_OFF_URL:
- pp = &a->sai_server_plat->power_off_url;
+ case LEJPM_SERVERS_URL:
+ pp = &a->sai_server->url;
+ lwsl_user("%s: server url %.*s\n", __func__, ctx->npos, ctx->buf);
break;
- case LEJPM_SERVERS_PLATFORMS_POWER_CONTROL:
- pc = saip_pcon_by_name(a->power, ctx->buf);
- if (pc)
- lws_dll2_add_tail(&a->sai_server_plat->pcon_list,
- &pc->controlled_plats_owner);
-
- return 0;
-
default:
return 0;
}
@@ -270,28 +214,26 @@ saip_config_global(struct sai_power *power, const char *d)
n = (int)ctx.line;
lejp_destruct(&ctx);
- /* let's wire up any dependencies so the dependent knows easily */
-
- lws_start_foreach_dll(struct lws_dll2 *, px, power->sai_server_owner.head) {
- saip_server_t *s = lws_container_of(px, saip_server_t, list);
-
- lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) {
- saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list);
-
- if (sp->depends) {
- saip_server_plat_t *d = find_platform(power, sp->depends);
-
- if (!d) {
- lwsl_err("%s: can't find dependency '%s' on conf for '%s'\n", __func__,
- sp->depends, sp->host);
- return 1;
- }
-
- lws_dll2_add_tail(&d->dependencies_list, &sp->dependencies_owner);
+ /* link up dependencies */
+ lws_start_foreach_dll(struct lws_dll2 *, p, power->sai_pcon_owner.head) {
+ saip_pcon_t *pc = lws_container_of(p, saip_pcon_t, list);
+
+ if (pc->depends_on) {
+ saip_pcon_t *parent = saip_pcon_by_name(power, pc->depends_on);
+ if (parent) {
+ pc->parent = parent;
+ /* Add to parent's children list if needed?
+ * The current requirement is just knowing the dependency.
+ * We might want to maintain a list of children in the parent
+ * for easier traversal, but let's stick to the struct.
+ */
+ } else {
+ lwsl_err("%s: PCON %s depends on unknown %s\n",
+ __func__, pc->name, pc->depends_on);
+ return 1;
}
-
- } lws_end_foreach_dll(px1);
- } lws_end_foreach_dll(px);
+ }
+ } lws_end_foreach_dll(p);
return 0;
}
diff --git a/src/power/p-http-api.c b/src/power/p-http-api.c
index ddd4ba5..ce294df 100644
--- a/src/power/p-http-api.c
+++ b/src/power/p-http-api.c
@@ -47,48 +47,74 @@ extern struct sai_power power;
static void
saip_sul_action_power_off(struct lws_sorted_usec_list *sul)
{
- saip_server_plat_t *sp = lws_container_of(sul,
- saip_server_plat_t, sul_delay_off);
+ saip_pcon_t *pc = lws_container_of(sul, saip_pcon_t, sul_delay_off);
lws_ss_state_return_t r;
- saip_pcon_t *pc;
- if (!sp->pcon_list.owner) {
- lwsl_notice("%s: no power-controller ss for %s\n", __func__, sp->host);
- return;
+ saip_notify_server_power_state(pc->name, 0, 1);
+
+ lwsl_warn("%s: powering OFF pcon %s\n", __func__, pc->name);
+
+ /* If type is WOL, we might suspend? Or nothing for now. */
+ if (pc->ss_tasmota_off) {
+ r = lws_ss_client_connect(pc->ss_tasmota_off);
+ if (r)
+ lwsl_ss_err(pc->ss_tasmota_off, "failed to connect tasmota OFF secure stream: %d", r);
}
+}
- pc = lws_container_of(sp->pcon_list.owner, saip_pcon_t,
- controlled_plats_owner);
+saip_pcon_t *
+find_pcon_by_builder_name(struct sai_power *pwr, const char *builder_name)
+{
+ lws_start_foreach_dll(struct lws_dll2 *, p, pwr->sai_pcon_owner.head) {
+ saip_pcon_t *pc = lws_container_of(p, saip_pcon_t, list);
+
+ lws_start_foreach_dll(struct lws_dll2 *, b_node, pc->registered_builders_owner.head) {
+ saip_builder_t *sb = lws_container_of(b_node, saip_builder_t, list);
- saip_notify_server_power_state(sp->host, 0, 1);
+ lwsl_notice("%s: %s %s\n", __func__, sb->name, builder_name);
- lwsl_warn("%s: powering OFF host %s via power-control %s\n", __func__, sp->host, pc->name);
+ if (!strcmp(sb->name, builder_name))
+ return pc;
- r = lws_ss_client_connect(pc->ss_tasmota_off);
- if (r)
- lwsl_ss_err(pc->ss_tasmota_off, "failed to connect tasmota OFF secure stream: %d", r);
+ } lws_end_foreach_dll(b_node);
+ } lws_end_foreach_dll(p);
+
+ return NULL;
}
-saip_server_plat_t *
-find_platform(struct sai_power *pwr, const char *host)
+/* Helper to find PCON by name */
+saip_pcon_t *
+find_pcon(struct sai_power *pwr, const char *pcon_name)
{
- lws_start_foreach_dll(struct lws_dll2 *, px, pwr->sai_server_owner.head) {
- saip_server_t *s = lws_container_of(px, saip_server_t, list);
-
- lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) {
- saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list);
+ return saip_pcon_by_name(pwr, pcon_name);
+}
- if (!strcmp(host, sp->host))
- return sp;
+void
+saip_builder_bringup(saip_server_t *sps, saip_pcon_t *pc)
+{
+ saip_notify_server_power_state(pc->name, 1, 0);
+
+ if (pc->type && !strcmp(pc->type, "wol")) {
+ if (pc->mac) {
+ lwsl_notice("%s: triggering WOL for %s\n", __func__, pc->name);
+ write(lws_spawn_get_fd_stdxxx(lsp_wol, 0),
+ pc->mac, strlen(pc->mac));
+ } else {
+ lwsl_err("%s: WOL type but no MAC for %s\n", __func__, pc->name);
+ }
+ }
- } lws_end_foreach_dll(px1);
- } lws_end_foreach_dll(px);
+ if (pc->ss_tasmota_on) {
+ lwsl_ss_notice(pc->ss_tasmota_on, "starting tasmota");
+ if (lws_ss_client_connect(pc->ss_tasmota_on))
+ lwsl_ss_err(pc->ss_tasmota_on, "failed to connect tasmota ON secure stream");
+ }
- return NULL;
+ saip_queue_stay_info(sps);
}
void
-saip_notify_server_stay_state(const char *plat_name, int stay_on)
+saip_notify_server_stay_state(const char *builder_name, int stay_on)
{
sai_stay_state_update_t ssu;
saip_server_link_t *m;
@@ -108,7 +134,7 @@ saip_notify_server_stay_state(const char *plat_name, int stay_on)
m = (saip_server_link_t *)lws_ss_to_user_object(sps->ss);
memset(&ssu, 0, sizeof(ssu));
- lws_strncpy(ssu.builder_name, plat_name, sizeof(ssu.builder_name));
+ lws_strncpy(ssu.builder_name, builder_name, sizeof(ssu.builder_name));
ssu.stay_on = (char)stay_on;
sai_ss_serialize_queue_helper(sps->ss, &m->bl_pwr_to_srv,
@@ -120,31 +146,30 @@ saip_notify_server_stay_state(const char *plat_name, int stay_on)
void
saip_set_stay(const char *builder_name, int stay_on)
{
- saip_server_plat_t *sp = find_platform(&power, builder_name);
- saip_server_link_t *pss;
+ saip_pcon_t *pc = find_pcon_by_builder_name(&power, builder_name);
+ /* saip_server_link_t *pss; */ /* Unused? */
saip_server_t *sps;
- if (!sp)
+ if (!pc) {
+ lwsl_warn("%s: Unknown builder %s\n", __func__, builder_name);
return;
+ }
sps = lws_container_of(power.sai_server_owner.head, saip_server_t, list);
- pss = (saip_server_link_t *)lws_ss_to_user_object(sps->ss);
- sp->stay = (char)stay_on;
- saip_notify_server_stay_state(builder_name, stay_on | sp->needed);
+ /* pss = (saip_server_link_t *)lws_ss_to_user_object(sps->ss); */
- if (stay_on | sp->needed)
- saip_builder_bringup(sps, sp, pss);
- else
- /*
- * power-off is delayed, so we just set the stay flag...
- * but let's cancel any pending power-off
- */
- lws_sul_cancel(&sp->sul_delay_off);
+ pc->manual_stay = (char)stay_on;
+ saip_notify_server_stay_state(builder_name, stay_on | pc->needed);
- /* Find the first (usually only) configured sai-server connection */
- if (!power.sai_server_owner.head) {
- lwsl_warn("%s: No sai-server configured to notify\n", __func__);
- return;
+ /* Trigger state re-eval */
+ saip_pcon_start_check();
+
+ if (stay_on | pc->needed) {
+ /* Ensure it's on immediately if needed */
+ saip_builder_bringup(sps, pc);
+ } else {
+ /* Cancel pending off */
+ lws_sul_cancel(&pc->sul_delay_off);
}
saip_queue_stay_info(sps);
@@ -196,12 +221,12 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
{
local_srv_t *g = (local_srv_t *)userobj;
char *path = NULL, pn[128];
- saip_server_plat_t *sp;
+ saip_pcon_t *pc;
saip_server_t *sps;
int apo = 0;
size_t len;
- lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name((int)state));
+ // lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name((int)state));
switch ((int)state) {
case LWSSSCS_CREATING:
@@ -210,7 +235,7 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
case LWSSSCS_SERVER_TXN:
lws_ss_get_metadata(lws_ss_from_user(g), "path", (const void **)&path, &len);
- // lwsl_ss_user(lws_ss_from_user(g), "LWSSSCS_SERVER_TXN path '%.*s' (%d)", (int)len, path, (int)len);
+ lwsl_ss_user(lws_ss_from_user(g), "LWSSSCS_SERVER_TXN path '%.*s' (%d)", (int)len, path, (int)len);
/*
* path is containing a string like "/power-off/b32"
@@ -231,21 +256,17 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
g->pos = 0;
if (len == 1 && path[0] == '/') {
- /* print controllable platforms */
+ /* print controllable PCONs */
g->size = 0;
- lws_start_foreach_dll(struct lws_dll2 *, px, power.sai_server_owner.head) {
- saip_server_t *s = lws_container_of(px, saip_server_t, list);
+ lws_start_foreach_dll(struct lws_dll2 *, px, power.sai_pcon_owner.head) {
+ saip_pcon_t *pc = lws_container_of(px, saip_pcon_t, list);
- lws_start_foreach_dll(struct lws_dll2 *, px1, s->sai_plat_owner.head) {
- saip_server_plat_t *sp = lws_container_of(px1, saip_server_plat_t, list);
+ if (g->size)
+ g->payload[g->size++] = ',';
+ g->size = g->size + (size_t)lws_snprintf(g->payload + g->size, sizeof(g->payload) - g->size - 3, "%s", pc->name);
- if (g->size)
- g->payload[g->size++] = ',';
- g->size = g->size + (size_t)lws_snprintf(g->payload + g->size, sizeof(g->payload) - g->size - 3, "%s", sp->host);
-
- } lws_end_foreach_dll(px1);
} lws_end_foreach_dll(px);
g->payload[g->size] = '\0';
@@ -255,48 +276,49 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
if (len > 6 && !strncmp(path, "/stay/", 6)) {
lws_strnncpy(pn, &path[6], len - 6, sizeof(pn));
- sp = find_platform(&power, pn);
+ /* Assuming 'pn' is a builder name for legacy compatibility */
+ pc = find_pcon_by_builder_name(&power, pn);
- if (sp)
+ if (pc)
g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "%c", '0' + (sp->stay | sp->needed));
+ "%c", '0' + (pc->manual_stay | pc->needed));
else
g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "unknown host %s", pn);
+ "unknown builder %s", pn);
goto bail;
}
if (len > 10 && !strncmp(path, "/power-on/", 10)) {
lws_strnncpy(pn, &path[10], len - 10, sizeof(pn));
- sp = find_platform(&power, pn);
- if (!sp) {
+ /* Assume builder name */
+ pc = find_pcon_by_builder_name(&power, pn);
+ if (!pc) {
g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "Unable to find host %s", pn);
+ "Unable to find PCON for %s", pn);
goto bail;
}
- if (sp->power_on_mac) {
- saip_notify_server_power_state(sp->host, 1, 0);
+
+ saip_notify_server_power_state(pc->name, 1, 0);
+
+ if (pc->mac) {
if (write(lws_spawn_get_fd_stdxxx(lsp_wol, 0),
- sp->power_on_mac, strlen(sp->power_on_mac)) !=
- (ssize_t)strlen(sp->power_on_mac))
+ pc->mac, strlen(pc->mac)) !=
+ (ssize_t)strlen(pc->mac))
g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
"Write to resume %s failed %d", pn, errno);
else
g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
"Resumed %s with stay", pn);
- sp->stay = 1;
+ pc->manual_stay = 1;
goto bail;
}
- if (sp->pcon_list.owner) {
- saip_pcon_t *pc = lws_container_of(sp->pcon_list.owner,
- saip_pcon_t,
- controlled_plats_owner);
+ if (pc->ss_tasmota_on) {
if (lws_ss_client_connect(pc->ss_tasmota_on)) {
lwsl_ss_err(pc->ss_tasmota_on, "failed to connect tasmota ON secure stream");
g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "power-on ss failed create %s", sp->host);
+ "power-on ss failed create %s", pc->name);
goto bail;
}
} else {
@@ -305,10 +327,10 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
goto bail;
}
- lwsl_warn("%s: powered on host %s\n", __func__, sp->host);
+ lwsl_warn("%s: powered on %s\n", __func__, pc->name);
- sp->stay = 1; /* so builder can understand it's manual */
- saip_notify_server_power_state(sp->host, 1, 0);
+ pc->manual_stay = 1; /* so builder can understand it's manual */
+ saip_notify_server_power_state(pc->name, 1, 0);
sps = lws_container_of(power.sai_server_owner.head,
saip_server_t, list);
@@ -316,7 +338,7 @@ local_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
saip_queue_stay_info(sps);
g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "Manually powered on %s", sp->host);
+ "Manually powered on %s", pc->name);
goto bail;
}
@@ -343,8 +365,8 @@ power_off:
g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
"Unable to find host %s", pn);
- sp = find_platform(&power, pn);
- if (sp) {
+ pc = find_pcon_by_builder_name(&power, pn);
+ if (pc) {
if (apo) {
char needs[128];
@@ -353,8 +375,16 @@ power_off:
* Since it's not a manual request,
* we should deny it if any deps still need us
*/
+ /* Deps logic needs to check PARENT or DEPENDENTS?
+ Original logic checked: "if any deps still need us" (us = dependency_list).
+ Current PCON struct has 'parent' and 'depends_on'.
+ If *we* are needed by someone else... we don't have a list of dependents easily.
+ But 'needed' flag should be set if we are needed.
+ Let's trust 'pc->needed' which we update in rx.
+ */
needs[0] = '\0';
+ /*
lws_start_foreach_dll(struct lws_dll2 *, px1,
sp->dependencies_owner.head) {
saip_server_plat_t *sp1 = lws_container_of(px1,
@@ -366,12 +396,13 @@ power_off:
"%s ", sp1->name);
} lws_end_foreach_dll(px1);
+ */
- if (needs[0] || sp->needed) {
+ if (needs[0] || pc->needed) {
g->size = (size_t)lws_snprintf(g->payload,
sizeof(g->payload),
"NAK: %s needed: %d, deps needed: '%s'",
- pn, sp->needed, needs);
+ pn, pc->needed, needs);
goto bail;
}
}
@@ -380,20 +411,20 @@ power_off:
* OK this is it, schedule it to happen
*/
lws_sul_schedule(lws_ss_cx_from_user(g), 0,
- &sp->sul_delay_off,
+ &pc->sul_delay_off,
saip_sul_action_power_off,
SAI_POWERDOWN_HOLDOFF_US);
- lwsl_warn("%s: scheduled powering off host %s in %ds\n",
- __func__, sp->host,
+ lwsl_warn("%s: scheduled powering off pcon %s in %ds\n",
+ __func__, pc->name,
(int)(SAI_POWERDOWN_HOLDOFF_US / LWS_USEC_PER_SEC));
g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload),
- "ACK: Scheduled powering off host %s in %ds",
- sp->host,
+ "ACK: Scheduled powering off pcon %s in %ds",
+ pc->name,
(int)(SAI_POWERDOWN_HOLDOFF_US / LWS_USEC_PER_SEC));
- sp->stay = 0; /* reset any manual power up */
+ pc->manual_stay = 0; /* reset any manual power up */
}
bail:
diff --git a/src/power/p-private.h b/src/power/p-private.h
index 1d92458..fc94cc5 100644
--- a/src/power/p-private.h
+++ b/src/power/p-private.h
@@ -59,14 +59,23 @@ typedef struct tasmota_parse {
uint8_t s;
} tasmota_parse_t;
+struct saip_pcon;
+
typedef struct saip_pcon {
struct lws_dll2 list; /* sai_power.sai_pcon_owner */
- lws_dll2_owner_t controlled_plats_owner; /* saip_server_plat_t */
+ /* List of builders registered to this PCON (dynamic) */
+ lws_dll2_owner_t registered_builders_owner; /* saip_builder_t */
+
+ lws_sorted_usec_list_t sul_delay_off;
+
+ struct saip_pcon *parent; /* if we depend on another pcon */
+ const char *depends_on; /* name from config */
const char *name;
const char *type;
const char *url;
+ const char *mac; /* For WOL */
char url_on[128];
char url_off[128];
@@ -77,37 +86,28 @@ typedef struct saip_pcon {
struct lws_ss_handle *ss_tasmota_monitor;
char on;
+ char manual_stay; /* user asked to keep this PCON on via UI */
+ char needed; /* transiently set by deps analysis */
} saip_pcon_t;
-struct saip_ws_pss;
-
-typedef struct saip_server_plat {
- lws_dll2_t list;
- lws_dll2_t dependencies_list;
- lws_dll2_t pcon_list; /* saip_pcon->controlled_plats_owner */
-
- lws_dll2_owner_t dependencies_owner; /* saip_server_plat_t->dependencies_list */
+/* Represents a builder connected to us */
+typedef struct saip_builder {
+ struct lws_dll2 list; /* in saip_pcon.registered_builders_owner */
- lws_sorted_usec_list_t sul_delay_off;
+ char name[64];
- const char *name;
- const char *host;
- const char *depends; /* depended-on plat must stay powered if we need power */
- const char *power_on_type;
- const char *power_on_url;
- const char *power_on_mac;
- const char *power_off_type;
- const char *power_off_url;
+ /* The websocket wsi for this builder connection (if connected) */
+ struct lws *wsi;
+} saip_builder_t;
- char stay;
- char needed;
-} saip_server_plat_t;
+struct saip_ws_pss;
typedef struct saip_server {
lws_dll2_t list;
- lws_dll2_owner_t sai_plat_owner; /* list of platforms we offer */
+ /* Removed sai_plat_owner as sai-power no longer manages platforms directly */
+ /* lws_dll2_owner_t sai_plat_owner; */
struct lws_ss_handle *ss;
@@ -129,6 +129,7 @@ struct sai_power {
struct lws_vhost *vhost;
lws_sorted_usec_list_t sul_idle;
+ lws_sorted_usec_list_t sul_pcon_check; /* periodic check for cold start */
const char *power_off;
@@ -140,15 +141,11 @@ struct sai_power {
const char *port; /* port we listen on */
};
-saip_server_plat_t *
-find_platform(struct sai_power *pwr, const char *host);
-
struct jpargs {
struct sai_power *power;
saip_server_t *sai_server;
- saip_server_plat_t *sai_server_plat;
saip_pcon_t *sai_pcon;
sai_plat_server_ref_t *mref;
@@ -166,7 +163,6 @@ LWS_SS_USER_TYPEDEF
} saip_server_link_t;
-
extern struct sai_power power;
extern const lws_ss_info_t ssi_saip_server_link_t, ssi_saip_smartplug_t;
extern const struct lws_protocols protocol_com_warmcat_sai;
@@ -176,10 +172,10 @@ saip_config_global(struct sai_power *power, const char *d);
extern int saip_config(struct sai_power *power, const char *d);
extern void saip_config_destroy(struct sai_power *power);
extern void
-saip_notify_server_power_state(const char *plat_name, int up, int down);
+saip_notify_server_power_state(const char *pcon_name, int up, int down);
void
-saip_set_stay(const char *builder_name, int stay_on);
+saip_set_stay(const char *pcon_name, int stay_on);
int
saip_queue_stay_info(saip_server_t *sps);
saip_pcon_t *
@@ -187,8 +183,20 @@ saip_pcon_by_name(struct sai_power *power, const char *name);
int
saip_parse_tasmota_status(tasmota_parse_t *tp);
-int
-saip_builder_bringup(saip_server_t *sps, saip_server_plat_t *sp,
- saip_server_link_t *pss);
+
void
saip_ss_create_tasmota(void);
+
+void
+saip_pcon_start_check(void);
+
+/* Handler for builder WS connections */
+int
+callback_builder(struct lws *wsi, enum lws_callback_reasons reason,
+ void *user, void *in, size_t len);
+
+saip_pcon_t *
+find_pcon_by_builder_name(struct sai_power *pwr, const char *builder_name);
+
+void
+saip_builder_bringup(saip_server_t *sps, saip_pcon_t *pc);
diff --git a/src/power/p-sai.c b/src/power/p-sai.c
index e7932b9..7e0183c 100644
--- a/src/power/p-sai.c
+++ b/src/power/p-sai.c
@@ -130,7 +130,8 @@ static const char * const default_ss_policy =
"{\"local\": {"
"\"server\":" "true,"
"\"port\":" "3333,"
- "\"protocol\":" "\"h1\","
+ "\"protocol\":" "\"ws\"," /* Changed to ws */
+ "\"ws_subprotocol\":" "\"com-warmcat-sai-builder\","
"\"tls\":" "false,"
"\"metadata\": ["
"{\"path\": \"\"},"
@@ -179,11 +180,180 @@ callback_std(struct lws *wsi, enum lws_callback_reasons reason, void *user,
static const struct lws_protocols protocol_std =
{ "protocol_std", callback_std, 0, 0 };
+/*
+ * Builder connection handling
+ */
+
+int
+callback_builder(struct lws *wsi, enum lws_callback_reasons reason,
+ void *user, void *in, size_t len)
+{
+ struct lejp_ctx ctx;
+ lws_struct_args_t a;
+ saip_builder_t *b, **pb = (saip_builder_t **)user;
+ saip_pcon_t *pc;
+
+ switch (reason) {
+ case LWS_CALLBACK_ESTABLISHED:
+ lwsl_user("%s: builder connected\n", __func__);
+ *pb = NULL; /* Ensure user data is clean */
+ break;
+
+ case LWS_CALLBACK_RECEIVE:
+ // lwsl_hexdump_notice(in, len);
+ memset(&a, 0, sizeof(a));
+ a.map_st[0] = lsm_schema_builder_registration;
+ a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_builder_registration);
+ a.ac_block_size = 2048;
+
+ lws_struct_json_init_parse(&ctx, NULL, &a);
+ if (lejp_parse(&ctx, (uint8_t *)in, (int)len) < 0 || !a.dest) {
+ lwsl_warn("%s: JSON decode failed\n", __func__);
+ lwsac_free(&a.ac);
+ return -1;
+ }
+
+ if (a.top_schema_index == 0) {
+ sai_builder_registration_t *r = (sai_builder_registration_t *)a.dest;
+
+ lwsl_notice("%s: Registered builder '%s' on pcon '%s'\n",
+ __func__, r->builder_name, r->power_controller_name);
+
+ /* Find the PCON */
+ pc = saip_pcon_by_name(&power, r->power_controller_name);
+ if (pc) {
+ /* Create/Update builder entry using malloc */
+ b = malloc(sizeof(*b));
+ if (b) {
+ memset(b, 0, sizeof(*b));
+ lws_strncpy(b->name, r->builder_name, sizeof(b->name));
+ b->wsi = wsi;
+
+ /* Store pointer in user data for cleanup */
+ *pb = b;
+
+ /* Add to list */
+ lws_dll2_add_tail(&b->list, &pc->registered_builders_owner);
+ } else {
+ lwsl_err("%s: OOM allocating builder\n", __func__);
+ }
+
+ /* Trigger a check since we have a new builder (it's alive!) */
+ saip_pcon_start_check();
+
+ /* Send update to sai-server? */
+ saip_queue_stay_info(lws_container_of(power.sai_server_owner.head, saip_server_t, list));
+
+ } else {
+ lwsl_err("%s: Unknown PCON '%s' for builder '%s'\n",
+ __func__, r->power_controller_name, r->builder_name);
+ }
+
+ }
+ lwsac_free(&a.ac);
+ break;
+
+ case LWS_CALLBACK_CLOSED:
+ lwsl_user("%s: builder disconnected\n", __func__);
+ b = *pb;
+ if (b) {
+ /* Remove from list */
+ lws_dll2_remove(&b->list);
+ /* Free memory */
+ free(b);
+ *pb = NULL;
+
+ /* Update state */
+ saip_pcon_start_check();
+ }
+ break;
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+static const struct lws_protocols protocol_builder =
+ { "com-warmcat-sai-builder", callback_builder, sizeof(saip_builder_t *), 0 };
+
static const struct lws_protocols *pprotocols[] = {
&protocol_std,
+ &protocol_builder,
NULL
};
+/*
+ * Check PCON state logic
+ * If a PCON has NO registered builders, we default it to ON (Cold Start).
+ * If a PCON has registered builders, we defer to the "Stay" logic (which comes from sai-server).
+ *
+ * However, there is a nuance: If we just started up, we have 0 builders. We should turn everything ON.
+ * As builders connect, they register.
+ */
+
+void
+sul_pcon_check_cb(lws_sorted_usec_list_t *sul)
+{
+ /* Iterate all PCONs */
+ lws_start_foreach_dll(struct lws_dll2 *, p, power.sai_pcon_owner.head) {
+ saip_pcon_t *pc = lws_container_of(p, saip_pcon_t, list);
+ int target_on = 0;
+
+ /* Rule 1: No builders registered -> Turn ON (Cold start / Discovery) */
+ if (!pc->registered_builders_owner.count) {
+ target_on = 1;
+ lwsl_info("%s: PCON %s has 0 builders -> Force ON\n", __func__, pc->name);
+ }
+ /* Rule 2: Manual Stay -> Turn ON */
+ else if (pc->manual_stay) {
+ target_on = 1;
+ lwsl_info("%s: PCON %s has manual stay -> Force ON\n", __func__, pc->name);
+ }
+ /* Rule 3: Otherwise, rely on sai-server to tell us via 'stay' messages if it needs to be on.
+ * Wait... sai-server tells us which *builders* need to stay on.
+ * We need to map builder stay -> PCON stay.
+ * But sai-power receives 'stay' messages for builders.
+ */
+ else {
+ /* Check if any registered builder needs to stay on */
+ /* The 'stay' flag on the PCON struct isn't quite right,
+ we need to check the builders we know about.
+ Actually, sai-server tells sai-power: "Builder X needs to stay".
+ sai-power should look up Builder X, find its PCON, and mark the PCON as needed.
+ */
+ /* For now, let's assume if we aren't forcing it on, we let it be managed by the existing logic
+ * which we need to adapt.
+ */
+ }
+
+ /*
+ * Existing logic in p-smartplug.c or similar likely handles the actual HTTP switching
+ * based on a state flag. We need to make sure we set that flag.
+ * The 'saip_pcon_t' has an 'on' member.
+ */
+
+ /* If we decide it should be ON, trigger it */
+ if (target_on && !pc->on) {
+ /* This logic needs to hook into the actual switching code */
+ /* For now, just logging intent */
+ // pc->on = 1;
+ // saip_switch(pc, 1);
+ }
+
+ } lws_end_foreach_dll(p);
+
+ /* Schedule next check */
+ lws_sul_schedule(power.context, 0, &power.sul_pcon_check, sul_pcon_check_cb, 5 * LWS_US_PER_SEC);
+}
+
+void
+saip_pcon_start_check(void)
+{
+ /* Trigger immediate check */
+ lws_sul_schedule(power.context, 0, &power.sul_pcon_check, sul_pcon_check_cb, 1);
+}
+
static int
app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
int current, int target)
@@ -219,7 +389,8 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
lwsl_user("%s: OPERATIONAL: server url %p %s\n", __func__, sps, sps->url);
- if (lws_ss_create(cx, 0, &ssi_saip_server_link_t, sps,
+ if (sps->url &&
+ lws_ss_create(cx, 0, &ssi_saip_server_link_t, sps,
&sps->ss, NULL, NULL)) {
lwsl_err("%s: failed to create secure stream\n",
__func__);
@@ -228,6 +399,8 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
} lws_end_foreach_dll_safe(mp, mp1);
+ /* Start PCON monitoring */
+ saip_pcon_start_check();
break;
}
diff --git a/src/power/p-ws-server.c b/src/power/p-ws-server.c
index 0b8b345..05ed85e 100644
--- a/src/power/p-ws-server.c
+++ b/src/power/p-ws-server.c
@@ -35,7 +35,7 @@ static const lws_struct_map_t lsm_schema_power_state[] = {
};
void
-saip_notify_server_power_state(const char *plat_name, int up, int down)
+saip_notify_server_power_state(const char *pcon_name, int up, int down)
{
saip_server_link_t *m;
sai_power_state_t ps;
@@ -56,7 +56,7 @@ saip_notify_server_power_state(const char *plat_name, int up, int down)
memset(&ps, 0, sizeof(ps));
- lws_strncpy(ps.host, plat_name, sizeof(ps.host));
+ lws_strncpy(ps.host, pcon_name, sizeof(ps.host));
ps.powering_up = (char)up;
ps.powering_down = (char)down;
@@ -74,24 +74,13 @@ saip_queue_stay_info(saip_server_t *sps)
saip_server_link_t *m;
int r;
- lwsl_ss_notice(sps->ss, "@@@@@@@@@@@@@@ sai-power CONNECTED to server");
+ /* lwsl_ss_notice(sps->ss, "@@@@@@@@@@@@@@ sai-power CONNECTED to server"); */
m = (saip_server_link_t *)lws_ss_to_user_object(sps->ss);
memset(&pmb, 0, sizeof(pmb));
- lws_start_foreach_dll(struct lws_dll2 *, p, sps->sai_plat_owner.head) {
- saip_server_plat_t *sp = lws_container_of(p,
- saip_server_plat_t, list);
- sai_power_managed_builder_t *b = lwsac_use_zero(&ac, sizeof(*b), 2048);
-
- if (b) {
- lws_strncpy(b->name, sp->host, sizeof(b->name));
- b->stay_on = sp->stay;
-
- lws_dll2_add_tail(&b->list, &pmb.builders);
- }
- } lws_end_foreach_dll(p);
+ /* Send the PCON configuration and connected builders */
lws_start_foreach_dll(struct lws_dll2 *, p, power.sai_pcon_owner.head) {
saip_pcon_t *pc = lws_container_of(p, saip_pcon_t, list);
@@ -100,20 +89,23 @@ 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;
+ if (pc->depends_on)
+ lws_strncpy(pc1->depends_on, pc->depends_on, sizeof(pc1->depends_on));
+ if (pc->type)
+ lws_strncpy(pc1->type, pc->type, sizeof(pc1->type));
lws_dll2_add_tail(&pc1->list, &pmb.power_controllers);
+ /* Attach registered builders */
lws_start_foreach_dll(struct lws_dll2 *, p1,
- pc->controlled_plats_owner.head) {
- saip_server_plat_t *sp = lws_container_of(p1,
- saip_server_plat_t, pcon_list);
+ pc->registered_builders_owner.head) {
+ saip_builder_t *sb = lws_container_of(p1,
+ saip_builder_t, list);
sai_controlled_builder_t *c = lwsac_use_zero(&ac,
sizeof(*c), 2048);
if (c) {
- if (sp->host)
- lws_strncpy(c->name, sp->host,
- sizeof(c->name));
+ lws_strncpy(c->name, sb->name, sizeof(c->name));
lws_dll2_add_tail(&c->list,
&pc1->controlled_builders_owner);
@@ -122,6 +114,11 @@ saip_queue_stay_info(saip_server_t *sps)
}
} lws_end_foreach_dll(p);
+ /* The 'builders' list in pmb was for "power managed builders" (legacy).
+ * We now put builders *inside* power controllers.
+ * So we leave pmb.builders empty.
+ */
+
r = sai_ss_serialize_queue_helper(sps->ss, &m->bl_pwr_to_srv,
lsm_schema_power_managed_builders,
LWS_ARRAY_SIZE(lsm_schema_power_managed_builders),
@@ -131,44 +128,16 @@ saip_queue_stay_info(saip_server_t *sps)
return r;
}
-int
-saip_builder_bringup(saip_server_t *sps, saip_server_plat_t *sp,
- saip_server_link_t *pss)
-{
- saip_notify_server_power_state(sp->name, 1, 0);
-
- if (sp->power_on_type && !strcmp(sp->power_on_type, "wol")) {
- lwsl_notice("%s: triggering WOL\n", __func__);
- write(lws_spawn_get_fd_stdxxx(lsp_wol, 0),
- sp->power_on_mac, strlen(sp->power_on_mac));
- }
-
- if (sp->pcon_list.owner) {
- saip_pcon_t *pc = lws_container_of(sp->pcon_list.owner,
- saip_pcon_t,
- controlled_plats_owner);
-
- lwsl_ss_notice(pc->ss_tasmota_on, "starting tasmota");
- if (lws_ss_client_connect(pc->ss_tasmota_on))
- lwsl_ss_err(pc->ss_tasmota_on, "failed to connect tasmota ON secure stream");
- }
-
- return saip_queue_stay_info(sps);
-}
-
static lws_ss_state_return_t
saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
saip_server_link_t *pss = (saip_server_link_t *)userobj;
saip_server_t *sps = (saip_server_t *)lws_ss_opaque_from_user(pss);
- const char *p = (const char *)buf, *end = (const char *)buf + len;
- char plat[128], benched[4096];
- size_t n, bp = 0;
lws_struct_args_t a;
struct lejp_ctx ctx;
lwsl_notice("%s: len %d, flags: %d (saip_server_t %p)\n", __func__, (int)len, flags, (void *)sps);
- lwsl_hexdump_notice(buf, len);
+ /* lwsl_hexdump_notice(buf, len); */
memset(&a, 0, sizeof(a));
a.map_st[0] = lsm_schema_stay;
@@ -183,112 +152,44 @@ saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
lwsl_warn("%s: received stay %s: %d\n", __func__, stay->builder_name, stay->stay_on);
- saip_set_stay(stay->builder_name, stay->stay_on);
- lwsac_free(&a.ac);
- return 0;
- }
- lwsac_free(&a.ac);
-
- /* starting position is that no server-plat is needed */
-
- lws_start_foreach_dll(struct lws_dll2 *, px, sps->sai_plat_owner.head) {
- saip_server_plat_t *sp = lws_container_of(px, saip_server_plat_t, list);
- sp->needed = 0;
- } lws_end_foreach_dll(px);
-
- fprintf(stderr, "|||||||||||||||||||||||||||||||| Server says needed: '%.*s'\n", (int)len, buf);
-
- while (p < end) {
- n = 0;
- while (p < end && *p != ',')
- if (n < sizeof(plat) - 1)
- plat[n++] = *p++;
-
- plat[n] = '\0';
- if (p < end && *p == ',')
- p++;
-
/*
- * Does this server list this platform as having startable or ongoing
- * tasks?
+ * We received a stay request for a builder.
+ * We need to find which PCON controls this builder and update its manual_stay.
+ * But wait, 'sai_stay_t' is typically per-builder.
+ * We should map this back to the PCON.
*/
- lws_start_foreach_dll(struct lws_dll2 *, px, sps->sai_plat_owner.head) {
- saip_server_plat_t *sp = lws_container_of(px, saip_server_plat_t, list);
-
- /*
- * How about any dependency listed?
- */
-
- lws_start_foreach_dll(struct lws_dll2 *, px1, sp->dependencies_owner.head) {
- saip_server_plat_t *sp1 = lws_container_of(px1, saip_server_plat_t, dependencies_list);
-
- lwsl_notice("%s: setting %s as needed dep\n", __func__, sp1->name);
- sp1->needed = 2;
- saip_set_stay(sp1->name, sp1->stay);
-
- } lws_end_foreach_dll(px1);
-
- /*
- * Directly listed as needed?
- */
-
- if (!strcmp(sp->name, plat))
- sp->needed |= 1;
+ lws_start_foreach_dll(struct lws_dll2 *, p, power.sai_pcon_owner.head) {
+ saip_pcon_t *pc = lws_container_of(p, saip_pcon_t, list);
+ lws_start_foreach_dll(struct lws_dll2 *, b_node, pc->registered_builders_owner.head) {
+ saip_builder_t *sb = lws_container_of(b_node, saip_builder_t, list);
+ if (!strcmp(sb->name, stay->builder_name)) {
+ lwsl_notice("%s: Mapping stay for builder '%s' to PCON '%s'\n",
+ __func__, sb->name, pc->name);
+ /* Update PCON stay state */
+ pc->manual_stay = stay->stay_on;
+ saip_pcon_start_check();
+ goto found;
+ }
+ } lws_end_foreach_dll(b_node);
+ } lws_end_foreach_dll(p);
- } lws_end_foreach_dll(px);
+found:
+ lwsac_free(&a.ac);
+ return 0;
}
+ lwsac_free(&a.ac);
/*
- * Cascade dependencies up the platforms
- */
-
- lws_start_foreach_dll(struct lws_dll2 *, px, sps->sai_plat_owner.head) {
- saip_server_plat_t *sp = lws_container_of(px, saip_server_plat_t, list);
-
- if (sp->needed) {
- lws_start_foreach_dll(struct lws_dll2 *, py, sp->dependencies_owner.head) {
- saip_server_plat_t *spd = lws_container_of(py,
- saip_server_plat_t, dependencies_list);
-
- spd->needed |= 2;
-
- } lws_end_foreach_dll(py);
- }
- } lws_end_foreach_dll(px);
-
- /*
- * Bringup any directly needed or needed by dependency builders
+ * The old logic parsed comma-separated platform names to determine needed state.
+ * We are moving away from that. Sai-server should explicitly request power state
+ * or we should rely on the builder being "needed" implies PCON on.
+ * Actually, the requirement said: "You can no longer ask that a platform stays on, instead, you ask that the power controller stays on"
+ * So sai-server might send stay requests for PCONs directly if we update the UI.
+ * But for now, sai-server logic (s-power.c) sends stay for *builders*.
+ * So the mapping logic above is correct for transition.
*/
- lws_start_foreach_dll(struct lws_dll2 *, px, sps->sai_plat_owner.head) {
- saip_server_plat_t *sp = lws_container_of(px, saip_server_plat_t, list);
-
- if (sp->needed) {
- lwsl_notice("%s: Needed builders: %s\n", __func__, sp->name);
-
- /*
- * Server said this platform or at least one dependency
- * has pending jobs. sai-power config says this builder
- * can do jobs on that platform. Let's make sure it
- * is powered on.
- */
-
- saip_builder_bringup(sps, sp, pss);
-
- } else {
- bp += (size_t)lws_snprintf(&benched[bp], sizeof(benched) - bp - 1,
- "%s%s", !bp ? "" : ", ", sp->name);
- benched[sizeof(benched) - 1] = '\0';
- }
-
- } lws_end_foreach_dll(px);
-
- if (bp)
- lwsl_notice("%s: Benched builders: %s\n", __func__, benched);
-
- (void)sps;
-
return 0;
}
diff --git a/src/server/s-central.c b/src/server/s-central.c
index 15a499e..12a1846 100644
--- a/src/server/s-central.c
+++ b/src/server/s-central.c
@@ -147,6 +147,40 @@ sais_central_clean_abandoned(struct vhd *vhd)
lwsac_free(&ac);
}
+/* Check if we need to create new tables or add columns */
+static void
+sais_ensure_tables(struct vhd *vhd)
+{
+ char *err = NULL;
+
+ /* We already added power_controllers and pcon_builders,
+ * but we need to make sure 'builders' table has 'pcon' column.
+ */
+
+ /* Try adding the column, ignore error if it exists */
+ sqlite3_exec(vhd->server.pdb, "ALTER TABLE builders ADD COLUMN pcon varchar(64);", 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 ("
+ " name varchar(64) primary key,"
+ " type varchar(32),"
+ " url varchar(128),"
+ " depends_on varchar(64),"
+ " state integer"
+ ");", "create pcon table");
+
+ sai_sqlite3_statement(vhd->server.pdb,
+ "CREATE TABLE IF NOT EXISTS pcon_builders ("
+ " pcon_name varchar(64),"
+ " builder_name varchar(64)"
+ ");", "create pcon_builders table");
+
+}
+
void
sais_central_cb(lws_sorted_usec_list_t *sul)
{
@@ -162,6 +196,10 @@ sais_central_cb(lws_sorted_usec_list_t *sul)
sais_central_clean_abandoned(vhd);
+ /* Also ensure tables exist periodically/on startup */
+ if (!vhd->last_check_abandoned_tasks)
+ sais_ensure_tables(vhd);
+
vhd->last_check_abandoned_tasks = lws_now_usecs();
}
diff --git a/src/server/s-helpers.c b/src/server/s-helpers.c
index ee76cfc..0cb85d7 100644
--- a/src/server/s-helpers.c
+++ b/src/server/s-helpers.c
@@ -111,6 +111,29 @@ sai_destroy_resource_wellknown(struct lws_dll2 *d, void *user)
return 0;
}
+int
+sai_pcon_destroy_cb(struct lws_dll2 *d, void *user)
+{
+ sai_power_controller_t *pc = lws_container_of(d, sai_power_controller_t, list);
+
+ lws_dll2_remove(d);
+
+ /* Also need to clear the child lists */
+ lws_dll2_foreach_safe(&pc->controlled_builders_owner, NULL, sai_detach_builder);
+
+ /* We don't free pc because it's usually in an lwsac, but if it's from db it might be malloced.
+ * In s-power.c usage, it's lwsac.
+ * Wait, s-sai.c uses 'server->pdb' which is sqlite.
+ * The in-memory structure 'sai_power_managed_builders_t' is what we are likely destroying here if we are cleaning up ephemeral state.
+ *
+ * However, 'server' struct in 's-private.h' doesn't seem to have a list of live PCONs.
+ * We should check s-private.h to see if we added one.
+ * If not, we might be relying on the database + transient messages.
+ */
+
+ return 0;
+}
+
void
sais_server_destroy(struct vhd *vhd, sais_t *server)
{
@@ -126,4 +149,3 @@ sais_server_destroy(struct vhd *vhd, sais_t *server)
lws_dll2_foreach_safe(&server->resource_wellknown_owner, NULL,
sai_destroy_resource_wellknown);
}
-
diff --git a/src/server/s-power.c b/src/server/s-power.c
index d61d4dd..ee7cbf0 100644
--- a/src/server/s-power.c
+++ b/src/server/s-power.c
@@ -87,43 +87,55 @@ sais_power_rx(struct vhd *vhd, struct pss *pss, uint8_t *buf,
case 1: {
sai_power_managed_builders_t *pmb = (sai_power_managed_builders_t *)a.dest;
- uint64_t bf_set = 0;
-
- lws_start_foreach_dll(struct lws_dll2 *, p, pmb->builders.head) {
- sai_power_managed_builder_t *b = lws_container_of(p,
- sai_power_managed_builder_t, list);
- char q[256];
- int shi = 0;
-
- lwsl_notice("%s: Marking builder %s as power-managed\n",
- __func__, b->name);
- lws_snprintf(q, sizeof(q),
- "UPDATE builders SET power_managed=1 WHERE name = '%s' OR name LIKE '%s.%%'",
- b->name, b->name);
+ char q[256];
- if (sai_sqlite3_statement(vhd->server.pdb, q, "set power_managed"))
- lwsl_err("%s: Failed to mark builder %s as power-managed\n",
- __func__, b->name);
+ /*
+ * We received the PCON topology and registered builders from sai-power.
+ *
+ * 1. Store PCONs in DB.
+ * 2. Update builders' PCON association (if we have a table or column for it).
+ */
- lws_start_foreach_dll(struct lws_dll2 *, p2,
- vhd->server.builder_owner.head) {
-
- sai_plat_t *sp = lws_container_of(p2, sai_plat_t, sai_plat_list);
- const char *dot = strchr(sp->name, '.');
+ sai_sqlite3_statement(vhd->server.pdb, "BEGIN TRANSACTION", "txn begin");
- // lwsl_notice("%s: builder entry: %s\n", __func__, cb->name);
+ /* Clear old PCONs? Or Upsert?
+ * We probably want to replace since this is authoritative from sai-power.
+ * But maybe just clear everything first?
+ */
+ sai_sqlite3_statement(vhd->server.pdb, "DELETE FROM power_controllers", "delete pcons");
+ sai_sqlite3_statement(vhd->server.pdb, "DELETE FROM pcon_builders", "delete pcon_builders");
- if (dot && !(bf_set & (1 << shi)) && strlen(b->name) <= (size_t)(dot - sp->name) &&
- !strncmp(sp->name + (dot - sp->name) - strlen(b->name), b->name, strlen(b->name))) {
- lwsl_notice("%s: ++++++++++++ Setting %s .stay_on=%d\n", __func__, sp->name, b->stay_on);
- sp->stay_on = b->stay_on;
- bf_set |= (1 << shi);
- }
- shi++;
- } lws_end_foreach_dll(p2);
+ lws_start_foreach_dll(struct lws_dll2 *, p, pmb->power_controllers.head) {
+ sai_power_controller_t *pc = lws_container_of(p, sai_power_controller_t, list);
+
+ /* Insert PCON */
+ lws_snprintf(q, sizeof(q),
+ "INSERT INTO power_controllers (name, type, url, depends_on, state) VALUES ('%s', '%s', '', '%s', %d)",
+ pc->name, pc->type, pc->depends_on, pc->on);
+ sai_sqlite3_statement(vhd->server.pdb, q, "insert pcon");
+
+ /* Insert Controlled Builders */
+ lws_start_foreach_dll(struct lws_dll2 *, pb, pc->controlled_builders_owner.head) {
+ sai_controlled_builder_t *cb = lws_container_of(pb, sai_controlled_builder_t, list);
+ lws_snprintf(q, sizeof(q),
+ "INSERT INTO pcon_builders (pcon_name, builder_name) VALUES ('%s', '%s')",
+ pc->name, cb->name);
+ lwsl_notice("%s: Inserting pcon_builder: pcon='%s', builder='%s'\n", __func__, pc->name, cb->name);
+ sai_sqlite3_statement(vhd->server.pdb, q, "insert pcon_builder");
+
+ /* Update builders table with pcon name */
+ /* Note: builders table key is 'name'. */
+ lws_snprintf(q, sizeof(q),
+ "UPDATE builders SET pcon = '%s' WHERE name = '%s' OR name LIKE '%s.%%'",
+ pc->name, cb->name, cb->name);
+ sai_sqlite3_statement(vhd->server.pdb, q, "update builder pcon");
+
+ } lws_end_foreach_dll(pb);
} lws_end_foreach_dll(p);
+ sai_sqlite3_statement(vhd->server.pdb, "COMMIT", "txn commit");
+
sais_list_builders(vhd);
break;
diff --git a/src/server/s-private.h b/src/server/s-private.h
index 757e939..779b12e 100644
--- a/src/server/s-private.h
+++ b/src/server/s-private.h
@@ -55,6 +55,9 @@ typedef struct sai_platm {
/* the list of well-known, configured resources */
lws_dll2_owner_t resource_wellknown_owner; /* sai_resource_wellknown_t */
+ /* Live PCON topology from sai-power */
+ lws_dll2_owner_t power_controllers; /* sai_power_controller_t */
+
sqlite3 *pdb;
sqlite3 *pdb_auth;
} sais_t;
@@ -381,6 +384,9 @@ sai_detach_resource(struct lws_dll2 *d, void *user);
int
sai_destroy_resource_wellknown(struct lws_dll2 *d, void *user);
+int
+sai_pcon_destroy_cb(struct lws_dll2 *d, void *user);
+
void
sais_server_destroy(struct vhd *vhd, sais_t *server);
diff --git a/src/server/s-ws-builder.c b/src/server/s-ws-builder.c
index 2feed99..de48d52 100644
--- a/src/server/s-ws-builder.c
+++ b/src/server/s-ws-builder.c
@@ -783,6 +783,28 @@ sais_ws_json_rx_builder(struct vhd *vhd, struct pss *pss, uint8_t *buf, size_t b
__func__, build->name);
/*
+ * Step 1.5: Synchronize PCON binding from pcon_builders table if available.
+ * This handles the case where sai-power registered the PCON relationship
+ * before the builder connected.
+ */
+ {
+ char host[128];
+ const char *dot = strchr(build->name, '.');
+
+ if (dot)
+ lws_strnncpy(host, build->name, dot - build->name, sizeof(host));
+ else
+ lws_strncpy(host, build->name, sizeof(host));
+
+ lws_snprintf(q, sizeof(q),
+ "UPDATE builders SET pcon = (SELECT pcon_name FROM pcon_builders WHERE builder_name = '%s') "
+ "WHERE name = '%s' OR name LIKE '%s.%%'",
+ host, build->name, build->name);
+ lwsl_notice("%s: Syncing pcon for host '%s' (plat '%s'): %s\n", __func__, host, build->name, q);
+ sai_sqlite3_statement(vhd->server.pdb, q, "sync builder pcon");
+ }
+
+ /*
* Step 2: Update the long-lived, malloc'd in-memory list.
*/
diff --git a/src/server/s-ws-web.c b/src/server/s-ws-web.c
index 0ccc779..267eb28 100644
--- a/src/server/s-ws-web.c
+++ b/src/server/s-ws-web.c
@@ -155,11 +155,93 @@ reject:
return 1;
}
+int
+sais_list_pcons(struct vhd *vhd)
+{
+ char json_pcons[LWS_PRE + 8192], *start = json_pcons + LWS_PRE,
+ *p = start, *end = p + sizeof(json_pcons) - LWS_PRE;
+ unsigned int ss_flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM;
+ lws_struct_serialize_t *js;
+ struct lwsac *ac = NULL;
+ lws_wsmsg_info_t info;
+ size_t w;
+ lws_struct_json_serialize_result_t r;
+
+ sai_power_managed_builders_t pmb;
+ sai_power_controller_t *pc;
+
+ memset(&pmb, 0, sizeof(pmb));
+
+ /* Query PCONs from DB using schema map */
+ if (lws_struct_sq3_deserialize(vhd->server.pdb, NULL, "name ",
+ lsm_schema_sq3_map_power_controller,
+ &pmb.power_controllers, &ac, 0, 100)) {
+ /* It's okay if empty */
+ }
+
+ /* Iterate PCONs and populate controlled builders */
+ lws_start_foreach_dll(struct lws_dll2 *, d, pmb.power_controllers.head) {
+ pc = lws_container_of(d, sai_power_controller_t, list);
+ char filter[128];
+
+ /* Map 'builder_name' column to 'name' field in struct */
+ lws_snprintf(filter, sizeof(filter), "and pcon_name = '%s'", pc->name);
+ lws_struct_sq3_deserialize(vhd->server.pdb, filter, "builder_name ",
+ lsm_schema_sq3_map_controlled_builder,
+ &pc->controlled_builders_owner, &ac, 0, 100);
+
+ } lws_end_foreach_dll(d);
+
+ /* Serialize */
+ js = lws_struct_json_serialize_create(lsm_schema_power_managed_builders,
+ LWS_ARRAY_SIZE(lsm_schema_power_managed_builders),
+ 0, &pmb);
+ if (!js)
+ goto bail;
+
+ do {
+ r = lws_struct_json_serialize(js, (uint8_t *)p,
+ lws_ptr_diff_size_t(end, p) - 2, &w);
+ p += w;
+
+ switch (r) {
+ case LSJS_RESULT_FINISH:
+ /* fallthru */
+ case LSJS_RESULT_CONTINUE:
+ memset(&info, 0, sizeof(info));
+
+ info.private_source_idx = SAI_WEBSRV_PB__GENERATED;
+ info.buf = (uint8_t *)start;
+ info.len = lws_ptr_diff_size_t(p, start);
+ info.ss_flags = ss_flags;
+
+ if (sais_websrv_broadcast_REQUIRES_LWS_PRE(vhd->h_ss_websrv, &info) < 0)
+ lwsl_warn("%s: unable to broadcast pcons to web\n", __func__);
+
+ p = start;
+ ss_flags &= ~((unsigned int)LWSSS_FLAG_SOM);
+ break;
+
+ case LSJS_RESULT_ERROR:
+ lws_struct_json_serialize_destroy(&js);
+ goto bail;
+ }
+
+ } while (r == LSJS_RESULT_CONTINUE);
+
+ lws_struct_json_serialize_destroy(&js);
+ lwsac_free(&ac);
+ return 0;
+
+bail:
+ lwsac_free(&ac);
+ return 1;
+}
int
sais_list_builders(struct vhd *vhd)
{
- char json_builders[LWS_PRE + 1024], *start = json_builders + LWS_PRE,
+ char json_builders[LWS_PRE + 8192], *start = json_builders + LWS_PRE,
*p = start, *end = p + sizeof(json_builders) - LWS_PRE,
subsequent = 0;
unsigned int ss_flags = LWSSS_FLAG_SOM;
@@ -170,8 +252,12 @@ sais_list_builders(struct vhd *vhd)
sai_plat_t *sp;
size_t w;
+ /* Send PCONs first */
+ sais_list_pcons(vhd);
+
memset(&db_builders_owner, 0, sizeof(db_builders_owner));
+ /* Query builders table, which now includes 'pcon' column */
if (lws_struct_sq3_deserialize(vhd->server.pdb, NULL, "name ",
lsm_schema_sq3_map_plat,
&db_builders_owner, &ac, 0, 100)) {
@@ -189,6 +275,8 @@ sais_list_builders(struct vhd *vhd)
sai_plat_t *live_builder;
sp = lws_container_of(walk, sai_plat_t, sai_plat_list);
+ lwsl_notice("%s: listing builder '%s', pcon='%s'\n", __func__, sp->name, sp->pcon ? sp->pcon : "(null)");
+
live_builder = sais_builder_from_uuid(vhd, sp->name);
if (live_builder) {
@@ -227,6 +315,7 @@ sais_list_builders(struct vhd *vhd)
}
} lws_end_foreach_dll(p);
+ /* Use schema including pcon if available */
js = lws_struct_json_serialize_create(lsm_schema_map_plat_simple,
LWS_ARRAY_SIZE(lsm_schema_map_plat_simple),
0, sp);
@@ -486,7 +575,7 @@ websrvss_ws_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
m->vhd->sai_powers.head) {
struct pss *pss_power = lws_container_of(p, struct pss, same);
sai_stay_t *s;
-
+
s = malloc(sizeof(*s));
if (s) {
*s = *stay;
@@ -533,7 +622,7 @@ websrvss_srv_state(void *userobj, void *sh, lws_ss_constate_t state,
case LWSSSCS_DISCONNECTED: {
unsigned int total_viewers = 0;
- lws_buflist_destroy_all_segments(&m->bl_srv_to_web);
+ lws_buflist_destroy_all_segments(&m->bl_srv_to_web);
lws_wsmsg_destroy(m->private_heads, LWS_ARRAY_SIZE(m->private_heads));
m->viewers = 0;
diff --git a/src/web/w-ws-server.c b/src/web/w-ws-server.c
index 6047109..baaeaa7 100644
--- a/src/web/w-ws-server.c
+++ b/src/web/w-ws-server.c
@@ -52,6 +52,9 @@ const lws_struct_map_t lsm_schema_json_map[] = {
"com.warmcat.sai.taskactivity"),
LSM_SCHEMA (sai_build_metric_t, NULL, lsm_build_metric,
"com.warmcat.sai.build-metric"),
+ LSM_SCHEMA(sai_power_managed_builders_t, NULL,
+ lsm_power_managed_builders_list,
+ "com.warmcat.sai.power_managed_builders"),
};
enum {
@@ -63,6 +66,7 @@ enum {
SAIS_WS_WEBSRV_RX_LOADREPORT, /* builder's cpu load report */
SAIS_WS_WEBSRV_RX_TASKACTIVITY,
SAIS_WS_WEBSRV_RX_BUILD_METRIC,
+ SAIS_WS_WEBSRV_RX_POWER_MANAGED_BUILDERS,
};
/*
@@ -115,16 +119,9 @@ saiw_lp_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
*/
switch (m->a.top_schema_index) {
case SAIS_WS_WEBSRV_RX_LOADREPORT:
- saiw_ws_broadcast_browsers_REQUIRES_LWS_PRE(vhd, buf, len,
- lws_write_ws_flags(LWS_WRITE_TEXT,
- flags & LWSSS_FLAG_SOM, 0));
- break;
case SAIS_WS_WEBSRV_RX_TASKACTIVITY:
- saiw_ws_broadcast_browsers_REQUIRES_LWS_PRE(vhd, buf, len,
- lws_write_ws_flags(LWS_WRITE_TEXT,
- flags & LWSSS_FLAG_SOM, 0));
- break;
case SAIS_WS_WEBSRV_RX_SAI_BUILDERS:
+ case SAIS_WS_WEBSRV_RX_POWER_MANAGED_BUILDERS:
saiw_ws_broadcast_browsers_REQUIRES_LWS_PRE(vhd, buf, len,
lws_write_ws_flags(LWS_WRITE_TEXT,
flags & LWSSS_FLAG_SOM,
@@ -141,6 +138,7 @@ saiw_lp_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
case SAIS_WS_WEBSRV_RX_TASKCHANGE:
case SAIS_WS_WEBSRV_RX_EVENTCHANGE:
case SAIS_WS_WEBSRV_RX_SAI_BUILDERS:
+ case SAIS_WS_WEBSRV_RX_POWER_MANAGED_BUILDERS:
saiw_ws_broadcast_browsers_REQUIRES_LWS_PRE(vhd, buf, len,
lws_write_ws_flags(LWS_WRITE_TEXT,
flags & LWSSS_FLAG_SOM,
@@ -224,6 +222,51 @@ saiw_lp_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
saiw_ws_broadcast_browsers_REQUIRES_LWS_PRE(vhd, buf, len - (unsigned int)n,
lws_write_ws_flags(LWS_WRITE_TEXT, flags & LWSSS_FLAG_SOM, flags & LWSSS_FLAG_EOM));
break;
+ case SAIS_WS_WEBSRV_RX_POWER_MANAGED_BUILDERS:
+ /* Just forward to browser */
+ /* We already forwarded fragments in the LEJP_CONTINUE block?
+ * Wait, in the LEJP_CONTINUE block above, we handle partial forwarding.
+ * But if it arrives in one chunk (n >= 0 immediately), we must forward it here.
+ * The existing logic for other types does this.
+ * Wait, the LOADREPORT/TASKACTIVITY/BUILDERS cases below just call broadcast.
+ * But they use 'len - n' which assumes n is the length processed?
+ * Lejp returns LEJP_CONTINUE or a positive integer representing the number of bytes used?
+ * No, lejp_parse returns LEJP_CONTINUE or 0 (success) or error code.
+ *
+ * Wait, lejp_parse returns a negative error code on failure.
+ * On success, it returns 0.
+ *
+ * The code `len - (unsigned int)n` suggests n is bytes consumed?
+ * Ah, older versions of lejp/lws might behave differently.
+ * But if n == 0 (success), `len - 0` = len.
+ * So it broadcasts the whole buffer.
+ *
+ * The `else` block of `if (n == LEJP_CONTINUE)` handles the `flags & EOM` case for fragments?
+ * No, `if (n == LEJP_CONTINUE)` handles forwarding intermediate fragments.
+ * The `else` block handles the FINAL fragment (where n == 0).
+ * But wait, the `else` block logic is:
+ *
+ * ```c
+ * } else {
+ * switch (m->a.top_schema_index) {
+ * case SAIS_WS_WEBSRV_RX_TASKCHANGE: ... broadcast ...
+ * }
+ * }
+ * ```
+ *
+ * It seems correct. I added `SAIS_WS_WEBSRV_RX_POWER_MANAGED_BUILDERS` to the `else` block switch as well.
+ * But I should check if I need to add a case in the final switch (where parsing is complete).
+ *
+ * In the final switch (m->a.top_schema_index), `SAIS_WS_WEBSRV_RX_LOADREPORT` etc are handled.
+ * I should add `SAIS_WS_WEBSRV_RX_POWER_MANAGED_BUILDERS` there too if I want to ensure the final chunk is sent if it wasn't handled by the `else` block?
+ *
+ * Actually, look at `SAIS_WS_WEBSRV_RX_SAI_BUILDERS` in the final switch. It does complex logic (updating vhd state).
+ * `com.warmcat.sai.power_managed_builders` is just passthrough to the browser, sai-web doesn't need to statefully track PCONs (browsers do).
+ * So treating it like LOADREPORT (passthrough) is correct.
+ */
+ saiw_ws_broadcast_browsers_REQUIRES_LWS_PRE(vhd, buf, len - (unsigned int)n,
+ lws_write_ws_flags(LWS_WRITE_TEXT, flags & LWSSS_FLAG_SOM, flags & LWSSS_FLAG_EOM));
+ break;
}
cleanup_parse_allocs: