diff --git a/dashboard.go b/dashboard.go deleted file mode 100644 index dfb29a9..0000000 --- a/dashboard.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - // External - werr "git.ahall.se/go/wrappederror" - "github.com/jackc/pgx/v5" - - // Standard - "context" - "encoding/json" -) - -type WidgetType int - -const ( - WIDGET_SPACE WidgetType = iota - WIDGET_LABEL - WIDGET_ONOFF -) - -type Widget struct { - Type WidgetType - X int - Y int - SpanX int - SpanY int - DatapointID int - Attributes map[string]string -} - -type Dashboard struct { - ID int - Name string - Widgets []Widget -} - -func DashboardGet(name string) (dashboard Dashboard, err error) { - var rows pgx.Rows - rows, err = db.Query(context.Background(), `SELECT * FROM dashboard WHERE name = $1`, name) - if err != nil { - err = werr.Wrap(err) - return - } - - dashboard, err = pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[Dashboard]) - if err != nil { - err = werr.Wrap(err) - return - } - - return -} - -func DashboardUpdate(dashboard Dashboard) error { - widgets, _ := json.Marshal(dashboard.Widgets) - - _, err := db.Exec( - context.Background(), - `UPDATE dashboard SET name=$2, widgets=$3 WHERE id=$1`, - dashboard.ID, - dashboard.Name, - widgets, - ) - if err != nil { - return werr.Wrap(err) - } - - return nil -} diff --git a/datapoint.go b/datapoint.go index f540db8..b47ccc9 100644 --- a/datapoint.go +++ b/datapoint.go @@ -28,35 +28,13 @@ type Datapoint struct { Datatype DatapointType Comment string LastValue time.Time `db:"last_value"` - DataPointID int `db:"datapoint_id"` DatapointValueJSON []byte `db:"datapoint_value_json"` LastDatapointValue DatapointValue - LastDatapointID *int `db:"last_value_id"` - LastValueInt *int `db:"last_value_int"` - LastValueString *string `db:"last_value_string"` - LastValueDateTime *time.Time `db:"last_value_datetime"` Found bool NodataProblemSeconds int `db:"nodata_problem_seconds"` NodataIsProblem bool `db:"nodata_is_problem"` } -type DatapointBrief struct { - ID int - Name string - Comment string - Datatype DatapointType - LastValueInt *int `db:"last_value_int"` - LastValueString *string `db:"last_value_string"` - LastValueDateTime *time.Time `db:"last_value_datetime"` - NodataIsProblem bool `db:"nodata_is_problem"` -} - -type DatapointTiny struct { - ID int - Value any - Valid bool -} - type DatapointValue struct { ID int DatapointID int `db:"datapoint_id"` @@ -284,7 +262,7 @@ func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{ } rows, _ := db.Query(context.Background(), query, param) - dp, err = pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[Datapoint]) + dp, err = pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Datapoint]) if err != nil { err = werr.Wrap(err) return @@ -314,7 +292,7 @@ func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{ `, dp.ID, ) - dp.LastDatapointValue, err = pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[DatapointValue]) + dp, err = pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Datapoint]) if err == sql.ErrNoRows { err = nil return @@ -392,53 +370,3 @@ func DatapointValues(id int, from, to time.Time) (values []DatapointValue, err e return } // }}} - -// DatapointsValue only returns ID and type and is used primarily for the dashboard. -func DatapointsValue(ids []int) (values []DatapointTiny, err error) { // {{{ - values = []DatapointTiny{} - - rows, _ := db.Query( - context.Background(), - ` - SELECT - id, - name, - comment, - datatype, - last_value_int, - last_value_string, - last_value_datetime, - nodata_is_problem - FROM datapoint - WHERE - id = ANY($1) - `, - ids, - ) - - var briefs []DatapointBrief - briefs, err = pgx.CollectRows(rows, pgx.RowToStructByNameLax[DatapointBrief]) - if err != nil { - err = werr.Wrap(err) - return - } - - for _, brief := range briefs { - tiny := DatapointTiny{} - tiny.ID = brief.ID - tiny.Valid = !brief.NodataIsProblem - - switch brief.Datatype { - case "INT": - tiny.Value = brief.LastValueInt - case "STRING": - tiny.Value = brief.LastValueString - case "DATETIME": - tiny.Value = brief.LastValueDateTime - } - - values = append(values, tiny) - } - - return -} // }}} diff --git a/go.mod b/go.mod index 7156421..6880382 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module smon -go 1.26.0 +go 1.25.0 require ( git.ahall.se/go/html_template v0.1.1 diff --git a/main.go b/main.go index 4094aec..d6f81cd 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,7 @@ import ( "time" ) -const VERSION = "v42" +const VERSION = "v41" var ( logger *slog.Logger @@ -160,9 +160,6 @@ func main() { // {{{ http.HandleFunc("/configuration/notification/delete/{prio}", actionConfigurationNotificationDelete) http.HandleFunc("/entry/{datapoint}", actionEntryDatapoint) - http.HandleFunc("GET /dashboard/values", actionDashboardValues) - http.HandleFunc("POST /dashboard/update", actionDashboardUpdate) - go nodataLoop() smonConfig, err = SmonConfigInit() @@ -408,52 +405,8 @@ func pageIndex(w http.ResponseWriter, r *http.Request) { // {{{ PAGE: "index", CONFIG: smonConfig.Settings, } - - dashboard, err := DashboardGet("start") - if err != nil { - logger.Error("dashboard", "error", err) - } - - data := make(map[string]any) - data["Dashboard"] = dashboard - page.Data = data page.Render(w, r) } // }}} -func actionDashboardValues(w http.ResponseWriter, r *http.Request) { // {{{ - values, err := DatapointsValue([]int{13, 15}) - if err != nil { - logger.Error("dashboard", "error", err) - httpError(w, err) - return - } - - j, _ := json.Marshal(struct { - OK bool - Values []DatapointTiny - }{ - true, - values, - }) - w.Write(j) -} // }}} -func actionDashboardUpdate(w http.ResponseWriter, r *http.Request) { // {{{ - var dashboard Dashboard - body, _ := io.ReadAll(r.Body) - err := json.Unmarshal(body, &dashboard) - if err != nil { - httpError(w, werr.Wrap(err)) - return - } - - err = DashboardUpdate(dashboard) - if err != nil { - httpError(w, werr.Wrap(err)) - return - } - - j, _ := json.Marshal(struct{ OK bool }{true}) - w.Write(j) -} // }}} func actionAreaNew(w http.ResponseWriter, r *http.Request) { // {{{ name := r.PathValue("name") @@ -465,6 +418,7 @@ func actionAreaNew(w http.ResponseWriter, r *http.Request) { // {{{ w.Header().Add("Location", "/configuration") w.WriteHeader(302) + return } // }}} func actionAreaRename(w http.ResponseWriter, r *http.Request) { // {{{ idStr := r.PathValue("id") @@ -483,6 +437,7 @@ func actionAreaRename(w http.ResponseWriter, r *http.Request) { // {{{ w.Header().Add("Location", "/configuration") w.WriteHeader(302) + return } // }}} func actionAreaDelete(w http.ResponseWriter, r *http.Request) { // {{{ idStr := r.PathValue("id") @@ -500,6 +455,7 @@ func actionAreaDelete(w http.ResponseWriter, r *http.Request) { // {{{ w.Header().Add("Location", "/configuration") w.WriteHeader(302) + return } // }}} func actionSectionNew(w http.ResponseWriter, r *http.Request) { // {{{ @@ -519,6 +475,7 @@ func actionSectionNew(w http.ResponseWriter, r *http.Request) { // {{{ w.Header().Add("Location", "/configuration") w.WriteHeader(302) + return } // }}} func actionSectionRename(w http.ResponseWriter, r *http.Request) { // {{{ idStr := r.PathValue("id") @@ -537,6 +494,7 @@ func actionSectionRename(w http.ResponseWriter, r *http.Request) { // {{{ w.Header().Add("Location", "/configuration") w.WriteHeader(302) + return } // }}} func actionSectionDelete(w http.ResponseWriter, r *http.Request) { // {{{ idStr := r.PathValue("id") @@ -554,6 +512,7 @@ func actionSectionDelete(w http.ResponseWriter, r *http.Request) { // {{{ w.Header().Add("Location", "/configuration") w.WriteHeader(302) + return } // }}} func pageProblems(w http.ResponseWriter, r *http.Request) { // {{{ @@ -618,6 +577,7 @@ func pageProblems(w http.ResponseWriter, r *http.Request) { // {{{ "TimeTo": timeTo.Format("2006-01-02T15:04:05"), } page.Render(w, r) + return } // }}} func actionProblemAcknowledge(w http.ResponseWriter, r *http.Request) { // {{{ idStr := r.PathValue("id") @@ -635,6 +595,8 @@ func actionProblemAcknowledge(w http.ResponseWriter, r *http.Request) { // {{{ w.Header().Add("Location", "/problems") w.WriteHeader(302) + + return } // }}} func actionProblemUnacknowledge(w http.ResponseWriter, r *http.Request) { // {{{ idStr := r.PathValue("id") @@ -652,6 +614,8 @@ func actionProblemUnacknowledge(w http.ResponseWriter, r *http.Request) { // {{{ w.Header().Add("Location", "/problems") w.WriteHeader(302) + + return } // }}} func pageDatapoints(w http.ResponseWriter, r *http.Request) { // {{{ @@ -678,6 +642,7 @@ func pageDatapoints(w http.ResponseWriter, r *http.Request) { // {{{ "Datapoints": datapoints, } page.Render(w, r) + return } // }}} func pageDatapointEdit(w http.ResponseWriter, r *http.Request) { // {{{ idStr := r.PathValue("id") @@ -734,6 +699,7 @@ func pageDatapointEdit(w http.ResponseWriter, r *http.Request) { // {{{ "Triggers": triggers, } page.Render(w, r) + return } // }}} func actionDatapointUpdate(w http.ResponseWriter, r *http.Request) { // {{{ idStr := r.PathValue("id") @@ -866,6 +832,7 @@ func pageDatapointValues(w http.ResponseWriter, r *http.Request) { // {{{ "TimeTo": timeTo.Format("2006-01-02T15:04:05"), } page.Render(w, r) + return } // }}} func actionDatapointJson(w http.ResponseWriter, r *http.Request) { // {{{ idStr := r.PathValue("id") diff --git a/problem.go b/problem.go index d5513dd..1d4390d 100644 --- a/problem.go +++ b/problem.go @@ -3,13 +3,11 @@ package main import ( // External werr "git.ahall.se/go/wrappederror" - "github.com/jackc/pgx/v5" // Standard "context" "database/sql" "encoding/json" - "errors" "fmt" "sort" "strings" @@ -154,7 +152,7 @@ func ProblemClose(trigger Trigger) (problemID int, err error) { // {{{ ) err = row.Scan(&problemID) - if errors.Is(err, pgx.ErrNoRows) { + if err == sql.ErrNoRows { err = nil return } diff --git a/sql/00028.sql b/sql/00028.sql deleted file mode 100644 index 3350ded..0000000 --- a/sql/00028.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE public.dashboard ( - id serial NOT NULL, - "name" varchar NOT NULL, - widgets jsonb DEFAULT '{}' NOT NULL, - CONSTRAINT dashboard_pk PRIMARY KEY (id), - CONSTRAINT dashboard_name_unique UNIQUE ("name") -); diff --git a/static/css/gruvbox/gruvbox.css b/static/css/gruvbox/gruvbox.css index e041022..6acdbc0 100644 --- a/static/css/gruvbox/gruvbox.css +++ b/static/css/gruvbox/gruvbox.css @@ -1,5 +1,5 @@ body { - /* background-image: url(/images/v0/gruvbox/background.svg); */ + background-image: url(/images/v0/gruvbox/background.svg); } #menu { box-shadow: 2px 0px 5px 3px rgba(0, 0, 0, 0.25); diff --git a/static/images/grid_management.svg b/static/images/grid_management.svg deleted file mode 100644 index be8a424..0000000 --- a/static/images/grid_management.svg +++ /dev/null @@ -1,1123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/static/images/widget_light_green.svg b/static/images/widget_light_green.svg deleted file mode 100644 index a0bc5a5..0000000 --- a/static/images/widget_light_green.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - diff --git a/static/images/widget_light_off.svg b/static/images/widget_light_off.svg deleted file mode 100644 index bcaed9f..0000000 --- a/static/images/widget_light_off.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - diff --git a/static/images/widget_light_red.svg b/static/images/widget_light_red.svg deleted file mode 100644 index 063f076..0000000 --- a/static/images/widget_light_red.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - diff --git a/static/js/dashboard.mjs b/static/js/dashboard.mjs deleted file mode 100644 index cd00746..0000000 --- a/static/js/dashboard.mjs +++ /dev/null @@ -1,805 +0,0 @@ -import { CustomHTMLElement } from './lib/custom_html_element.mjs' - -const WIDGET_SPACE = 0 -const WIDGET_LABEL = 1 -const WIDGET_ONOFF = 2 - -export class SmonDashboard extends CustomHTMLElement {// {{{ - static {// {{{ - this.tmpl = document.createElement('template') - this.tmpl.innerHTML = ` - - -
- - - - -
- -
-
-
- - -
Cell operations
-
-
- - - - -
- -
- - - - -
- -
- - - -
- -
-
-
- -
- -
- ` - }// }}} - constructor() {// {{{ - super(true) - const script = this.querySelector('script') - this.data = JSON.parse(script.textContent) - this.widgets = new Map() // keyed by datapoint ID, value is an array of widgets. - this.addColumns = 0 - this.addRows = 0 - this.editedWidget = null - this.widgetsEdited = false - - this.elEditDashboard.addEventListener('click', () => this.classList.toggle('edit')) - this.elAddColumns.addEventListener('click', () => { this.addColumns += 5; this.render() }) - this.elAddRows.addEventListener('click', () => { this.addRows += 5; this.render() }) - this.elUpdateDashboard.addEventListener('click', () => this.updateDashboard()) - this.elEditUpdate.addEventListener('click', () => this.editUpdate()) - - this.elGridAddLeft.addEventListener('click', () => this.gridAddLeft()) - this.elGridAddRight.addEventListener('click', () => this.gridAddRight()) - this.elGridAddAbove.addEventListener('click', () => this.gridAddAbove()) - this.elGridAddBelow.addEventListener('click', () => this.gridAddBelow()) - - this.elGridRemove.addEventListener('click', () => this.gridRemove()) - this.elGridRemoveMoveLeft.addEventListener('click', () => this.gridRemoveMoveLeft()) - this.elGridRemoveMoveUp.addEventListener('click', () => this.gridRemoveMoveUp()) - - this.elGridAddColumnLeft.addEventListener('click', () => this.gridAddColumnLeft()) - this.elGridAddColumnAbove.addEventListener('click', () => this.gridAddColumnAbove()) - this.elGridRemoveColumn.addEventListener('click', () => this.gridRemoveColumn()) - this.elGridRemoveRow.addEventListener('click', () => this.gridRemoveRow()) - - this.render() - this.fetchData() // Retrieve one as fast as possible since setInterval doesn't run before an interval. - setInterval(() => this.fetchData(), 1000) - }// }}} - - render() {// {{{ - const widgets = [] - this.widgets = new Map() - this.occupiedCells = new Map() - - let maxX = 0 - let maxY = 0 - - for (const wd of this.data.Widgets) { - if (!this.widgets.has(wd.DatapointID)) - this.widgets.set(wd.DatapointID, []) - // widgetRefs modifies the array in the map directly. - const widgetRefs = this.widgets.get(wd.DatapointID) - const widget = SmonWidget.create(wd.Type, wd) - widget.addEventListener('click', () => this.editCell(widget)) - widgetRefs.push(widget) - widgets.push(widget) - - maxX = Math.max(maxX, wd.X) - maxY = Math.max(maxY, wd.Y) - - this.occupiedCells.set(`${wd.X}x${wd.Y}`, true) - for (let x = 0; x <= wd.SpanX; x++) - this.occupiedCells.set(`${wd.X + x}x${wd.Y}`, true) - for (let y = 0; y <= wd.SpanY; y++) - this.occupiedCells.set(`${wd.X}x${wd.Y + y}`, true) - } - - maxX += this.addColumns - maxY += this.addRows - - // Empty elements are created to fill up the empty cells in the grid. - // This is needed as placeholders for grid and dragdrop targets when editing. - for (let x = 1; x <= maxX; x++) - for (let y = 1; y <= maxY; y++) { - if (this.occupiedCells.has(`${x}x${y}`)) - continue - - const empty = new SmonWidgetEmpty({ X: x, Y: y }) - empty.addEventListener('click', () => this.editCell(empty)) - widgets.push(empty) - } - - this.elUpdateDashboard.disabled = !this.widgetsEdited - - this.elGrid.replaceChildren(...widgets) - }// }}} - error(msg) {// {{{ - this.elError.innerText = msg - if (msg === '') - this.elError.classList.remove('show') - else - this.elError.classList.add('show') - }// }}} - async editUpdate() {// {{{ - const changes = await this.editedWidget.editUpdate() - this.widgetsEdited = true - - let replacedWidget = false - if (changes.add) { - const coords = `${changes.add.data.X}x${changes.add.data.Y}` - - for (const w of this.data.Widgets) { - if (w.X === changes.add.data.X && w.Y === changes.add.data.Y) { - alert(`A widget already exist at ${coords}`) - return - } - } - - if (!replacedWidget) - this.data.Widgets.push(changes.add.data) - } - - this.elEdit.close() - this.editedWidget = null - this.render() - }// }}} - editCell(el) {// {{{ - if (!this.classList.contains('edit')) - return - - this.editedWidget = el - const components = el.edit() - this.elEditComponents.replaceChildren(...components) - this.elEdit.showModal() - }// }}} - async fetchData() {// {{{ - try { - const res = await fetch(`/dashboard/values`) - const json = await res.json() - - if (!json.OK) - throw new Error(json.Error) - - for (const dp of json.Values) { - const widgets = this.widgets.get(dp.ID) || [] - for (const w of widgets) { - w.setValue(dp.Value, dp.Valid) - w.render() - } - } - - this.error('') - } catch (e) { - console.error(e) - this.error(e.message) - - // All widgets are set in a NULL state to visualize the error. - const allDpIDs = this.widgets.keys() - for (const id of allDpIDs) { - const widgets = this.widgets.get(id) || [] - for (const w of widgets) { - w.setValue(null, false) - w.render() - } - } - } - }// }}} - async updateDashboard() {// {{{ - try { - const res = await fetch('/dashboard/update', { - method: 'POST', - body: JSON.stringify(this.data), - }) - const json = await res.json() - if (!json.OK) - throw new Error(json.Error) - - this.widgetsEdited = false - this.render() - } catch (e) { - console.error(e) - alert(e.message) - } - }// }}} - - gridEdit() {// {{{ - this.widgetsEdited = true - this.elEdit.close() - this.editedWidget = null - this.render() - }// }}} - gridAddLeft() {// {{{ - const fromX = this.editedWidget.data.X - const fromY = this.editedWidget.data.Y - for (const w of this.data.Widgets) - if (w.X >= fromX && w.Y == fromY) - w.X++ - - this.gridEdit() - }// }}} - gridAddRight() {// {{{ - const fromX = this.editedWidget.data.X - const fromY = this.editedWidget.data.Y - for (const w of this.data.Widgets) - if (w.X > fromX && w.Y == fromY) - w.X++ - - this.gridEdit() - }// }}} - gridAddAbove() {// {{{ - const fromX = this.editedWidget.data.X - const fromY = this.editedWidget.data.Y - for (const w of this.data.Widgets) - if (w.X == fromX && w.Y >= fromY) - w.Y++ - - this.gridEdit() - }// }}} - gridAddBelow() {// {{{ - const fromX = this.editedWidget.data.X - const fromY = this.editedWidget.data.Y - for (const w of this.data.Widgets) - if (w.X == fromX && w.Y > fromY) - w.Y++ - - this.gridEdit() - }// }}} - gridAddColumnLeft() {// {{{ - const fromX = this.editedWidget.data.X - for (const w of this.data.Widgets) - if (w.X >= fromX) - w.X++ - - this.gridEdit() - }// }}} - gridAddColumnAbove() {// {{{ - const fromY = this.editedWidget.data.Y - for (const w of this.data.Widgets) - if (w.Y >= fromY) - w.Y++ - - this.gridEdit() - }// }}} - gridRemoveColumn() {// {{{ - // Keep all widgets that's not on the edited widget's X coordinate. - this.data.Widgets = this.data.Widgets.filter(w => - w.X != this.editedWidget.data.X - ) - - for (const w of this.data.Widgets) - if (w.X >= this.editedWidget.data.X) - w.X-- - this.gridEdit() - }// }}} - gridRemoveRow() {// {{{ - // Keep all widgets that's not on the edited widget's X coordinate. - this.data.Widgets = this.data.Widgets.filter(w => - w.Y != this.editedWidget.data.Y - ) - - for (const w of this.data.Widgets) - if (w.Y >= this.editedWidget.data.Y) - w.Y-- - this.gridEdit() - }// }}} - - gridRemove() {// {{{ - const fromX = this.editedWidget.data.X - const fromY = this.editedWidget.data.Y - - this.data.Widgets = this.data.Widgets.filter(w => - w.X != fromX || w.Y != fromY - ) - this.gridEdit() - }// }}} - gridRemoveMoveLeft() {// {{{ - const fromX = this.editedWidget.data.X - const fromY = this.editedWidget.data.Y - - this.data.Widgets = this.data.Widgets.filter(w => - w.X != fromX || w.Y != fromY - ) - - for (const w of this.data.Widgets) - if (w.X > fromX && w.Y == fromY) - w.X-- - - this.gridEdit() - }// }}} - gridRemoveMoveUp() {// {{{ - const fromX = this.editedWidget.data.X - const fromY = this.editedWidget.data.Y - - this.data.Widgets = this.data.Widgets.filter(w => - w.X != fromX || w.Y != fromY - ) - - for (const w of this.data.Widgets) - if (w.X == fromX && w.Y > fromY) - w.Y-- - - this.gridEdit() - }// }}} -}// }}} - -class SmonWidget extends CustomHTMLElement {// {{{ - static create(t, data) {// {{{ - data.Type = t - switch (t) { - case WIDGET_SPACE: return new SmonWidgetSpace(data) - case WIDGET_LABEL: return new SmonWidgetLabel(data) - case WIDGET_ONOFF: return new SmonWidgetOnOff(data) - default: - alert(`Unknown widget type: ${t} (${typeof t})`) - } - }// }}} - constructor(data) {// {{{ - super(true) - - this.data = data - this.value = null - this.editDialog = null - - if (!this.data.Attributes) - this.data.Attributes = {} - }// }}} - - render() {// {{{ - this.style.gridColumn = this.data.X - this.style.gridRow = this.data.Y - - if (this.data.SpanX > 0) - this.style.gridColumn = `${this.data.X} / ${this.data.X + this.data.SpanX + 1}` - - if (this.data.SpanY > 0) - this.style.gridRow = `${this.data.Y} / ${this.data.Y + this.data.SpanY + 1}` - - if (this.data.Attributes.BorderTop) { - if (this.data.Attributes.Color) - this.style.borderTop = `1px solid ${this.data.Attributes.Color}` - else - this.style.borderTop = `1px solid currentColor` - } - - if (this.data.Attributes.BorderBottom) { - if (this.data.Attributes.Color) - this.style.borderBottom = `1px solid ${this.data.Attributes.Color}` - else - this.style.borderBottom = `1px solid currentColor` - } - - if (this.data.Attributes.BorderLeft) { - if (this.data.Attributes.Color) - this.style.borderLeft = `1px solid ${this.data.Attributes.Color}` - else - this.style.borderLeft = `1px solid currentColor` - } - - if (this.data.Attributes.BorderRight) { - if (this.data.Attributes.Color) - this.style.borderRight = `1px solid ${this.data.Attributes.Color}` - else - this.style.borderRight = `1px solid currentColor` - } - }// }}} - setValue(v, valid) {// {{{ - this.value = valid ? v : null - }// }}} - edit() {// {{{ - if (this.editDialog !== null) - return - - const components = this.commonEditWidgets() - components.push(...this.editWidget()) - return components - }// }}} - commonEditWidgets() {// {{{ - this.editDiv = document.createElement('div') - this.editDiv.innerHTML = ` -
-
-
Borders
-
- - - - -
-
- -
-
Spanning
-
-
X
- - -
Y
- -
-
- -
-
Font
-
-
Size
- - -
Color
-
- - -
-
-
-
- ` - this.editDiv.style.marginBottom = '16px' - - this.elBorderTop = this.editDiv.querySelector('input[name="top"]') - this.elBorderBottom = this.editDiv.querySelector('input[name="bottom"]') - this.elBorderLeft = this.editDiv.querySelector('input[name="left"]') - this.elBorderRight = this.editDiv.querySelector('input[name="right"]') - this.elSpanX = this.editDiv.querySelector('input[name="span-x"]') - this.elSpanY = this.editDiv.querySelector('input[name="span-y"]') - this.elFontSize = this.editDiv.querySelector('input[name="font-size"]') - this.elUseColor = this.editDiv.querySelector('input[name="use-color"]') - this.elColor = this.editDiv.querySelector('input[name="color"]') - this.elColor.addEventListener('input', () => this.elUseColor.checked = true) - - this.elBorderTop.checked = this.data.Attributes.BorderTop - this.elBorderBottom.checked = this.data.Attributes.BorderBottom - this.elBorderLeft.checked = this.data.Attributes.BorderLeft - this.elBorderRight.checked = this.data.Attributes.BorderRight - - this.elSpanX.value = this.data.SpanX - this.elSpanY.value = this.data.SpanY - - this.elFontSize.value = this.data.Attributes.Size || '' - this.elUseColor.checked = this.data.Attributes.Color ? true : false - this.elColor.value = this.data.Attributes.Color || '' - - return [this.editDiv] - }// }}} - - // edit dialog wants to update the widget. - async editUpdate() {// {{{ - this.data.Attributes.BorderTop = this.elBorderTop.checked ? 'true' : '' - this.data.Attributes.BorderBottom = this.elBorderBottom.checked ? 'true' : '' - this.data.Attributes.BorderLeft = this.elBorderLeft.checked ? 'true' : '' - this.data.Attributes.BorderRight = this.elBorderRight.checked ? 'true' : '' - - this.data.SpanX = parseInt(this.elSpanX.value) - this.data.SpanY = parseInt(this.elSpanY.value) - - this.data.Attributes.Size = this.elFontSize.value - this.data.Attributes.Color = this.elUseColor.checked ? this.elColor.value : '' - - return await this.updateWidget() - }// }}} - - // Many widgets can have labels. - // This function applies common attributes like size and color. - applyLabelAttributes(el) {// {{{ - if (this.data.Attributes?.Size) - el.style.fontSize = this.data.Attributes.Size - - if (this.data.Attributes?.Color) - el.style.color = this.data.Attributes.Color - - if (this.data.Attributes?.Underline) - if (this.data.Attributes?.Color) - el.style.borderBottom = `1px solid ${this.data.Attributes.Color}` - else - el.style.borderBottom = `1px solid currentColor` - }// }}} - -}// }}} -class SmonWidgetEmpty extends SmonWidget {// {{{ - static {// {{{ - this.tmpl = document.createElement('template') - this.tmpl.innerHTML = ` - - ` - }// }}} - constructor(data) {// {{{ - super(data) - this.render() - }// }}} - render() {// {{{ - super.render() - }// }}} - editWidget() {// {{{ - this.editDiv = document.createElement('div') - this.editDiv.innerHTML = ` -
Add widget
- - ` - return [this.editDiv] - }// }}} - async updateWidget() {// {{{ - const t = parseInt(this.editDiv.querySelector('select').value) - const widget = SmonWidget.create(t, JSON.parse(JSON.stringify(this.data))) - return { add: widget } - }// }}} -}// }}} -class SmonWidgetSpace extends SmonWidget {// {{{ - static {// {{{ - this.tmpl = document.createElement('template') - this.tmpl.innerHTML = ` -
- ` - }// }}} - constructor(data) {// {{{ - super(data) - this.render() - }// }}} - render() {// {{{ - super.render() - - this.elSpacer.style.minWidth = this.data.Attributes.Width || '16px' - this.elSpacer.style.minHeight = this.data.Attributes.Height || '16px' - }// }}} - editWidget() {// {{{ - this.editDiv = document.createElement('div') - this.editDiv.innerHTML = ` -
Spacing
-
-
Width
- - -
Height
- -
- ` - - this.elWidth = this.editDiv.querySelector('[name="width"]') - this.elHeight = this.editDiv.querySelector('[name="height"]') - - this.elWidth.value = this.data.Attributes?.Width || '' - this.elHeight.value = this.data.Attributes?.Height || '' - - return [this.editDiv] - }// }}} - async updateWidget() {// {{{ - this.data.Attributes.Width = this.elWidth.value - this.data.Attributes.Height = this.elHeight.value - return {} - }// }}} -}// }}} -class SmonWidgetLabel extends SmonWidget {// {{{ - static {// {{{ - this.tmpl = document.createElement('template') - this.tmpl.innerHTML = ` - -
- ` - }// }}} - constructor(data) {// {{{ - super(data) - this.render() - }// }}} - render() {// {{{ - super.render() - this.elLabel.innerText = this.data.Attributes?.Label || '[set label]' - this.applyLabelAttributes(this.elLabel) - }// }}} - editWidget() {// {{{ - this.editDiv = document.createElement('div') - this.editDiv.innerHTML = ` -
Label
- - ` - this.editDiv.querySelector('input').value = this.data.Attributes.Label || '[set label]' - return [this.editDiv] - }// }}} - async updateWidget() {// {{{ - this.data.Attributes.Label = this.editDiv.querySelector('input').value - return {} - }// }}} -}// }}} -class SmonWidgetOnOff extends SmonWidget {// {{{ - static {// {{{ - this.tmpl = document.createElement('template') - this.tmpl.innerHTML = ` - - -
- ` - }// }}} - constructor(data) {// {{{ - super(data) - this.render() - }// }}} - render() {// {{{ - super.render() - - let img - switch (this.value) { - case 0: - case 'OFF': - img = 'widget_light_red.svg' - break - - case 1: - case 'ON': - img = 'widget_light_green.svg' - break - - default: - img = 'widget_light_off.svg' - } - - this.elStatus.setAttribute('src', `/images/${_VERSION}/${img}`) - this.elLabel.innerText = this.data.Attributes.Label || '[set label]' - this.applyLabelAttributes(this.elLabel) - }// }}} - editWidget() {// {{{ - this.editDiv = document.createElement('div') - this.editDiv.innerHTML = ` -
Label
- - ` - this.editDiv.querySelector('input').value = this.data.Attributes.Label || '[set label]' - return [this.editDiv] - }// }}} - async updateWidget() {// {{{ - this.data.Attributes.Label = this.editDiv.querySelector('input').value - return {} - }// }}} -}// }}} - -customElements.define("smon-widget", SmonWidget) -customElements.define("smon-widget-empty", SmonWidgetEmpty) -customElements.define("smon-widget-space", SmonWidgetSpace) -customElements.define("smon-widget-label", SmonWidgetLabel) -customElements.define("smon-widget-onoff", SmonWidgetOnOff) -customElements.define("smon-dashboard", SmonDashboard) diff --git a/static/js/lib/custom_html_element.mjs b/static/js/lib/custom_html_element.mjs deleted file mode 100644 index d1fb7ae..0000000 --- a/static/js/lib/custom_html_element.mjs +++ /dev/null @@ -1,51 +0,0 @@ -/* Use data-el or data-field attribute. - * Element with data-el="hum-ding" is accessible as this.elHumDing and fields with - * data-field="long-dong" as this.fieldLongDong. - * - * All field values can be retrieved with fieldValues() and uses the data-field attribute - * as LongDong as key. - */ - -export class CustomHTMLElement extends HTMLElement { - constructor(useShadow) {// {{{ - super() - - this._fields = new Map() - - const workOn = useShadow ? this.attachShadow({ mode: 'open' }) : this - workOn.appendChild(this.constructor.tmpl.content.cloneNode(true)) - workOn.querySelectorAll('*').forEach(el => { - const field = el.dataset.field - if (field !== undefined) { - const fieldName = this.toElementName('field', field) - this[fieldName] = el - this._fields.set(this.toElementName('', field), el) - } - - const name = el.dataset.el - if (name !== undefined) { - const elName = this.toElementName('el', name) - this[elName] = el - el.classList.add('el-' + name) - } - }) - }// }}} - allFields() {// {{{ - return this._fields - }// }}} - fieldValues() {// {{{ - const state = {} - for (const [name, field] of this._fields) { - if (field.tagName.toLowerCase() == 'input' && field.getAttribute('type').toLowerCase() == 'checkbox') - state[name] = field.checked - else - state[name] = field.value - - } - return state - }// }}} - toElementName(prefix, str) {// {{{ - str = prefix + '-' + str - return str.replace(/-(id|[a-z])/g, match => match.toUpperCase().replace('-', '')) - }// }}} -} diff --git a/views/layouts/main.gotmpl b/views/layouts/main.gotmpl index 263d61f..b82b49f 100644 --- a/views/layouts/main.gotmpl +++ b/views/layouts/main.gotmpl @@ -6,9 +6,6 @@ {{ template "fonts" }} -
diff --git a/views/pages/index.gotmpl b/views/pages/index.gotmpl index 8d1ead5..f5d7056 100644 --- a/views/pages/index.gotmpl +++ b/views/pages/index.gotmpl @@ -1,19 +1,13 @@ {{ define "page" }} - - -
- -
-

Smon

-

{{ .VERSION }}

-
+
+
- - - +
+

SMon

+

{{ .VERSION }}

+ +
{{ end }}