Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/content/docs/docs/create-a-widget/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ A widget that renders entirely in the browser, like [Clock](/docs/widgets/clock/
| `ctx.KIND` | `AUTH`, `INVALID`, `UPSTREAM`, `NETWORK`, `TIMEOUT`, `BLOCKED`, `INTERNAL`. See [API errors](/docs/contributing/api-errors/). |
| `ctx.log` | The structured logger. |

Keep every upstream call behind `ctx.fetchJSON`. It applies the SSRF guard, IP pinning, the size limit and the TLS setting.
Keep every upstream call behind `ctx.fetchJSON`. It applies the size limit and the TLS setting. An `optionsFrom` fetch also applies the SSRF guard and IP pinning, because its config arrives with the request. A refresh reads the saved config and is not guarded. Never build a URL from `ctx.params`.

Await every call. A rejected promise that nothing awaits stops the API process, and every widget fails until it restarts.

### Metrics and XML bodies

Expand Down
4 changes: 2 additions & 2 deletions src/content/docs/docs/create-a-widget/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ cp -r docs/widget-template ui/widgets/mywidget
"fields": [
{ "key": "url", "type": "text", "label": "Service URL", "placeholder": "http://host:port" },
{ "key": "apiKey", "type": "secret", "label": "API key", "optional": true },
{ "key": "showTotal", "type": "toggle", "label": "Show total", "default": true }
{ "key": "showTotal", "type": "toggle", "label": "Show total", "default": true, "cosmetic": true }
]
}
```

`name` must match the folder. Each field becomes a row in the widget's settings. A `secret` never returns to the browser. A `toggle` that was never saved is absent from `ctx.config`, so test for `false`. See [Manifest](/docs/create-a-widget/manifest/).
`name` must match the folder. Each field becomes a row in the widget's settings. A `secret` never returns to the browser. `cosmetic` marks a field that can change without asking for the secret again. A `toggle` that was never saved is absent from `ctx.config`, so test for `false`. See [Manifest](/docs/create-a-widget/manifest/).

## 3. Data

Expand Down
4 changes: 3 additions & 1 deletion src/content/docs/docs/create-a-widget/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ A widget with no light design declares it, and keeps its dark card and dark inte
| --- | --- |
| `text` | An inline-edit row. |
| `number` | An inline-edit row that stores a number. |
| `secret` | An inline-edit row for a masked value. Shows `Configured` once set. The value stays on the server. Leaving it blank keeps the stored value. |
| `secret` | An inline-edit row for a masked value. Shows `Configured` once set. The value stays on the server. Leaving it blank keeps the stored value, unless a field that is not `cosmetic` changed. |
| `toggle` | An on and off switch, stored as a boolean. |
| `color` | The swatch and colour control used elsewhere in the admin. Saves `#rrggbb`. |
| `select` | A dropdown. `"variant": "pills"` renders a radio group. With `optionsFrom` it adds a Fetch button. |
Expand All @@ -125,9 +125,11 @@ A widget with no light design declares it, and keeps its dark card and dark inte
| `default` | The value used when none is saved. |
| `hint` | Short help under the field. On a `group`, it shows at the bottom of the section. |
| `optional` | When `true`, the field is not required to save. A required `secret` counts as missing only when nothing is stored. |
| `cosmetic` | When `true`, changing the field keeps a stored `secret`. Use it only on a field that cannot change the server a secret is sent to, the account it is paired with, or a path on a user-entered server. Links, labels, colours and units qualify. A repeated key needs it on every declaration. The keys a cosmetic picker `carries` count as cosmetic too. |
| `transient` | When `true`, the field is sent to an `optionsFrom` fetch but not saved. Use it for a search box. Top-level fields only. |
| `carries` | For a `select` with `optionsFrom`: extra config keys this picker writes, from the chosen option's `set` block. |
| `showIf` | Shows the field only when a sibling matches: `{ "field": "provider", "equals": "adguard" }`, or `{ "field": "provider", "in": ["adguard", "pihole"] }`. Anything else is rejected. |
| `options` | For a `select`, `multiselect` or `picklist`: an array of `{ "value", "label" }` objects. Any other entry is rejected. |
| `optionsFrom` | For a `select`: the data endpoint that returns the options at config time. |
| `variant` | For a `select`: `"pills"` renders a radio group. |
| `min`, `max` | For a `group`: the fewest and most entries. |
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The first page load after install or upgrade sets the list. A host name is trust

Stored secrets are stripped from the config before it reaches the browser. A populated field reports as set without returning its value, in config responses and in exports alike.

A secret is restored only for the request it was stored for. If a save changes where the credential would be sent, by editing a badge URL, its non-secret headers, or any non-secret field of a widget's config, the stored value is not restored and Stackyard names what must be re-entered. Matching on an item's id alone would let an imported config point an existing credential somewhere new. Turning **Allow self-signed certificate** on or off for an app, and changing the port map (`settings.server.portMap`), count as such a change.
A secret is restored only for the request it was stored for. If a save changes where the credential would be sent, by editing a badge URL, its non-secret headers, or a widget field that decides where it goes, the stored value is not restored and Stackyard names what must be re-entered. Matching on an item's id alone would let an imported config point an existing credential somewhere new. Turning **Allow self-signed certificate** on or off for an app, and changing the port map (`settings.server.portMap`), count as such a change.

Secrets are stored in plain text in `apps.json` on the data volume. Protect that volume with filesystem permissions and backups.

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Check that the service is reachable and the credential is still valid.

### A credential stopped working after an edit

Changing where or how a credential would be sent clears the stored value. Editing a badge URL, its non-secret headers, or a non-secret field of a widget's config all count. So do turning **Allow self-signed certificate** on or off for an app, and changing the port map (`settings.server.portMap`) in the config. Unticking **Secret** on a header row clears it on the next save.
Changing where or how a credential would be sent clears the stored value. Editing a badge URL, its non-secret headers, or a widget field that decides where the credential goes all count. Display settings such as units, colours and links do not. So do turning **Allow self-signed certificate** on or off for an app, and changing the port map (`settings.server.portMap`) in the config. Unticking **Secret** on a header row clears it on the next save.

Stackyard names the items to re-enter. Enter the credential again and save.

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/docs/widgets/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Widgets reorder in the same list as apps and folders. See [Apps and folders](/do

A field marked Secret is stored on the server and never sent back to the browser. It shows as set without revealing its value.

Changing where a credential would be sent, by editing a URL or a non-secret field, clears the stored value, and Stackyard names what to re-enter. This stops an imported config from taking a credential out of an install.
Changing where a credential would be sent, by editing a URL, an account or another field that decides where it goes, clears the stored value, and Stackyard names what to re-enter. This stops an imported config from taking a credential out of an install.

## Writing a widget

Expand Down
Loading