diff --git a/internal/cli/cli_notices.go b/internal/cli/cli_notices.go
index 747224c..ab3f0b4 100755
--- a/internal/cli/cli_notices.go
+++ b/internal/cli/cli_notices.go
@@ -1,7 +1,7 @@
package cli
// ===========================================================================================================
-// This file was generated automatically at 16-11-2022 12:32:57 using gogenlicense.
+// This file was generated automatically at 16-11-2022 18:40:47 using gogenlicense.
// Do not edit manually, as changes may be overwritten.
// ===========================================================================================================
@@ -1631,7 +1631,7 @@ package cli
// # Generation
//
// This variable and the associated documentation have been automatically generated using the 'gogenlicense' tool.
-// It was last updated at 16-11-2022 12:32:57.
+// It was last updated at 16-11-2022 18:40:47.
var LegalNotices string
func init() {
diff --git a/internal/dis/component/control/home/home.html b/internal/dis/component/control/home/home.html
index f03cfca..7202932 100644
--- a/internal/dis/component/control/home/home.html
+++ b/internal/dis/component/control/home/home.html
@@ -27,6 +27,12 @@
{{.URL}}
{{ .Statistics.Bundles.Summary }}
+
+ {{ $edit := .Statistics.Bundles.LastEdit }}
+ {{ if $edit.Valid }}
+
+ last edited {{ $edit.Time.Format "2006-01-02T15:04:05Z07:00" }}
+ {{ end }}
diff --git a/internal/dis/component/control/info/html/instance.html b/internal/dis/component/control/info/html/instance.html
index 6578dd0..571c1bc 100644
--- a/internal/dis/component/control/info/html/instance.html
+++ b/internal/dis/component/control/info/html/instance.html
@@ -310,7 +310,7 @@
{{ .Count }}
- {{ .LastEdit }}
+ {{ .LastEdit.Time.Format "2006-01-02T15:04:05Z07:00" }}
{{ .MainBundle }}
diff --git a/internal/dis/component/control/static/assets_dist.go b/internal/dis/component/control/static/assets_dist.go
index e05bff8..9d5593c 100644
--- a/internal/dis/component/control/static/assets_dist.go
+++ b/internal/dis/component/control/static/assets_dist.go
@@ -16,13 +16,13 @@ var AssetsComponentsIndex = Assets{
// AssetsControlIndex contains assets for the 'ControlIndex' entrypoint.
var AssetsControlIndex = Assets{
- Scripts: ``,
+ Scripts: ``,
Styles: ` `,
}
// AssetsControlInstance contains assets for the 'ControlInstance' entrypoint.
var AssetsControlInstance = Assets{
- Scripts: ``,
+ Scripts: ``,
Styles: ` `,
}
diff --git a/internal/dis/component/control/static/dist/ControlIndex.03d7b00f.js b/internal/dis/component/control/static/dist/ControlIndex.03d7b00f.js
new file mode 100644
index 0000000..40f9338
--- /dev/null
+++ b/internal/dis/component/control/static/dist/ControlIndex.03d7b00f.js
@@ -0,0 +1 @@
+!function(){function t(t){return t&&t.__esModule?t.default:t}var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},r={},i=e.parcelRequireafa4;null==i&&((i=function(t){if(t in n)return n[t].exports;if(t in r){var e=r[t];delete r[t];var i={id:t,exports:{}};return n[t]=i,e.call(i.exports,i,i.exports),i.exports}var s=new Error("Cannot find module '"+t+"'");throw s.code="MODULE_NOT_FOUND",s}).register=function(t,e){r[t]=e},e.parcelRequireafa4=i),i.register("8s4Fe",(function(t,e){i("8vh0V"),i("1a6Da")})),i.register("8vh0V",(function(t,e){var n=i("4Ma2p");const r=document.getElementsByClassName("remote-action");Array.from(r).forEach((t=>{const e=t.getAttribute("data-action"),r=t.hasAttribute("data-force-reload"),i=t.getAttribute("data-param"),s=function(){var e,n;const r=null!==(n=parseInt(null!==(e=t.getAttribute("data-buffer"))&&void 0!==e?e:"",10))&&void 0!==n?n:0;return isFinite(r)&&r>0?r:0}();t.addEventListener("click",(function(t){t.preventDefault();const o=document.createElement("div");o.className="modal-terminal",document.body.append(o);const a=document.createElement("pre"),u=function(t,e,n){let r=null;const i=[],s=()=>{o.paintedFrames++,t.innerText=i.join("\n"),e.scrollTop=e.scrollHeight,r=null},o=(t,e)=>{if(i.push(t),0!==n&&i.length>n&&i.splice(0,i.length-n),null!==r&&(o.missedFrames++,window.cancelAnimationFrame(r)),e)return s();r=window.requestAnimationFrame(s)};return o.paintedFrames=0,o.missedFrames=0,o}(a,o,s);o.append(a);const c=document.createElement("button");c.className="pure-button pure-button-success",c.append(r?"Close & Reload":"Close"),c.addEventListener("click",(function(t){var e;if(t.preventDefault(),r)return c.setAttribute("disabled","disabled"),a.innerHTML="Reloading page ...",void location.reload();null===(e=o.parentNode)||void 0===e||e.removeChild(o)}));const d=window.onbeforeunload;window.onbeforeunload=()=>"A remote session is in progress. Are you sure you want to leave?";let l=!1;const f=function(){l||(l=!0,window.onbeforeunload=d,o.append(c))};u("Connecting ...",!0),(0,n.default)((t=>{u("Connected",!0),t.send(e),"string"==typeof i&&t.send(i)}),(t=>{u(t)})).then((()=>{u("Connection closed.",!0),f()})).catch((()=>{u("Connection errored.",!0),f()}))}))}))})),i.register("4Ma2p",(function(t,e){var n,r,i,s;function o(t,e){return new Promise(((n,r)=>{const i=new WebSocket(location.href.replace("http","ws"));i.onclose=n,i.onerror=r,i.onmessage=t=>e(t.data),i.onopen=()=>t(i)}))}n=t.exports,r="default",i=function(){return o},Object.defineProperty(n,r,{get:i,set:s,enumerable:!0,configurable:!0})})),i.register("1a6Da",(function(e,n){var r=i("c0qMM");const s={date:e=>{const n=t(r)(e.innerText),i=n.format("YYYY-MM-DD HH:mm:ss ([UTC]Z)");if(0===n.unix()){const t=document.createElement("code");return t.style.color="gray",t.append(i),t}return i},path:t=>{const e=t.innerText.split("/");return e[e.length-1]},pathbuilder:t=>{var e;const n=(null!==(e=t.getAttribute("data-name"))&&void 0!==e?e:"pathbuilder")+".xml",[r,i]=o(n,t.innerText,"application/xml");r.className="pure-button";const s=n+" ("+i.size+" Bytes)";return r.append(s),r}},o=(t,e,n)=>{const r=new Blob([e],{type:null!=n?n:"text/plain"}),i=document.createElement("a");return i.target="_blank",i.download=t,i.href=URL.createObjectURL(r),[i,r]};Object.keys(s).forEach((t=>{const e=s[t];document.querySelectorAll("code."+t).forEach((t=>{const n=e(t);if("string"==typeof n)return t.innerHTML="",void t.appendChild(document.createTextNode(n));t.parentNode.replaceChild(n,t)}))}))})),i.register("c0qMM",(function(t,e){t.exports,t.exports=function(){"use strict";var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",o="hour",a="day",u="week",c="month",d="quarter",l="year",f="date",h="Invalid Date",p=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,m=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,$={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_")},g=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:g,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+g(r,2,"0")+":"+g(i,2,"0")},m:function t(e,n){if(e.date()1)return t(o[0])}else{var a=e.name;M[a]=e,i=a}return!r&&i&&(y=i),i||!r&&y},b=function(t,e){if(w(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new O(n)},S=v;S.l=D,S.i=w,S.w=function(t,e){return b(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var O=function(){function $(t){this.$L=D(t.locale,null,!0),this.parse(t)}var g=$.prototype;return g.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(S.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match(p);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},g.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},g.$utils=function(){return S},g.isValid=function(){return!(this.$d.toString()===h)},g.isSame=function(t,e){var n=b(t);return this.startOf(e)<=n&&n<=this.endOf(e)},g.isAfter=function(t,e){return b(t){const e=t.getAttribute("data-action"),r=t.hasAttribute("data-force-reload"),i=t.getAttribute("data-param"),s=function(){var e,n;const r=null!==(n=parseInt(null!==(e=t.getAttribute("data-buffer"))&&void 0!==e?e:"",10))&&void 0!==n?n:0;return isFinite(r)&&r>0?r:0}();t.addEventListener("click",(function(t){t.preventDefault();const o=document.createElement("div");o.className="modal-terminal",document.body.append(o);const a=document.createElement("pre"),u=function(t,e,n){let r=null;const i=[],s=()=>{o.paintedFrames++,t.innerText=i.join("\n"),e.scrollTop=e.scrollHeight,r=null},o=(t,e)=>{if(i.push(t),0!==n&&i.length>n&&i.splice(0,i.length-n),null!==r&&(o.missedFrames++,window.cancelAnimationFrame(r)),e)return s();r=window.requestAnimationFrame(s)};return o.paintedFrames=0,o.missedFrames=0,o}(a,o,s);o.append(a);const c=document.createElement("button");c.className="pure-button pure-button-success",c.append(r?"Close & Reload":"Close"),c.addEventListener("click",(function(t){var e;if(t.preventDefault(),r)return c.setAttribute("disabled","disabled"),a.innerHTML="Reloading page ...",void location.reload();null===(e=o.parentNode)||void 0===e||e.removeChild(o)}));const d=window.onbeforeunload;window.onbeforeunload=()=>"A remote session is in progress. Are you sure you want to leave?";let l=!1;const f=function(){l||(l=!0,window.onbeforeunload=d,o.append(c))};u("Connecting ...",!0),(0,n.default)((t=>{u("Connected",!0),t.send(e),"string"==typeof i&&t.send(i)}),(t=>{u(t)})).then((()=>{u("Connection closed.",!0),f()})).catch((()=>{u("Connection errored.",!0),f()}))}))}))})),i.register("4Ma2p",(function(t,e){var n,r,i,s;function o(t,e){return new Promise(((n,r)=>{const i=new WebSocket(location.href.replace("http","ws"));i.onclose=n,i.onerror=r,i.onmessage=t=>e(t.data),i.onopen=()=>t(i)}))}n=t.exports,r="default",i=function(){return o},Object.defineProperty(n,r,{get:i,set:s,enumerable:!0,configurable:!0})})),i.register("1a6Da",(function(e,n){var r=i("c0qMM");const s={date:e=>t(r)(e.innerText).format("YYYY-MM-DD HH:mm:ss ([UTC]Z)"),path:t=>{const e=t.innerText.split("/");return e[e.length-1]},pathbuilder:t=>{var e;const n=(null!==(e=t.getAttribute("data-name"))&&void 0!==e?e:"pathbuilder")+".xml",[r,i]=o(n,t.innerText,"application/xml");r.className="pure-button";const s=n+" ("+i.size+" Bytes)";return r.append(s),r}},o=(t,e,n)=>{const r=new Blob([e],{type:null!=n?n:"text/plain"}),i=document.createElement("a");return i.target="_blank",i.download=t,i.href=URL.createObjectURL(r),[i,r]};Object.keys(s).forEach((t=>{const e=s[t];document.querySelectorAll("code."+t).forEach((t=>{const n=e(t);if("string"==typeof n)return t.innerHTML="",void t.appendChild(document.createTextNode(n));t.parentNode.replaceChild(n,t)}))}))})),i.register("c0qMM",(function(t,e){t.exports,t.exports=function(){"use strict";var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",o="hour",a="day",u="week",c="month",d="quarter",l="year",f="date",h="Invalid Date",p=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,m=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,$={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_")},g=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:g,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+g(r,2,"0")+":"+g(i,2,"0")},m:function t(e,n){if(e.date()1)return t(o[0])}else{var a=e.name;M[a]=e,i=a}return!r&&i&&(y=i),i||!r&&y},b=function(t,e){if(w(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new O(n)},S=v;S.l=D,S.i=w,S.w=function(t,e){return b(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var O=function(){function $(t){this.$L=D(t.locale,null,!0),this.parse(t)}var g=$.prototype;return g.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(S.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match(p);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},g.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},g.$utils=function(){return S},g.isValid=function(){return!(this.$d.toString()===h)},g.isSame=function(t,e){var n=b(t);return this.startOf(e)<=n&&n<=this.endOf(e)},g.isAfter=function(t,e){return b(t){const e=t.getAttribute("data-action"),r=t.hasAttribute("data-force-reload"),i=t.getAttribute("data-param"),s=function(){const e=parseInt(t.getAttribute("data-buffer")??"",10)??0;return isFinite(e)&&e>0?e:0}();t.addEventListener("click",(function(t){t.preventDefault();const o=document.createElement("div");o.className="modal-terminal",document.body.append(o);const a=document.createElement("pre"),u=function(t,e,n){let r=null;const i=[],s=()=>{o.paintedFrames++,t.innerText=i.join("\n"),e.scrollTop=e.scrollHeight,r=null},o=(t,e)=>{if(i.push(t),0!==n&&i.length>n&&i.splice(0,i.length-n),null!==r&&(o.missedFrames++,window.cancelAnimationFrame(r)),e)return s();r=window.requestAnimationFrame(s)};return o.paintedFrames=0,o.missedFrames=0,o}(a,o,s);o.append(a);const c=document.createElement("button");c.className="pure-button pure-button-success",c.append(r?"Close & Reload":"Close"),c.addEventListener("click",(function(t){if(t.preventDefault(),r)return c.setAttribute("disabled","disabled"),a.innerHTML="Reloading page ...",void location.reload();o.parentNode?.removeChild(o)}));const d=window.onbeforeunload;window.onbeforeunload=()=>"A remote session is in progress. Are you sure you want to leave?";let f=!1;const l=function(){f||(f=!0,window.onbeforeunload=d,o.append(c))};u("Connecting ...",!0),(0,n.default)((t=>{u("Connected",!0),t.send(e),"string"==typeof i&&t.send(i)}),(t=>{u(t)})).then((()=>{u("Connection closed.",!0),l()})).catch((()=>{u("Connection errored.",!0),l()}))}))}))})),i.register("9DNME",(function(t,e){var n,r,i,s;function o(t,e){return new Promise(((n,r)=>{const i=new WebSocket(location.href.replace("http","ws"));i.onclose=n,i.onerror=r,i.onmessage=t=>e(t.data),i.onopen=()=>t(i)}))}n=t.exports,r="default",i=function(){return o},Object.defineProperty(n,r,{get:i,set:s,enumerable:!0,configurable:!0})})),i.register("9LRKV",(function(e,n){var r=i("jK56W");const s={date:e=>{const n=t(r)(e.innerText),i=n.format("YYYY-MM-DD HH:mm:ss ([UTC]Z)");if(0===n.unix()){const t=document.createElement("code");return t.style.color="gray",t.append(i),t}return i},path:t=>{const e=t.innerText.split("/");return e[e.length-1]},pathbuilder:t=>{const e=(t.getAttribute("data-name")??"pathbuilder")+".xml",[n,r]=o(e,t.innerText,"application/xml");n.className="pure-button";const i=e+" ("+r.size+" Bytes)";return n.append(i),n}},o=(t,e,n)=>{const r=new Blob([e],{type:n??"text/plain"}),i=document.createElement("a");return i.target="_blank",i.download=t,i.href=URL.createObjectURL(r),[i,r]};Object.keys(s).forEach((t=>{const e=s[t];document.querySelectorAll("code."+t).forEach((t=>{const n=e(t);if("string"==typeof n)return t.innerHTML="",void t.appendChild(document.createTextNode(n));t.parentNode.replaceChild(n,t)}))}))})),i.register("jK56W",(function(t,e){t.exports,t.exports=function(){var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",o="hour",a="day",u="week",c="month",d="quarter",f="year",l="date",h="Invalid Date",p=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,m=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,$={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_")},g=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},y={s:g,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+g(r,2,"0")+":"+g(i,2,"0")},m:function t(e,n){if(e.date()1)return t(o[0])}else{var a=e.name;M[a]=e,i=a}return!r&&i&&(v=i),i||!r&&v},b=function(t,e){if(w(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new O(n)},S=y;S.l=D,S.i=w,S.w=function(t,e){return b(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var O=function(){function $(t){this.$L=D(t.locale,null,!0),this.parse(t)}var g=$.prototype;return g.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(S.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match(p);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},g.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},g.$utils=function(){return S},g.isValid=function(){return!(this.$d.toString()===h)},g.isSame=function(t,e){var n=b(t);return this.startOf(e)<=n&&n<=this.endOf(e)},g.isAfter=function(t,e){return b(t){const e=t.getAttribute("data-action"),r=t.hasAttribute("data-force-reload"),i=t.getAttribute("data-param"),s=function(){const e=parseInt(t.getAttribute("data-buffer")??"",10)??0;return isFinite(e)&&e>0?e:0}();t.addEventListener("click",(function(t){t.preventDefault();const o=document.createElement("div");o.className="modal-terminal",document.body.append(o);const a=document.createElement("pre"),u=function(t,e,n){let r=null;const i=[],s=()=>{o.paintedFrames++,t.innerText=i.join("\n"),e.scrollTop=e.scrollHeight,r=null},o=(t,e)=>{if(i.push(t),0!==n&&i.length>n&&i.splice(0,i.length-n),null!==r&&(o.missedFrames++,window.cancelAnimationFrame(r)),e)return s();r=window.requestAnimationFrame(s)};return o.paintedFrames=0,o.missedFrames=0,o}(a,o,s);o.append(a);const c=document.createElement("button");c.className="pure-button pure-button-success",c.append(r?"Close & Reload":"Close"),c.addEventListener("click",(function(t){if(t.preventDefault(),r)return c.setAttribute("disabled","disabled"),a.innerHTML="Reloading page ...",void location.reload();o.parentNode?.removeChild(o)}));const d=window.onbeforeunload;window.onbeforeunload=()=>"A remote session is in progress. Are you sure you want to leave?";let f=!1;const l=function(){f||(f=!0,window.onbeforeunload=d,o.append(c))};u("Connecting ...",!0),(0,n.default)((t=>{u("Connected",!0),t.send(e),"string"==typeof i&&t.send(i)}),(t=>{u(t)})).then((()=>{u("Connection closed.",!0),l()})).catch((()=>{u("Connection errored.",!0),l()}))}))}))})),i.register("9DNME",(function(t,e){var n,r,i,s;function o(t,e){return new Promise(((n,r)=>{const i=new WebSocket(location.href.replace("http","ws"));i.onclose=n,i.onerror=r,i.onmessage=t=>e(t.data),i.onopen=()=>t(i)}))}n=t.exports,r="default",i=function(){return o},Object.defineProperty(n,r,{get:i,set:s,enumerable:!0,configurable:!0})})),i.register("9LRKV",(function(e,n){var r=i("jK56W");const s={date:e=>t(r)(e.innerText).format("YYYY-MM-DD HH:mm:ss ([UTC]Z)"),path:t=>{const e=t.innerText.split("/");return e[e.length-1]},pathbuilder:t=>{const e=(t.getAttribute("data-name")??"pathbuilder")+".xml",[n,r]=o(e,t.innerText,"application/xml");n.className="pure-button";const i=e+" ("+r.size+" Bytes)";return n.append(i),n}},o=(t,e,n)=>{const r=new Blob([e],{type:n??"text/plain"}),i=document.createElement("a");return i.target="_blank",i.download=t,i.href=URL.createObjectURL(r),[i,r]};Object.keys(s).forEach((t=>{const e=s[t];document.querySelectorAll("code."+t).forEach((t=>{const n=e(t);if("string"==typeof n)return t.innerHTML="",void t.appendChild(document.createTextNode(n));t.parentNode.replaceChild(n,t)}))}))})),i.register("jK56W",(function(t,e){t.exports,t.exports=function(){var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",o="hour",a="day",u="week",c="month",d="quarter",f="year",l="date",h="Invalid Date",p=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,m=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,$={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_")},g=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:g,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+g(r,2,"0")+":"+g(i,2,"0")},m:function t(e,n){if(e.date()1)return t(o[0])}else{var a=e.name;M[a]=e,i=a}return!r&&i&&(y=i),i||!r&&y},b=function(t,e){if(w(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new O(n)},S=v;S.l=D,S.i=w,S.w=function(t,e){return b(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var O=function(){function $(t){this.$L=D(t.locale,null,!0),this.parse(t)}var g=$.prototype;return g.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(S.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match(p);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},g.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},g.$utils=function(){return S},g.isValid=function(){return!(this.$d.toString()===h)},g.isSame=function(t,e){var n=b(t);return this.startOf(e)<=n&&n<=this.endOf(e)},g.isAfter=function(t,e){return b(t) HTMLElement | string> = {
"date": (element) => {
- return dayjs(element.innerText).format('YYYY-MM-DD HH:mm:ss ([UTC]Z)')
+ const value = dayjs(element.innerText);
+ const text = value.format('YYYY-MM-DD HH:mm:ss ([UTC]Z)')
+
+ // if the date is the zero date, then it is assumed to be invalid
+ if (value.unix() === 0) {
+ const code = document.createElement('code')
+ code.style.color = 'gray'
+ code.append(text)
+ return code
+ }
+ return text
},
"path": (element) => {
const text = element.innerText.split("/");
diff --git a/internal/phpx/serialize.go b/internal/phpx/serialize.go
deleted file mode 100644
index 958fc7f..0000000
--- a/internal/phpx/serialize.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package phpx
-
-import "encoding/json"
-
-// BooleanIsh represents a boolean php value.
-//
-// The value can be serialized to and from php and will behave accordingly.
-//
-// The value will always be Marshaled as "true" or "false".
-//
-// When Unmarshaled, it behaves as described on https://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting.
-type BooleanIsh bool
-
-func (bi BooleanIsh) MarshalJSON() ([]byte, error) {
- if bi {
- return []byte("true"), nil
- }
- return []byte("false"), nil
-}
-func (bi *BooleanIsh) UnmarshalJSON(data []byte) (err error) {
- // unmarshal into a generic value
- var value any
- err = json.Unmarshal(data, &value)
- if err != nil {
- return err
- }
-
- // check if it is false ish
- var isFalseIsh bool
- switch d := value.(type) {
- case bool:
- isFalseIsh = !d
- case int:
- isFalseIsh = d == 0
- case float64:
- isFalseIsh = d == 0
- case string:
- isFalseIsh = d == "" || d == "0"
- case []any:
- isFalseIsh = len(d) == 0
- case map[string]any:
- isFalseIsh = len(d) == 0
- case nil:
- isFalseIsh = true
- }
- *bi = BooleanIsh(!isFalseIsh)
-
- return nil
-}
diff --git a/internal/phpx/types.go b/internal/phpx/types.go
new file mode 100644
index 0000000..4346e24
--- /dev/null
+++ b/internal/phpx/types.go
@@ -0,0 +1,138 @@
+package phpx
+
+import (
+ "encoding/json"
+ "strconv"
+ "time"
+)
+
+// PHPBoolean represents a boolean php value.
+//
+// The value can be marshaled to and from php and will behave as a PHP would behave.
+//
+// The value will always be marshaled as "true" or "false".
+// Unmarshaling uses [Boolean].
+type PHPBoolean bool
+
+func (bi PHPBoolean) MarshalJSON() ([]byte, error) {
+ if bi {
+ return []byte("true"), nil
+ }
+ return []byte("false"), nil
+}
+func (bi *PHPBoolean) UnmarshalJSON(data []byte) (err error) {
+ // unmarshal into a generic value
+ var value any
+ err = json.Unmarshal(data, &value)
+ if err != nil {
+ return err
+ }
+
+ // cast into a boolean
+ cast, ok := Boolean(value)
+ if !ok {
+ value = false
+ }
+ *bi = PHPBoolean(cast)
+
+ return nil
+}
+
+// Boolean tries to cast the given value to a boolean.
+//
+// It is able to handle any value that would be [json.Unmarshaled] from a corresponding PHP value.
+// Value treates all values as the boolean true, except for the ones listed at [doc].
+//
+// [doc]: https://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting
+func Boolean(value any) (b bool, ok bool) {
+ switch d := value.(type) {
+ case bool:
+ return d, true
+ case float64:
+ return d != 0, true
+ case string:
+ return (d != "" && d != "0"), true
+ case []any:
+ return len(d) != 0, true
+ case map[string]any:
+ return len(d) != 0, true
+ case nil:
+ return true, true
+ }
+ return true, false
+}
+
+// String tries to cast the given value to a string.
+//
+// It is able to handle any value that would be [json.Unmarshaled] from a corresponding PHP value.
+// Value casting is described at [doc].
+//
+// [doc]: https://www.php.net/manual/en/language.types.string.php#language.types.string.casting
+func String(value any) (s string, ok bool) {
+ switch d := value.(type) {
+ case bool:
+ if d {
+ return "1", true
+ }
+ return "", true
+ case float64:
+ if d == float64(int64(d)) {
+ return strconv.FormatInt(int64(d), 10), true
+ }
+ // TODO: not sure this is entirely correct
+ // and we should handle ints here!
+ return strconv.FormatFloat(d, 'E', 1, 64), true
+ case string:
+ return d, true
+ case []any, map[string]any:
+ return "Array", true
+ case nil:
+ return "", true
+ }
+
+ return "", false
+}
+
+// Integer tries to cast the given value to an integer.
+//
+// It is able to handle any value that would be [json.Unmarshaled] from a corresponding PHP value.
+// Value casting is described at [doc].
+//
+// [doc]: https://www.php.net/manual/en/language.types.integer.php#language.types.integer.casting
+func Integer(value any) (i int64, ok bool) {
+ str, ok := String(value)
+ if !ok {
+ return 0, false
+ }
+
+ // try to parse the "leading" string, by successively cutting off parts of the tail
+ // once we have a valid number, return it.
+ for l := 0; l < len(str); l++ {
+ i64, err := strconv.ParseInt(str[:len(str)-l], 10, 64)
+ if err != nil {
+ continue
+ }
+ return i64, true
+ }
+ return 0, true
+}
+
+// TimeInt represents a time value in PHP, represented as an integer
+type TimeInt time.Time
+
+func (ts TimeInt) Time() time.Time {
+ return time.Time(ts)
+}
+func (ts TimeInt) MarshalJSON() ([]byte, error) {
+ return []byte(strconv.FormatInt(ts.Time().Unix(), 10)), nil
+}
+
+func (ts *TimeInt) UnmarshalJSON(data []byte) (err error) {
+ var value any
+ if err := json.Unmarshal(data, &value); err != nil {
+ return err
+ }
+ unix, _ := Integer(value)
+ *ts = TimeInt(time.Unix(unix, 0))
+ return nil
+}
diff --git a/internal/wisski/ingredient/fetcher.go b/internal/wisski/ingredient/fetcher.go
index 0063f49..2f204fc 100644
--- a/internal/wisski/ingredient/fetcher.go
+++ b/internal/wisski/ingredient/fetcher.go
@@ -84,14 +84,31 @@ type BundleStatistics struct {
Count int `json:"entities"`
- LastEdit int `json:"lastEdit"`
+ LastEdit phpx.TimeInt `json:"lastEdit"`
- MainBundle phpx.BooleanIsh `json:"mainBundle"`
+ MainBundle phpx.PHPBoolean `json:"mainBundle"`
} `json:"bundleStatistics"`
TotalBundles int `json:"totalBundles"`
TotalMainBundles int `json:"totalMainBundles"`
}
+type LastEdit struct {
+ Time time.Time
+ Valid bool
+}
+
+// LastEdit returns the last time any bundle was edited, and if any edit was bigger than the reference time
+func (bs BundleStatistics) LastEdit() (le LastEdit) {
+ for _, bundle := range bs.Bundles {
+ time := bundle.LastEdit.Time()
+ if time.After(le.Time) {
+ le.Valid = true
+ le.Time = time
+ }
+ }
+ return
+}
+
func (bs BundleStatistics) Summary() string {
var totalCount int
for _, bundle := range bs.Bundles {