From 1f8d247c8cc87a44f12d97f2ce217e331128c2e5 Mon Sep 17 00:00:00 2001 From: liss-bot Date: Sat, 4 Jul 2026 17:15:02 +0000 Subject: [PATCH] deploy: 9a5ed7a54c50f4d819e5b89487e2b8030a3df4f0 --- 404.html | 8 +-- api/index.html | 8 +-- assets/css/styles.283a0681.css | 1 + assets/css/styles.39682e6f.css | 1 - ...d5aa6.1c171b49.js => 044d5aa6.5a03dbc5.js} | 2 +- ...b4c8f.6ce48445.js => 052b4c8f.92ca9ad0.js} | 2 +- ...4bce0.ba1aa4e6.js => 0a44bce0.e0a74859.js} | 2 +- ...e2fe2.c3bef218.js => 0bfe2fe2.03e2b087.js} | 2 +- ...88f16.28567404.js => 15788f16.e089cc89.js} | 2 +- ...5c531.2ef3fa78.js => 1745c531.8e24c3b8.js} | 2 +- ...a09e8.de3730e3.js => 18ba09e8.add4f6e6.js} | 2 +- ...fb0df.9039ab38.js => 1f5fb0df.cba70676.js} | 2 +- ...0bf91.05609c7f.js => 2140bf91.07f53cf1.js} | 2 +- ...9d47d.afa54dc8.js => 27d9d47d.2076536d.js} | 2 +- ...0607c.443d382a.js => 4120607c.a6f09d70.js} | 2 +- .../js/{4809.2b896511.js => 4809.a561fd4b.js} | 2 +- ...b0d83.4d7bfd68.js => 4bdb0d83.c08fcb6c.js} | 2 +- ...4d076.ddbd54a2.js => 4d54d076.089aab94.js} | 2 +- ...2a80b.8317f95c.js => 52e2a80b.fe45a810.js} | 2 +- ...c5cf1.b3c8efcf.js => 5fbc5cf1.f0eeee44.js} | 2 +- ...b1ed7.5fc1eb7c.js => 625b1ed7.90a913e6.js} | 2 +- ...50eef.76dcb182.js => 68d50eef.ab01a67b.js} | 2 +- ...14192.ad6976a9.js => 72e14192.9ace4040.js} | 2 +- ...6d47a.68fccae7.js => 7776d47a.2e144c54.js} | 2 +- ...1faeb.0845962f.js => 8051faeb.5111aada.js} | 2 +- ...908d2.ac570d16.js => 83d908d2.ef084f2e.js} | 2 +- ...24b4e.6737270d.js => 87124b4e.a107ba1f.js} | 2 +- ...a5e38.6221b85a.js => 88ca5e38.b0775334.js} | 2 +- ...62683.d340793c.js => 90a62683.a64be9fa.js} | 2 +- ...185c1.f7acbc46.js => 9b4185c1.a8865e57.js} | 2 +- assets/js/9be9f402.139a9412.js | 1 - assets/js/9be9f402.fb229c7e.js | 1 + ...f8394.bf258cb4.js => 9d9f8394.443c5822.js} | 2 +- ...13279.75a2e5b1.js => a3713279.8b3d526e.js} | 2 +- ...906ba.2ca85396.js => a63906ba.1bcf6e0c.js} | 2 +- ...280f2.094114e0.js => ac5280f2.1805cbad.js} | 2 +- ...8a330.ca64f775.js => caf8a330.e0a4bf28.js} | 2 +- ...75163.2e37952c.js => d8075163.d55778ce.js} | 2 +- ...5b3a5.625eeae2.js => e505b3a5.fde2408b.js} | 2 +- ...eded4.a5cc1630.js => e92eded4.65f47532.js} | 2 +- ...99be6.4a413e20.js => e9599be6.bd3c3bfb.js} | 2 +- ...ebc2d.73042edb.js => f52ebc2d.f5dc9353.js} | 2 +- ...d4472.3a25b056.js => f62d4472.6d8d78c6.js} | 2 +- ...3b146.19bca188.js => f853b146.04e98266.js} | 2 +- ...76190.a03d246a.js => f9f76190.0c5331be.js} | 2 +- ...31120.733cbaef.js => fe531120.77272f6d.js} | 2 +- ...b1e34.37c385c9.js => ff8b1e34.2be0e67c.js} | 2 +- ...4f506.9b4c8790.js => ffd4f506.4b754715.js} | 2 +- assets/js/main.249792b4.js | 2 + ...CENSE.txt => main.249792b4.js.LICENSE.txt} | 0 assets/js/main.d8a3f468.js | 2 - ...n.577949f2.js => runtime~main.17c323d8.js} | 2 +- blog/index.html | 8 +-- docs/alternate-views/index.html | 10 ++-- docs/api/index.html | 10 ++-- docs/authentication/authelia-oidc/index.html | 10 ++-- docs/authentication/authentik/index.html | 10 ++-- docs/authentication/built-in/index.html | 10 ++-- .../cloudflare-tunnel/index.html | 10 ++-- docs/authentication/header-auth/index.html | 10 ++-- docs/authentication/index.html | 10 ++-- docs/authentication/keycloak/index.html | 10 ++-- docs/authentication/oidc/index.html | 10 ++-- .../other-auth-methods/index.html | 10 ++-- docs/authentication/pocketid/index.html | 10 ++-- docs/authentication/tailscale/index.html | 10 ++-- docs/authentication/zitadel/index.html | 10 ++-- docs/backup-restore/index.html | 10 ++-- docs/configuring/index.html | 10 ++-- docs/contributing/index.html | 10 ++-- docs/credits/index.html | 10 ++-- docs/deployment/bare-metal/index.html | 10 ++-- docs/deployment/cloud/index.html | 10 ++-- docs/deployment/docker/index.html | 10 ++-- docs/deployment/index.html | 10 ++-- docs/deployment/self-hosted-os/index.html | 10 ++-- .../self-hosted-platforms/index.html | 10 ++-- docs/developing/index.html | 10 ++-- docs/development-guides/index.html | 10 ++-- docs/icons/index.html | 10 ++-- docs/index.html | 8 +-- docs/license/index.html | 10 ++-- docs/management/index.html | 10 ++-- docs/multi-language-support/index.html | 10 ++-- docs/pages-and-sections/index.html | 10 ++-- docs/privacy/index.html | 10 ++-- docs/quick-start/index.html | 10 ++-- docs/release-workflow/index.html | 10 ++-- docs/searching/index.html | 10 ++-- docs/showcase/index.html | 10 ++-- docs/status-indicators/index.html | 10 ++-- docs/theming/index.html | 10 ++-- docs/troubleshooting/index.html | 10 ++-- docs/widgets/index.html | 55 ++++++++++++++----- index.html | 8 +-- markdown-page/index.html | 8 +-- rss.xml | 10 +++- search-index.json | 2 +- search/index.html | 8 +-- survey/index.html | 8 +-- updates/index.html | 8 +-- 101 files changed, 329 insertions(+), 294 deletions(-) create mode 100644 assets/css/styles.283a0681.css delete mode 100644 assets/css/styles.39682e6f.css rename assets/js/{044d5aa6.1c171b49.js => 044d5aa6.5a03dbc5.js} (99%) rename assets/js/{052b4c8f.6ce48445.js => 052b4c8f.92ca9ad0.js} (99%) rename assets/js/{0a44bce0.ba1aa4e6.js => 0a44bce0.e0a74859.js} (98%) rename assets/js/{0bfe2fe2.c3bef218.js => 0bfe2fe2.03e2b087.js} (99%) rename assets/js/{15788f16.28567404.js => 15788f16.e089cc89.js} (98%) rename assets/js/{1745c531.2ef3fa78.js => 1745c531.8e24c3b8.js} (93%) rename assets/js/{18ba09e8.de3730e3.js => 18ba09e8.add4f6e6.js} (98%) rename assets/js/{1f5fb0df.9039ab38.js => 1f5fb0df.cba70676.js} (99%) rename assets/js/{2140bf91.05609c7f.js => 2140bf91.07f53cf1.js} (99%) rename assets/js/{27d9d47d.afa54dc8.js => 27d9d47d.2076536d.js} (99%) rename assets/js/{4120607c.443d382a.js => 4120607c.a6f09d70.js} (99%) rename assets/js/{4809.2b896511.js => 4809.a561fd4b.js} (91%) rename assets/js/{4bdb0d83.4d7bfd68.js => 4bdb0d83.c08fcb6c.js} (99%) rename assets/js/{4d54d076.ddbd54a2.js => 4d54d076.089aab94.js} (99%) rename assets/js/{52e2a80b.8317f95c.js => 52e2a80b.fe45a810.js} (98%) rename assets/js/{5fbc5cf1.b3c8efcf.js => 5fbc5cf1.f0eeee44.js} (99%) rename assets/js/{625b1ed7.5fc1eb7c.js => 625b1ed7.90a913e6.js} (99%) rename assets/js/{68d50eef.76dcb182.js => 68d50eef.ab01a67b.js} (99%) rename assets/js/{72e14192.ad6976a9.js => 72e14192.9ace4040.js} (99%) rename assets/js/{7776d47a.68fccae7.js => 7776d47a.2e144c54.js} (99%) rename assets/js/{8051faeb.0845962f.js => 8051faeb.5111aada.js} (99%) rename assets/js/{83d908d2.ac570d16.js => 83d908d2.ef084f2e.js} (99%) rename assets/js/{87124b4e.6737270d.js => 87124b4e.a107ba1f.js} (99%) rename assets/js/{88ca5e38.6221b85a.js => 88ca5e38.b0775334.js} (99%) rename assets/js/{90a62683.d340793c.js => 90a62683.a64be9fa.js} (99%) rename assets/js/{9b4185c1.f7acbc46.js => 9b4185c1.a8865e57.js} (99%) delete mode 100644 assets/js/9be9f402.139a9412.js create mode 100644 assets/js/9be9f402.fb229c7e.js rename assets/js/{9d9f8394.bf258cb4.js => 9d9f8394.443c5822.js} (99%) rename assets/js/{a3713279.75a2e5b1.js => a3713279.8b3d526e.js} (99%) rename assets/js/{a63906ba.2ca85396.js => a63906ba.1bcf6e0c.js} (99%) rename assets/js/{ac5280f2.094114e0.js => ac5280f2.1805cbad.js} (95%) rename assets/js/{caf8a330.ca64f775.js => caf8a330.e0a4bf28.js} (99%) rename assets/js/{d8075163.2e37952c.js => d8075163.d55778ce.js} (99%) rename assets/js/{e505b3a5.625eeae2.js => e505b3a5.fde2408b.js} (99%) rename assets/js/{e92eded4.a5cc1630.js => e92eded4.65f47532.js} (99%) rename assets/js/{e9599be6.4a413e20.js => e9599be6.bd3c3bfb.js} (99%) rename assets/js/{f52ebc2d.73042edb.js => f52ebc2d.f5dc9353.js} (99%) rename assets/js/{f62d4472.3a25b056.js => f62d4472.6d8d78c6.js} (99%) rename assets/js/{f853b146.19bca188.js => f853b146.04e98266.js} (99%) rename assets/js/{f9f76190.a03d246a.js => f9f76190.0c5331be.js} (99%) rename assets/js/{fe531120.733cbaef.js => fe531120.77272f6d.js} (99%) rename assets/js/{ff8b1e34.37c385c9.js => ff8b1e34.2be0e67c.js} (99%) rename assets/js/{ffd4f506.9b4c8790.js => ffd4f506.4b754715.js} (99%) create mode 100644 assets/js/main.249792b4.js rename assets/js/{main.d8a3f468.js.LICENSE.txt => main.249792b4.js.LICENSE.txt} (100%) delete mode 100644 assets/js/main.d8a3f468.js rename assets/js/{runtime~main.577949f2.js => runtime~main.17c323d8.js} (72%) diff --git a/404.html b/404.html index 8aab1264..9c19c877 100644 --- a/404.html +++ b/404.html @@ -11,14 +11,14 @@ - - - + + + -
Skip to main content
404

Page Not Found

The page you're looking for doesn't exist or has been moved. Try navigating back to the docs or homepage.

+
Skip to main content
404

Page Not Found

The page you're looking for doesn't exist or has been moved. Try navigating back to the docs or homepage.

\ No newline at end of file diff --git a/api/index.html b/api/index.html index 596204ad..4737b806 100644 --- a/api/index.html +++ b/api/index.html @@ -11,14 +11,14 @@ - - - + + + -
Skip to main content
Loading API reference…
+
Skip to main content
Loading API reference…
\ No newline at end of file diff --git a/assets/css/styles.283a0681.css b/assets/css/styles.283a0681.css new file mode 100644 index 00000000..cabef989 --- /dev/null +++ b/assets/css/styles.283a0681.css @@ -0,0 +1 @@ +@import url(https://fonts.googleapis.com/css2?family=Racing+Sans+One&family=Poppins:wght@300&family=Roboto+Mono:wght@500;300&display=swap);.col,.container{padding:0 var(--ifm-spacing-horizontal);width:100%}.markdown>h2,.markdown>h3,.markdown>h4,.markdown>h5,.markdown>h6{margin-bottom:calc(var(--ifm-heading-vertical-rhythm-bottom)*var(--ifm-leading))}body,ol ol,ol ul,ul ol,ul ul{margin:0}pre,table{overflow:auto}blockquote,pre{margin:0 0 var(--ifm-spacing-vertical)}.breadcrumbs__link,.button{transition-timing-function:var(--ifm-transition-timing-default)}.button,.hash-link{-webkit-user-select:none}.button,code{vertical-align:middle}.button--outline.button--active,.button--outline:active,.button--outline:hover,:root{--ifm-button-color:var(--ifm-font-color-base-inverse)}.menu__link:hover,a{transition:color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.navbar--dark,:root{--ifm-navbar-link-hover-color:var(--ifm-color-primary)}.menu,.navbar-sidebar{overflow-x:hidden}:root,html[data-theme=dark]{--ifm-color-emphasis-500:var(--ifm-color-gray-500)}#carbonads .carbon-poweredby,#carbonads a,#carbonads a:hover{color:var(--carbon-text-color)}.toggleButton_gllP,html{-webkit-tap-highlight-color:transparent}.markdown li,.operation-path[data-v-ec6c8861],.prompt[data-v-e7c7c266],body{word-wrap:break-word}.scalar-app .scale-0,.scalar-app .scale-100,.scalar-app .scale-75{scale:var(--tw-scale-x) var(--tw-scale-y)}.scalar-app .backdrop-blur,.scalar-app .backdrop-filter{backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.close,.home-page-features-wrapper .feature div.read-the-docs{float:right}:root{--ifm-color-scheme:light;--ifm-dark-value:10%;--ifm-darker-value:15%;--ifm-darkest-value:30%;--ifm-light-value:15%;--ifm-lighter-value:30%;--ifm-lightest-value:50%;--ifm-contrast-background-value:90%;--ifm-contrast-foreground-value:70%;--ifm-contrast-background-dark-value:70%;--ifm-contrast-foreground-dark-value:90%;--ifm-color-primary:#3578e5;--ifm-color-secondary:#ebedf0;--ifm-color-success:#00a400;--ifm-color-info:#54c7ec;--ifm-color-warning:#ffba00;--ifm-color-danger:#fa383e;--ifm-color-primary-dark:#306cce;--ifm-color-primary-darker:#2d66c3;--ifm-color-primary-darkest:#2554a0;--ifm-color-primary-light:#538ce9;--ifm-color-primary-lighter:#72a1ed;--ifm-color-primary-lightest:#9abcf2;--ifm-color-primary-contrast-background:#ebf2fc;--ifm-color-primary-contrast-foreground:#102445;--ifm-color-secondary-dark:#d4d5d8;--ifm-color-secondary-darker:#c8c9cc;--ifm-color-secondary-darkest:#a4a6a8;--ifm-color-secondary-light:#eef0f2;--ifm-color-secondary-lighter:#f1f2f5;--ifm-color-secondary-lightest:#f5f6f8;--ifm-color-secondary-contrast-background:#fdfdfe;--ifm-color-secondary-contrast-foreground:#474748;--ifm-color-success-dark:#009400;--ifm-color-success-darker:#008b00;--ifm-color-success-darkest:#007300;--ifm-color-success-light:#26b226;--ifm-color-success-lighter:#4dbf4d;--ifm-color-success-lightest:#80d280;--ifm-color-success-contrast-background:#e6f6e6;--ifm-color-success-contrast-foreground:#003100;--ifm-color-info-dark:#4cb3d4;--ifm-color-info-darker:#47a9c9;--ifm-color-info-darkest:#3b8ba5;--ifm-color-info-light:#6ecfef;--ifm-color-info-lighter:#87d8f2;--ifm-color-info-lightest:#aae3f6;--ifm-color-info-contrast-background:#eef9fd;--ifm-color-info-contrast-foreground:#193c47;--ifm-color-warning-dark:#e6a700;--ifm-color-warning-darker:#d99e00;--ifm-color-warning-darkest:#b38200;--ifm-color-warning-light:#ffc426;--ifm-color-warning-lighter:#ffcf4d;--ifm-color-warning-lightest:#ffdd80;--ifm-color-warning-contrast-background:#fff8e6;--ifm-color-warning-contrast-foreground:#4d3800;--ifm-color-danger-dark:#e13238;--ifm-color-danger-darker:#d53035;--ifm-color-danger-darkest:#af272b;--ifm-color-danger-light:#fb565b;--ifm-color-danger-lighter:#fb7478;--ifm-color-danger-lightest:#fd9c9f;--ifm-color-danger-contrast-background:#ffebec;--ifm-color-danger-contrast-foreground:#4b1113;--ifm-color-white:#fff;--ifm-color-black:#000;--ifm-color-gray-0:var(--ifm-color-white);--ifm-color-gray-100:#f5f6f7;--ifm-color-gray-200:#ebedf0;--ifm-color-gray-300:#dadde1;--ifm-color-gray-400:#ccd0d5;--ifm-color-gray-500:#bec3c9;--ifm-color-gray-600:#8d949e;--ifm-color-gray-700:#606770;--ifm-color-gray-800:#444950;--ifm-color-gray-900:#1c1e21;--ifm-color-gray-1000:var(--ifm-color-black);--ifm-color-emphasis-0:var(--ifm-color-gray-0);--ifm-color-emphasis-100:var(--ifm-color-gray-100);--ifm-color-emphasis-200:var(--ifm-color-gray-200);--ifm-color-emphasis-300:var(--ifm-color-gray-300);--ifm-color-emphasis-400:var(--ifm-color-gray-400);--ifm-color-emphasis-600:var(--ifm-color-gray-600);--ifm-color-emphasis-700:var(--ifm-color-gray-700);--ifm-color-emphasis-800:var(--ifm-color-gray-800);--ifm-color-emphasis-900:var(--ifm-color-gray-900);--ifm-color-emphasis-1000:var(--ifm-color-gray-1000);--ifm-color-content:var(--ifm-color-emphasis-900);--ifm-color-content-inverse:var(--ifm-color-emphasis-0);--ifm-color-content-secondary:#525860;--ifm-background-color:#0000;--ifm-background-surface-color:var(--ifm-color-content-inverse);--ifm-global-border-width:1px;--ifm-global-radius:0.4rem;--ifm-hover-overlay:#0000000d;--ifm-font-color-base:var(--ifm-color-content);--ifm-font-color-base-inverse:var(--ifm-color-content-inverse);--ifm-font-color-secondary:var(--ifm-color-content-secondary);--ifm-font-family-base:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--ifm-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--ifm-font-size-base:100%;--ifm-font-weight-light:300;--ifm-font-weight-normal:400;--ifm-font-weight-semibold:500;--ifm-font-weight-bold:700;--ifm-font-weight-base:var(--ifm-font-weight-normal);--ifm-line-height-base:1.65;--ifm-global-spacing:1rem;--ifm-spacing-vertical:var(--ifm-global-spacing);--ifm-spacing-horizontal:var(--ifm-global-spacing);--ifm-transition-fast:200ms;--ifm-transition-slow:400ms;--ifm-transition-timing-default:cubic-bezier(0.08,0.52,0.52,1);--ifm-global-shadow-lw:0 1px 2px 0 #0000001a;--ifm-global-shadow-md:0 5px 40px #0003;--ifm-global-shadow-tl:0 12px 28px 0 #0003,0 2px 4px 0 #0000001a;--ifm-z-index-dropdown:100;--ifm-z-index-fixed:200;--ifm-z-index-overlay:400;--ifm-container-width:1140px;--ifm-container-width-xl:1320px;--ifm-code-background:#f6f7f8;--ifm-code-border-radius:var(--ifm-global-radius);--ifm-code-font-size:90%;--ifm-code-padding-horizontal:0.1rem;--ifm-code-padding-vertical:0.1rem;--ifm-pre-background:var(--ifm-code-background);--ifm-pre-border-radius:var(--ifm-code-border-radius);--ifm-pre-color:inherit;--ifm-pre-line-height:1.45;--ifm-pre-padding:1rem;--ifm-heading-color:inherit;--ifm-heading-margin-top:0;--ifm-heading-margin-bottom:var(--ifm-spacing-vertical);--ifm-heading-font-family:var(--ifm-font-family-base);--ifm-heading-font-weight:var(--ifm-font-weight-bold);--ifm-heading-line-height:1.25;--ifm-h1-font-size:2rem;--ifm-h2-font-size:1.5rem;--ifm-h3-font-size:1.25rem;--ifm-h4-font-size:1rem;--ifm-h5-font-size:0.875rem;--ifm-h6-font-size:0.85rem;--ifm-image-alignment-padding:1.25rem;--ifm-leading-desktop:1.25;--ifm-leading:calc(var(--ifm-leading-desktop)*1rem);--ifm-list-left-padding:2rem;--ifm-list-margin:1rem;--ifm-list-item-margin:0.25rem;--ifm-list-paragraph-margin:1rem;--ifm-table-cell-padding:0.75rem;--ifm-table-background:#0000;--ifm-table-stripe-background:#00000008;--ifm-table-border-width:1px;--ifm-table-border-color:var(--ifm-color-emphasis-300);--ifm-table-head-background:inherit;--ifm-table-head-color:inherit;--ifm-table-head-font-weight:var(--ifm-font-weight-bold);--ifm-table-cell-color:inherit;--ifm-link-color:var(--ifm-color-primary);--ifm-link-decoration:none;--ifm-link-hover-color:var(--ifm-link-color);--ifm-link-hover-decoration:underline;--ifm-paragraph-margin-bottom:var(--ifm-leading);--ifm-blockquote-font-size:var(--ifm-font-size-base);--ifm-blockquote-border-left-width:2px;--ifm-blockquote-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-blockquote-padding-vertical:0;--ifm-blockquote-shadow:none;--ifm-blockquote-color:var(--ifm-color-emphasis-800);--ifm-blockquote-border-color:var(--ifm-color-emphasis-300);--ifm-hr-background-color:var(--ifm-color-emphasis-500);--ifm-hr-height:1px;--ifm-hr-margin-vertical:1.5rem;--ifm-scrollbar-size:7px;--ifm-scrollbar-track-background-color:#f1f1f1;--ifm-scrollbar-thumb-background-color:silver;--ifm-scrollbar-thumb-hover-background-color:#a7a7a7;--ifm-alert-background-color:inherit;--ifm-alert-border-color:inherit;--ifm-alert-border-radius:var(--ifm-global-radius);--ifm-alert-border-width:0px;--ifm-alert-border-left-width:5px;--ifm-alert-color:var(--ifm-font-color-base);--ifm-alert-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-alert-padding-vertical:var(--ifm-spacing-vertical);--ifm-alert-shadow:var(--ifm-global-shadow-lw);--ifm-avatar-intro-margin:1rem;--ifm-avatar-intro-alignment:inherit;--ifm-avatar-photo-size:3rem;--ifm-badge-background-color:inherit;--ifm-badge-border-color:inherit;--ifm-badge-border-radius:var(--ifm-global-radius);--ifm-badge-border-width:var(--ifm-global-border-width);--ifm-badge-color:var(--ifm-color-white);--ifm-badge-padding-horizontal:calc(var(--ifm-spacing-horizontal)*0.5);--ifm-badge-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-breadcrumb-border-radius:1.5rem;--ifm-breadcrumb-spacing:0.5rem;--ifm-breadcrumb-color-active:var(--ifm-color-primary);--ifm-breadcrumb-item-background-active:var(--ifm-hover-overlay);--ifm-breadcrumb-padding-horizontal:0.8rem;--ifm-breadcrumb-padding-vertical:0.4rem;--ifm-breadcrumb-size-multiplier:1;--ifm-breadcrumb-separator:url('data:image/svg+xml;utf8,');--ifm-breadcrumb-separator-filter:none;--ifm-breadcrumb-separator-size:0.5rem;--ifm-breadcrumb-separator-size-multiplier:1.25;--ifm-button-background-color:inherit;--ifm-button-border-color:var(--ifm-button-background-color);--ifm-button-border-width:var(--ifm-global-border-width);--ifm-button-font-weight:var(--ifm-font-weight-bold);--ifm-button-padding-horizontal:1.5rem;--ifm-button-padding-vertical:0.375rem;--ifm-button-size-multiplier:1;--ifm-button-transition-duration:var(--ifm-transition-fast);--ifm-button-border-radius:calc(var(--ifm-global-radius)*var(--ifm-button-size-multiplier));--ifm-button-group-spacing:2px;--ifm-card-background-color:var(--ifm-background-surface-color);--ifm-card-border-radius:calc(var(--ifm-global-radius)*2);--ifm-card-horizontal-spacing:var(--ifm-global-spacing);--ifm-card-vertical-spacing:var(--ifm-global-spacing);--ifm-toc-border-color:var(--ifm-color-emphasis-300);--ifm-toc-link-color:var(--ifm-color-content-secondary);--ifm-toc-padding-vertical:0.5rem;--ifm-toc-padding-horizontal:0.5rem;--ifm-dropdown-background-color:var(--ifm-background-surface-color);--ifm-dropdown-font-weight:var(--ifm-font-weight-semibold);--ifm-dropdown-link-color:var(--ifm-font-color-base);--ifm-dropdown-hover-background-color:var(--ifm-hover-overlay);--ifm-footer-background-color:var(--ifm-color-emphasis-100);--ifm-footer-color:inherit;--ifm-footer-link-color:var(--ifm-color-emphasis-700);--ifm-footer-link-hover-color:var(--ifm-color-primary);--ifm-footer-link-horizontal-spacing:0.5rem;--ifm-footer-padding-horizontal:calc(var(--ifm-spacing-horizontal)*2);--ifm-footer-padding-vertical:calc(var(--ifm-spacing-vertical)*2);--ifm-footer-title-color:inherit;--ifm-footer-logo-max-width:min(30rem,90vw);--ifm-hero-background-color:var(--ifm-background-surface-color);--ifm-hero-text-color:var(--ifm-color-emphasis-800);--ifm-menu-color:var(--ifm-color-emphasis-700);--ifm-menu-color-active:var(--ifm-color-primary);--ifm-menu-color-background-active:var(--ifm-hover-overlay);--ifm-menu-color-background-hover:var(--ifm-hover-overlay);--ifm-menu-link-padding-horizontal:0.75rem;--ifm-menu-link-padding-vertical:0.375rem;--ifm-menu-link-sublist-icon:url('data:image/svg+xml;utf8,');--ifm-menu-link-sublist-icon-filter:none;--ifm-navbar-background-color:var(--ifm-background-surface-color);--ifm-navbar-height:3.75rem;--ifm-navbar-item-padding-horizontal:0.75rem;--ifm-navbar-item-padding-vertical:0.25rem;--ifm-navbar-link-color:var(--ifm-font-color-base);--ifm-navbar-link-active-color:var(--ifm-link-color);--ifm-navbar-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-navbar-padding-vertical:calc(var(--ifm-spacing-vertical)*0.5);--ifm-navbar-shadow:var(--ifm-global-shadow-lw);--ifm-navbar-search-input-background-color:var(--ifm-color-emphasis-200);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-800);--ifm-navbar-search-input-placeholder-color:var(--ifm-color-emphasis-500);--ifm-navbar-search-input-icon:url('data:image/svg+xml;utf8,');--ifm-navbar-sidebar-width:83vw;--ifm-pagination-border-radius:var(--ifm-global-radius);--ifm-pagination-color-active:var(--ifm-color-primary);--ifm-pagination-font-size:1rem;--ifm-pagination-item-active-background:var(--ifm-hover-overlay);--ifm-pagination-page-spacing:0.2em;--ifm-pagination-padding-horizontal:calc(var(--ifm-spacing-horizontal)*1);--ifm-pagination-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-pagination-nav-border-radius:var(--ifm-global-radius);--ifm-pagination-nav-color-hover:var(--ifm-color-primary);--ifm-pills-color-active:var(--ifm-color-primary);--ifm-pills-color-background-active:var(--ifm-hover-overlay);--ifm-pills-spacing:0.125rem;--ifm-tabs-color:var(--ifm-font-color-secondary);--ifm-tabs-color-active:var(--ifm-color-primary);--ifm-tabs-color-active-border:var(--ifm-tabs-color-active);--ifm-tabs-padding-horizontal:1rem;--ifm-tabs-padding-vertical:1rem}.badge--danger,.badge--info,.badge--primary,.badge--secondary,.badge--success,.badge--warning{--ifm-badge-border-color:var(--ifm-badge-background-color)}.button--link,.button--outline{--ifm-button-background-color:#0000}*,.scalar-app .box-border{box-sizing:border-box}html{background-color:var(--ifm-background-color);color:var(--ifm-font-color-base);color-scheme:var(--ifm-color-scheme);font:var(--ifm-font-size-base)/var(--ifm-line-height-base) var(--ifm-font-family-base);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;-webkit-text-size-adjust:100%;text-size-adjust:100%;--primary:#54bff7;--background:var(--pale-grey);--text-color:var(--black);--hero-background:var(--bright-white);--kinda-transparent:#ffffffd9;--hyperlink:#ff62ce;--feature-img-shadow:4px 4px 6px #a9a9a980,-2px -2px 4px #0006;--footer-color:var(--white);--footer-text-color:var(--black);scroll-behavior:smooth}body{background:var(--background)}iframe{border:0;color-scheme:auto}.container{margin:0 auto;max-width:var(--ifm-container-width)}.container--fluid,.scalar-app .max-w-\[inherit\]{max-width:inherit}.row{display:flex;flex-wrap:wrap;margin:0 calc(var(--ifm-spacing-horizontal)*-1)}.authContent[data-v-8af07610] .markdown,.margin-bottom--none,.margin-vert--none,.markdown>:last-child,.scalar-app .\!mb-0{margin-bottom:0!important}.margin-top--none,.margin-vert--none{margin-top:0!important}.row--no-gutters{margin-left:0;margin-right:0}.margin-horiz--none,.margin-right--none,.scalar-app .\!mr-0{margin-right:0!important}.row--no-gutters>.col{padding-left:0;padding-right:0}.row--align-top,.scalar-app .items-start{align-items:flex-start}.row--align-bottom,.scalar-app .items-end{align-items:flex-end}.left[data-v-bc24f891],.menuExternalLink_NmtK,.row--align-center,.scalar-app .items-center{align-items:center}.row--align-stretch,.scalar-app .items-stretch{align-items:stretch}.row--align-baseline,.scalar-app .items-baseline{align-items:baseline}.col{--ifm-col-width:100%;flex:1 0;margin-left:0;max-width:var(--ifm-col-width)}.padding-bottom--none,.padding-vert--none{padding-bottom:0!important}.padding-top--none,.padding-vert--none,.scalar-app .\!pt-0{padding-top:0!important}.padding-horiz--none,.padding-left--none,[data-v-103d9d56] .cm-gutter+.cm-gutter :not(.cm-foldGutter) .cm-gutterElement,[data-v-776dfdde] .cm-gutter+.cm-gutter :not(.cm-foldGutter) .cm-gutterElement{padding-left:0!important}.padding-horiz--none,.padding-right--none{padding-right:0!important}.col[class*=col--]{flex:0 0 var(--ifm-col-width)}.col--1{--ifm-col-width:8.33333%}.col--offset-1{margin-left:8.33333%}.col--2{--ifm-col-width:16.66667%}.col--offset-2{margin-left:16.66667%}.col--3{--ifm-col-width:25%}.col--offset-3{margin-left:25%}.col--4{--ifm-col-width:33.33333%}.col--offset-4{margin-left:33.33333%}.col--5{--ifm-col-width:41.66667%}.col--offset-5{margin-left:41.66667%}.col--6{--ifm-col-width:50%}.col--offset-6{margin-left:50%}.col--7{--ifm-col-width:58.33333%}.col--offset-7{margin-left:58.33333%}.col--8{--ifm-col-width:66.66667%}.col--offset-8{margin-left:66.66667%}.col--9{--ifm-col-width:75%}.col--offset-9{margin-left:75%}.col--10{--ifm-col-width:83.33333%}.col--offset-10{margin-left:83.33333%}.col--11{--ifm-col-width:91.66667%}.col--offset-11{margin-left:91.66667%}.col--12{--ifm-col-width:100%}.col--offset-12{margin-left:100%}.margin-horiz--none,.margin-left--none{margin-left:0!important}.margin--none,.scalar-app .\!m-0{margin:0!important}.margin-bottom--xs,.margin-vert--xs{margin-bottom:.25rem!important}.margin-top--xs,.margin-vert--xs{margin-top:.25rem!important}.margin-horiz--xs,.margin-left--xs{margin-left:.25rem!important}.margin-horiz--xs,.margin-right--xs{margin-right:.25rem!important}.margin--xs{margin:.25rem!important}.margin-bottom--sm,.margin-vert--sm{margin-bottom:.5rem!important}.margin-top--sm,.margin-vert--sm{margin-top:.5rem!important}.margin-horiz--sm,.margin-left--sm{margin-left:.5rem!important}.margin-horiz--sm,.margin-right--sm{margin-right:.5rem!important}.margin--sm{margin:.5rem!important}.margin-bottom--md,.margin-vert--md{margin-bottom:1rem!important}.margin-top--md,.margin-vert--md{margin-top:1rem!important}.margin-horiz--md,.margin-left--md{margin-left:1rem!important}.margin-horiz--md,.margin-right--md{margin-right:1rem!important}.margin--md{margin:1rem!important}.margin-bottom--lg,.margin-vert--lg{margin-bottom:2rem!important}.margin-top--lg,.margin-vert--lg{margin-top:2rem!important}.margin-horiz--lg,.margin-left--lg{margin-left:2rem!important}.margin-horiz--lg,.margin-right--lg{margin-right:2rem!important}.margin--lg{margin:2rem!important}.margin-bottom--xl,.margin-vert--xl{margin-bottom:5rem!important}.margin-top--xl,.margin-vert--xl{margin-top:5rem!important}.margin-horiz--xl,.margin-left--xl{margin-left:5rem!important}.margin-horiz--xl,.margin-right--xl{margin-right:5rem!important}.margin--xl{margin:5rem!important}.padding--none,.scalar-app .\!p-0{padding:0!important}.padding-bottom--xs,.padding-vert--xs{padding-bottom:.25rem!important}.padding-top--xs,.padding-vert--xs{padding-top:.25rem!important}.padding-horiz--xs,.padding-left--xs{padding-left:.25rem!important}.padding-horiz--xs,.padding-right--xs{padding-right:.25rem!important}.padding--xs{padding:.25rem!important}.padding-bottom--sm,.padding-vert--sm{padding-bottom:.5rem!important}.padding-top--sm,.padding-vert--sm{padding-top:.5rem!important}.padding-horiz--sm,.padding-left--sm{padding-left:.5rem!important}.padding-horiz--sm,.padding-right--sm{padding-right:.5rem!important}.padding--sm{padding:.5rem!important}.padding-bottom--md,.padding-vert--md{padding-bottom:1rem!important}.padding-top--md,.padding-vert--md{padding-top:1rem!important}.padding-horiz--md,.padding-left--md{padding-left:1rem!important}.padding-horiz--md,.padding-right--md{padding-right:1rem!important}.padding--md{padding:1rem!important}.padding-bottom--lg,.padding-vert--lg{padding-bottom:2rem!important}.padding-top--lg,.padding-vert--lg{padding-top:2rem!important}.padding-horiz--lg,.padding-left--lg{padding-left:2rem!important}.padding-horiz--lg,.padding-right--lg{padding-right:2rem!important}.padding--lg{padding:2rem!important}.padding-bottom--xl,.padding-vert--xl{padding-bottom:5rem!important}.padding-top--xl,.padding-vert--xl{padding-top:5rem!important}.padding-horiz--xl,.padding-left--xl{padding-left:5rem!important}.padding-horiz--xl,.padding-right--xl{padding-right:5rem!important}.padding--xl{padding:5rem!important}code{background-color:var(--ifm-code-background);border:.1rem solid #0000001a;border-radius:var(--ifm-code-border-radius);font-family:var(--ifm-font-family-monospace);font-size:var(--ifm-code-font-size);padding:var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal)}a code{color:inherit}pre{background-color:var(--ifm-pre-background);border-radius:var(--ifm-pre-border-radius);color:var(--ifm-pre-color);font:var(--ifm-code-font-size)/var(--ifm-pre-line-height) var(--ifm-font-family-monospace);padding:var(--ifm-pre-padding)}pre code{background-color:initial;border:none;font-size:100%;line-height:inherit;padding:0}kbd{background-color:var(--ifm-color-emphasis-0);border:1px solid var(--ifm-color-emphasis-400);border-radius:.2rem;box-shadow:inset 0 -1px 0 var(--ifm-color-emphasis-400);color:var(--ifm-color-emphasis-800);font:80% var(--ifm-font-family-monospace);padding:.15rem .3rem}h1,h2,h3,h4,h5,h6{color:var(--ifm-heading-color);font-family:var(--ifm-heading-font-family);font-weight:var(--ifm-heading-font-weight);line-height:var(--ifm-heading-line-height);margin:var(--ifm-heading-margin-top) 0 var(--ifm-heading-margin-bottom) 0}article div.markdown h1,h1{font-size:var(--ifm-h1-font-size)}article div.markdown h2,h2{font-size:var(--ifm-h2-font-size)}article div.markdown h3,h3{font-size:var(--ifm-h3-font-size)}h4{font-size:var(--ifm-h4-font-size)}h5{font-size:var(--ifm-h5-font-size)}h6{font-size:var(--ifm-h6-font-size)}.container_lyt7,.container_lyt7>svg,.scalar-app .max-w-\[100\%\],.scalar-app .max-w-full,img{max-width:100%}img[align=right]{padding-left:var(--image-alignment-padding)}img[align=left]{padding-right:var(--image-alignment-padding)}.markdown{--ifm-h1-vertical-rhythm-top:3;--ifm-h2-vertical-rhythm-top:2;--ifm-h3-vertical-rhythm-top:1.5;--ifm-heading-vertical-rhythm-top:1.25;--ifm-h1-vertical-rhythm-bottom:1.25;--ifm-heading-vertical-rhythm-bottom:1}.markdown:after,.markdown:before{content:"";display:table}.markdown:after{clear:both}.markdown h1:first-child{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-h1-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown>h2{--ifm-h2-font-size:2rem;margin-top:calc(var(--ifm-h2-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h3{--ifm-h3-font-size:1.5rem;margin-top:calc(var(--ifm-h3-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h4,.markdown>h5,.markdown>h6{margin-top:calc(var(--ifm-heading-vertical-rhythm-top)*var(--ifm-leading))}.markdown>p,.markdown>pre,.markdown>ul{margin-bottom:var(--ifm-leading)}.markdown li>p{margin-top:var(--ifm-list-paragraph-margin)}.markdown li+li{margin-top:var(--ifm-list-item-margin)}ol,ul{margin:0 0 var(--ifm-list-margin);padding-left:var(--ifm-list-left-padding)}.scalar-app .markdown ol ol ol,.scalar-app .markdown ol ol ol ol ol ol,.scalar-app .markdown ol ol ol ol ol ol ol ol ol,ol ol,ul ol{list-style-type:lower-roman}.scalar-app .markdown ol ol,.scalar-app .markdown ol ol ol ol ol,.scalar-app .markdown ol ol ol ol ol ol ol ol,ol ol ol,ol ul ol,ul ol ol,ul ul ol{list-style-type:lower-alpha}table{border-collapse:collapse;display:block;margin-bottom:var(--ifm-spacing-vertical)}table thead tr{border-bottom:2px solid var(--ifm-table-border-color)}table thead,table tr:nth-child(2n){background-color:var(--ifm-table-stripe-background)}table tr{background-color:var(--ifm-table-background);border-top:var(--ifm-table-border-width) solid var(--ifm-table-border-color)}table td,table th{border:var(--ifm-table-border-width) solid var(--ifm-table-border-color);padding:var(--ifm-table-cell-padding)}table th{background-color:var(--ifm-table-head-background);color:var(--ifm-table-head-color);font-weight:var(--ifm-table-head-font-weight)}table td{color:var(--ifm-table-cell-color)}strong{font-weight:var(--ifm-font-weight-bold)}a{color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}a:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.button:hover,.text--no-decoration,.text--no-decoration:hover,a:not([href]){-webkit-text-decoration:none;text-decoration:none}p{margin:0 0 var(--ifm-paragraph-margin-bottom)}blockquote{border-left:var(--ifm-blockquote-border-left-width) solid var(--ifm-blockquote-border-color);box-shadow:var(--ifm-blockquote-shadow);color:var(--ifm-blockquote-color);font-size:var(--ifm-blockquote-font-size);padding:var(--ifm-blockquote-padding-vertical) var(--ifm-blockquote-padding-horizontal)}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}hr{background-color:var(--ifm-hr-background-color);border:0;height:var(--ifm-hr-height);margin:var(--ifm-hr-margin-vertical) 0}.shadow--lw{box-shadow:var(--ifm-global-shadow-lw)!important}.shadow--md{box-shadow:var(--ifm-global-shadow-md)!important}.shadow--tl{box-shadow:var(--ifm-global-shadow-tl)!important}.text--primary,.wordWrapButtonEnabled_uzNF .wordWrapButtonIcon_b1P5{color:var(--ifm-color-primary)}.text--secondary{color:var(--ifm-color-secondary)}.text--success{color:var(--ifm-color-success)}.text--info{color:var(--ifm-color-info)}.text--warning{color:var(--ifm-color-warning)}.text--danger{color:var(--ifm-color-danger)}.text--center{text-align:center}.text--left{text-align:left}.text--justify{text-align:justify}.text--right{text-align:right}.scalar-app .capitalize,.start-section-color .start-item[data-v-673f10b6],.tag-name[data-v-1124be5d],.text--capitalize{text-transform:capitalize}.scalar-app .lowercase,.text--lowercase{text-transform:lowercase}.scalar-app .uppercase,.text--uppercase{text-transform:uppercase}.text--light{font-weight:var(--ifm-font-weight-light)}.text--normal{font-weight:var(--ifm-font-weight-normal)}.text--semibold{font-weight:var(--ifm-font-weight-semibold)}.text--bold{font-weight:var(--ifm-font-weight-bold)}.scalar-app .hljs-emphasis,.scalar-app .italic,.scalar-app .markdown em,.text--italic{font-style:italic}.property-detail-truncate>.property-detail-value[data-v-1295f965],.scalar-app .truncate,.text--truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text--break{word-wrap:break-word!important;word-break:break-word!important}.clean-btn{background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:0}.alert,.alert .close{color:var(--ifm-alert-foreground-color)}.clean-list{list-style:none;padding-left:0}.alert--primary{--ifm-alert-background-color:var(--ifm-color-primary-contrast-background);--ifm-alert-background-color-highlight:#3578e526;--ifm-alert-foreground-color:var(--ifm-color-primary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-primary-dark)}.alert--secondary{--ifm-alert-background-color:var(--ifm-color-secondary-contrast-background);--ifm-alert-background-color-highlight:#ebedf026;--ifm-alert-foreground-color:var(--ifm-color-secondary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-secondary-dark)}.alert--success{--ifm-alert-background-color:var(--ifm-color-success-contrast-background);--ifm-alert-background-color-highlight:#00a40026;--ifm-alert-foreground-color:var(--ifm-color-success-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-success-dark)}.alert--info{--ifm-alert-background-color:var(--ifm-color-info-contrast-background);--ifm-alert-background-color-highlight:#54c7ec26;--ifm-alert-foreground-color:var(--ifm-color-info-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-info-dark)}.alert--warning{--ifm-alert-background-color:var(--ifm-color-warning-contrast-background);--ifm-alert-background-color-highlight:#ffba0026;--ifm-alert-foreground-color:var(--ifm-color-warning-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-warning-dark)}.alert--danger{--ifm-alert-background-color:var(--ifm-color-danger-contrast-background);--ifm-alert-background-color-highlight:#fa383e26;--ifm-alert-foreground-color:var(--ifm-color-danger-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-danger-dark)}.alert{--ifm-code-background:var(--ifm-alert-background-color-highlight);--ifm-link-color:var(--ifm-alert-foreground-color);--ifm-link-hover-color:var(--ifm-alert-foreground-color);--ifm-link-decoration:underline;--ifm-tabs-color:var(--ifm-alert-foreground-color);--ifm-tabs-color-active:var(--ifm-alert-foreground-color);--ifm-tabs-color-active-border:var(--ifm-alert-border-color);background-color:var(--ifm-alert-background-color);border:var(--ifm-alert-border-width) solid var(--ifm-alert-border-color);border-left-width:var(--ifm-alert-border-left-width);border-radius:var(--ifm-alert-border-radius);box-shadow:var(--ifm-alert-shadow);padding:var(--ifm-alert-padding-vertical) var(--ifm-alert-padding-horizontal)}.alert__heading{align-items:center;display:flex;font:700 var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);margin-bottom:.5rem;text-transform:uppercase}.alert__icon{display:inline-flex;margin-right:.4em}.alert__icon svg{fill:var(--ifm-alert-foreground-color);stroke:var(--ifm-alert-foreground-color);stroke-width:0}.alert .close{margin:calc(var(--ifm-alert-padding-vertical)*-1) calc(var(--ifm-alert-padding-horizontal)*-1) 0 0;opacity:.75}.alert .close:focus,.alert .close:hover{opacity:1}.alert a{text-decoration-color:var(--ifm-alert-border-color)}.alert a:hover{text-decoration-thickness:2px}.avatar{column-gap:var(--ifm-avatar-intro-margin);display:flex}.avatar__photo{border-radius:50%;display:block;height:var(--ifm-avatar-photo-size);overflow:hidden;width:var(--ifm-avatar-photo-size)}.avatar__photo--sm{--ifm-avatar-photo-size:2rem}.avatar__photo--lg{--ifm-avatar-photo-size:4rem}.avatar__photo--xl{--ifm-avatar-photo-size:6rem}.avatar__intro{display:flex;flex:1 1;flex-direction:column;justify-content:center;text-align:var(--ifm-avatar-intro-alignment)}.badge,.breadcrumbs__item,.breadcrumbs__link,.dropdown>.navbar__link:after,.scalar-app .inline-block,.searchBarContainer_NW3z.searchIndexLoading_EJ1f .searchBarLoadingRing_YnHq{display:inline-block}.avatar__name{font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base)}.avatar__subtitle{margin-top:.25rem}.avatar--vertical{--ifm-avatar-intro-alignment:center;--ifm-avatar-intro-margin:0.5rem;align-items:center;flex-direction:column}.badge{background-color:var(--ifm-badge-background-color);border:var(--ifm-badge-border-width) solid var(--ifm-badge-border-color);border-radius:var(--ifm-badge-border-radius);color:var(--ifm-badge-color);font-size:75%;font-weight:var(--ifm-font-weight-bold);line-height:1;padding:var(--ifm-badge-padding-vertical) var(--ifm-badge-padding-horizontal)}.badge--primary{--ifm-badge-background-color:var(--ifm-color-primary)}.badge--secondary{--ifm-badge-background-color:var(--ifm-color-secondary);color:var(--ifm-color-black)}.breadcrumbs__link,.button.button--secondary.button--outline:not(.button--active):not(:hover){color:var(--ifm-font-color-base)}.badge--success{--ifm-badge-background-color:var(--ifm-color-success)}.badge--info{--ifm-badge-background-color:var(--ifm-color-info)}.badge--warning{--ifm-badge-background-color:var(--ifm-color-warning)}.badge--danger{--ifm-badge-background-color:var(--ifm-color-danger)}.breadcrumbs{margin-bottom:0;padding-left:0}.breadcrumbs__item:not(:last-child):after{background:var(--ifm-breadcrumb-separator) center;content:" ";display:inline-block;filter:var(--ifm-breadcrumb-separator-filter);height:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier));margin:0 var(--ifm-breadcrumb-spacing);opacity:.5;width:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier))}.breadcrumbs__item--active .breadcrumbs__link{background:var(--ifm-breadcrumb-item-background-active);color:var(--ifm-breadcrumb-color-active)}.breadcrumbs__link{border-radius:var(--ifm-breadcrumb-border-radius);font-size:calc(1rem*var(--ifm-breadcrumb-size-multiplier));padding:calc(var(--ifm-breadcrumb-padding-vertical)*var(--ifm-breadcrumb-size-multiplier)) calc(var(--ifm-breadcrumb-padding-horizontal)*var(--ifm-breadcrumb-size-multiplier));transition-duration:var(--ifm-transition-fast);transition-property:background,color}.breadcrumbs__link:any-link:hover,.breadcrumbs__link:link:hover,.breadcrumbs__link:visited:hover,area[href].breadcrumbs__link:hover{background:var(--ifm-breadcrumb-item-background-active);-webkit-text-decoration:none;text-decoration:none}.breadcrumbs--sm{--ifm-breadcrumb-size-multiplier:0.8}.breadcrumbs--lg{--ifm-breadcrumb-size-multiplier:1.2}.button{background-color:var(--ifm-button-background-color);border:var(--ifm-button-border-width) solid var(--ifm-button-border-color);border-radius:var(--ifm-button-border-radius);font-size:calc(.875rem*var(--ifm-button-size-multiplier));font-weight:var(--ifm-button-font-weight);line-height:1.5;padding:calc(var(--ifm-button-padding-vertical)*var(--ifm-button-size-multiplier)) calc(var(--ifm-button-padding-horizontal)*var(--ifm-button-size-multiplier));text-align:center;transition-duration:var(--ifm-button-transition-duration);user-select:none;white-space:nowrap}.button,.button:hover{color:var(--ifm-button-color)}.button--outline{--ifm-button-color:var(--ifm-button-border-color)}.button--outline:hover{--ifm-button-background-color:var(--ifm-button-border-color)}.button--link{--ifm-button-border-color:#0000;color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}.button--link.button--active,.button--link:active,.button--link:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}#carbonads a,.banner_woPo,.button-link-wrapper:hover,.dropdown__link--active,.dropdown__link:hover,.link2_y3x6:hover,.link_ecgS:hover,.menu__link:hover,.navLink_KmGT,.navLink_KmGT:hover,.navbar__brand:hover,.navbar__link--active,.navbar__link:hover,.pagination-nav__link:hover,.pagination__link:hover,.rssLink_aJml,.rssLink_aJml:hover,.sidebarItemLink_mo7H:hover{-webkit-text-decoration:none;text-decoration:none}.button.disabled,.button:disabled,.button[disabled]{opacity:.65;pointer-events:none}.button--sm{--ifm-button-size-multiplier:0.8}.button--lg{--ifm-button-size-multiplier:1.35}.button--block{display:block;width:100%}.button.button--secondary{color:var(--ifm-color-gray-900)}:where(.button--primary){--ifm-button-background-color:var(--ifm-color-primary);--ifm-button-border-color:var(--ifm-color-primary)}:where(.button--primary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-primary-dark);--ifm-button-border-color:var(--ifm-color-primary-dark)}.button--primary.button--active,.button--primary:active{--ifm-button-background-color:var(--ifm-color-primary-darker);--ifm-button-border-color:var(--ifm-color-primary-darker)}:where(.button--secondary){--ifm-button-background-color:var(--ifm-color-secondary);--ifm-button-border-color:var(--ifm-color-secondary)}:where(.button--secondary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-secondary-dark);--ifm-button-border-color:var(--ifm-color-secondary-dark)}.button--secondary.button--active,.button--secondary:active{--ifm-button-background-color:var(--ifm-color-secondary-darker);--ifm-button-border-color:var(--ifm-color-secondary-darker)}:where(.button--success){--ifm-button-background-color:var(--ifm-color-success);--ifm-button-border-color:var(--ifm-color-success)}:where(.button--success):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-success-dark);--ifm-button-border-color:var(--ifm-color-success-dark)}.button--success.button--active,.button--success:active{--ifm-button-background-color:var(--ifm-color-success-darker);--ifm-button-border-color:var(--ifm-color-success-darker)}:where(.button--info){--ifm-button-background-color:var(--ifm-color-info);--ifm-button-border-color:var(--ifm-color-info)}:where(.button--info):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-info-dark);--ifm-button-border-color:var(--ifm-color-info-dark)}.button--info.button--active,.button--info:active{--ifm-button-background-color:var(--ifm-color-info-darker);--ifm-button-border-color:var(--ifm-color-info-darker)}:where(.button--warning){--ifm-button-background-color:var(--ifm-color-warning);--ifm-button-border-color:var(--ifm-color-warning)}:where(.button--warning):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-warning-dark);--ifm-button-border-color:var(--ifm-color-warning-dark)}.button--warning.button--active,.button--warning:active{--ifm-button-background-color:var(--ifm-color-warning-darker);--ifm-button-border-color:var(--ifm-color-warning-darker)}:where(.button--danger){--ifm-button-background-color:var(--ifm-color-danger);--ifm-button-border-color:var(--ifm-color-danger)}:where(.button--danger):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-danger-dark);--ifm-button-border-color:var(--ifm-color-danger-dark)}.button--danger.button--active,.button--danger:active{--ifm-button-background-color:var(--ifm-color-danger-darker);--ifm-button-border-color:var(--ifm-color-danger-darker)}.button-group{display:inline-flex;gap:var(--ifm-button-group-spacing)}.button-group>.button:not(:first-child),.scalar-app .rounded-l-none{border-bottom-left-radius:0;border-top-left-radius:0}.button-group>.button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.button-group--block{display:flex;justify-content:stretch}.button-group--block>.button,.scalar-app .flex-grow,.scalar-app .grow{flex-grow:1}.card{background-color:var(--ifm-card-background-color);border-radius:var(--ifm-card-border-radius);box-shadow:var(--ifm-global-shadow-lw);display:flex;flex-direction:column;overflow:hidden}.card--full-height{height:100%}.card__image{padding-top:var(--ifm-card-vertical-spacing)}.card__image:first-child{padding-top:0}.card__body,.card__footer,.card__header{padding:var(--ifm-card-vertical-spacing) var(--ifm-card-horizontal-spacing)}.card__body:not(:last-child),.card__footer:not(:last-child),.card__header:not(:last-child),.parameter-item:last-of-type .parameter-schema[data-v-be55ce3a],.property-enum-value:last-of-type .property-enum-value-label[data-v-f4b54bdd]{padding-bottom:0}.card__body>:last-child,.card__footer>:last-child,.card__header>:last-child{margin-bottom:0}.card__footer,.scalar-app .mt-auto{margin-top:auto}.table-of-contents{font-size:.8rem;margin-bottom:0;padding:var(--ifm-toc-padding-vertical) 0}.table-of-contents,.table-of-contents ul{list-style:none;padding-left:var(--ifm-toc-padding-horizontal)}.table-of-contents li{margin:var(--ifm-toc-padding-vertical) var(--ifm-toc-padding-horizontal)}.table-of-contents__left-border{border-left:1px solid var(--ifm-toc-border-color)}.table-of-contents__link{color:var(--ifm-toc-link-color);display:block}.table-of-contents__link--active,.table-of-contents__link--active code,.table-of-contents__link:hover,.table-of-contents__link:hover code{color:var(--ifm-color-primary);-webkit-text-decoration:none;text-decoration:none}.close{color:var(--ifm-color-black);font-size:1.5rem;font-weight:var(--ifm-font-weight-bold);line-height:1;opacity:.5;padding:1rem;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.close:hover{opacity:.7}.close:focus,.theme-code-block-highlighted-line .codeLineNumber_Tfdd:before{opacity:.8}.dropdown{display:inline-flex;font-weight:var(--ifm-dropdown-font-weight);position:relative;vertical-align:top}.dropdown--hoverable:hover .dropdown__menu,.dropdown--show .dropdown__menu{opacity:1;pointer-events:all;transform:translateY(-1px);visibility:visible}.dropdown--right .dropdown__menu{left:inherit;right:0}.dropdown--nocaret .navbar__link:after{content:none!important}.dropdown__menu{background-color:var(--ifm-dropdown-background-color);border-radius:var(--ifm-global-radius);box-shadow:var(--ifm-global-shadow-md);left:0;list-style:none;max-height:80vh;min-width:10rem;opacity:0;overflow-y:auto;padding:.5rem;pointer-events:none;position:absolute;top:calc(100% - var(--ifm-navbar-item-padding-vertical) + .3rem);transform:translateY(-.625rem);transition-duration:var(--ifm-transition-fast);transition-property:opacity,transform,visibility;transition-timing-function:var(--ifm-transition-timing-default);visibility:hidden;z-index:var(--ifm-z-index-dropdown)}.sidebar_re4s,.tableOfContents_bqdL{max-height:calc(100vh - var(--ifm-navbar-height) - 2rem)}.menu__caret,.menu__link,.menu__list-item-collapsible{border-radius:.25rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.dropdown__link{border-radius:.25rem;color:var(--ifm-dropdown-link-color);display:block;font-size:.875rem;margin-top:.2rem;padding:.25rem .5rem;white-space:nowrap}.dropdown__link--active,.dropdown__link:hover{background-color:var(--ifm-dropdown-hover-background-color);color:var(--ifm-dropdown-link-color)}.dropdown__link--active,.dropdown__link--active:hover{--ifm-dropdown-link-color:var(--ifm-link-color)}.dropdown>.navbar__link:after{border-color:currentcolor #0000;border-style:solid;border-width:.4em .4em 0;content:"";margin-left:.3em;position:relative;top:2px;transform:translateY(-50%)}.footer{background-color:var(--ifm-footer-background-color);color:var(--ifm-footer-color);padding:var(--ifm-footer-padding-vertical) var(--ifm-footer-padding-horizontal)}.footer--dark{--ifm-footer-background-color:#303846;--ifm-footer-color:var(--ifm-footer-link-color);--ifm-footer-link-color:var(--ifm-color-secondary);--ifm-footer-title-color:var(--ifm-color-white)}.footer__links{margin-bottom:1rem}.footer__link-item{color:var(--ifm-footer-link-color);line-height:2}.footer__link-item:hover{color:var(--ifm-footer-link-hover-color)}.footer__link-separator{margin:0 var(--ifm-footer-link-horizontal-spacing)}.footer__logo{margin-top:1rem;max-width:var(--ifm-footer-logo-max-width)}.footer__title{color:var(--ifm-footer-title-color);font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base);margin-bottom:var(--ifm-heading-margin-bottom)}.menu,.navbar__link{font-weight:var(--ifm-font-weight-semibold)}.footer__item,.property-description[data-v-d5367294]:first-of-type:last-of-type,.property-heading:empty+.property-description[data-v-d5367294]:last-of-type{margin-top:0}.admonitionContent_BuS1>:last-child,.collapsibleContent_i85q p:last-child,.details_lb9f>summary>p:last-child,.footer__items,.operation-details-card-item[data-v-55addca4] .parameter-list-items,.scalar-app .last\:mb-0:last-child,.scalar-app .markdown td>*,.scalar-app .markdown th>*,.scalar-app .markdown>:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):last-child,.scalar-app .mb-0,.searchResultItem_U687>h2,.section-accordion-title[data-v-9419dd23] .section-header{margin-bottom:0}.composition-panel .property.property.property.property--level-0[data-v-84242873],.requestPreview.succeeded[data-v-0eb5f95d],[type=checkbox]{padding:0}.hero{align-items:center;background-color:var(--ifm-hero-background-color);color:var(--ifm-hero-text-color);display:flex;padding:4rem 2rem}.hero--primary{--ifm-hero-background-color:var(--ifm-color-primary);--ifm-hero-text-color:var(--ifm-font-color-base-inverse)}.hero--dark{--ifm-hero-background-color:#303846;--ifm-hero-text-color:var(--ifm-color-white)}.hero__title,.title_f1Hy,h2{font-size:3rem}.hero__subtitle,.scalar-app .markdown h1{font-size:1.5rem}.menu__list{list-style:none;margin:0;padding-left:0}.menu__caret,.menu__link{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu__list .menu__list{flex:0 0 100%;margin-top:.25rem;padding-left:var(--ifm-menu-link-padding-horizontal)}.menu__list-item:not(:first-child){margin-top:.25rem}.menu__list-item--collapsed .menu__list{height:0;overflow:hidden}.details_lb9f[data-collapsed=false].isBrowser_bmU9>summary:before,.details_lb9f[open]:not(.isBrowser_bmU9)>summary:before,.menu__list-item--collapsed .menu__caret:before,.menu__list-item--collapsed .menu__link--sublist:after,.toggle-icon-sun-ray[data-v-60be8692]:nth-of-type(2){transform:rotate(90deg)}.menu__list-item-collapsible{display:flex;flex-wrap:wrap;position:relative}.menu__caret:hover,.menu__link:hover,.menu__list-item-collapsible--active,.menu__list-item-collapsible:hover{background:var(--ifm-menu-color-background-hover)}.menu__list-item-collapsible .menu__link--active,.menu__list-item-collapsible .menu__link:hover{background:none!important}.menu__caret,.menu__link{align-items:center;display:flex}.navbar-sidebar,.navbar-sidebar__backdrop{opacity:0;transition-duration:var(--ifm-transition-fast);transition-timing-function:ease-in-out;top:0;bottom:0;left:0}.menu__link{color:var(--ifm-menu-color);flex:1;line-height:1.25}.menu__link:hover{color:var(--ifm-menu-color)}.menu__caret:before,.menu__link--sublist-caret:after{content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast) linear;width:1.25rem}.menu__link--sublist-caret:after{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem;margin-left:auto;min-width:1.25rem}.menu__link--active,.menu__link--active:hover{color:var(--ifm-menu-color-active)}.navbar__brand,.navbar__link{color:var(--ifm-navbar-link-color)}.menu__link--active:not(.menu__link--sublist){background-color:var(--ifm-menu-color-background-active)}.menu__caret:before{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem}.scalar-app .active\:brightness-90:active,.scalar-app .blur,.scalar-app .brightness-lifted,.scalar-app .filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.authContent[data-v-8af07610],.requestContent[data-v-0eb5f95d]{grid-template-rows:0fr;transition:grid-template-rows .2s ease-out}.navbar--dark,html[data-theme=dark]{--ifm-menu-link-sublist-icon-filter:invert(100%) sepia(94%) saturate(17%) hue-rotate(223deg) brightness(104%) contrast(98%)}.navbar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-navbar-shadow);height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar,.navbar>.container,.navbar>.container-fluid{display:flex}.navbar--fixed-top{position:sticky;top:0;z-index:var(--ifm-z-index-fixed)}.navbar__inner{display:flex;flex-wrap:wrap;justify-content:space-between;width:100%}.navbar__brand{align-items:center;display:flex;margin-right:1rem;min-width:0}.navbar__brand:hover{color:var(--ifm-navbar-link-hover-color)}.announcementBarContent_xLdY,.navbar__title{flex:1 1 auto}.navbar__toggle{display:none;margin-right:.5rem}.navbar__logo{flex:0 0 auto;height:2rem;margin-right:.5rem}.navbar__logo img,.scalar-app .h-full,body,html{height:100%}.navbar__items,.request-card-footer-addon[data-v-cbc307e3]{align-items:center;display:flex;flex:1;min-width:0}.navbar__items--center{flex:0 0 auto}.navbar__items--center .navbar__brand{margin:0}.introduction-card-row[data-v-5764c94a]>*,.navbar__items--center+.navbar__items--right{flex:1}.navbar__items--right{flex:0 0 auto;justify-content:flex-end}.navbar__item{display:inline-block;padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}#nprogress,.navbar__item.dropdown .navbar__link:not([href]),.scalar-app .pointer-events-none,.start-item[data-v-673f10b6]:empty{pointer-events:none}.navbar__link--active,.navbar__link:hover{color:var(--ifm-navbar-link-hover-color)}.navbar--dark,.navbar--primary{--ifm-menu-color:var(--ifm-color-gray-300);--ifm-navbar-link-color:var(--ifm-color-gray-100);--ifm-navbar-search-input-background-color:#ffffff1a;--ifm-navbar-search-input-placeholder-color:#ffffff80;color:var(--ifm-color-white)}.navbar--dark{--ifm-navbar-background-color:#242526;--ifm-menu-color-background-active:#ffffff0d;--ifm-navbar-search-input-color:var(--ifm-color-white)}.navbar--primary{--ifm-navbar-background-color:var(--ifm-color-primary);--ifm-navbar-link-hover-color:var(--ifm-color-white);--ifm-menu-color-active:var(--ifm-color-white);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-500)}.navbar__search-input{appearance:none;background:var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat .75rem center/1rem 1rem;border:none;border-radius:2rem;color:var(--ifm-navbar-search-input-color);cursor:text;display:inline-block;font-size:1rem;height:2rem;padding:0 .5rem 0 2.25rem;width:12.5rem}.navbar__search-input::placeholder{color:var(--ifm-navbar-search-input-placeholder-color)}.navbar-sidebar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-global-shadow-md);position:fixed;transform:translate3d(-100%,0,0);transition-property:opacity,visibility,transform;visibility:hidden;width:var(--ifm-navbar-sidebar-width)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar__items,.toggle-icon-dark .toggle-icon-moon-mask[data-v-60be8692]{transform:translateZ(0)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar--show .navbar-sidebar__backdrop{opacity:1;visibility:visible}.navbar-sidebar__backdrop{background-color:#0009;position:fixed;right:0;transition-property:opacity,visibility;visibility:hidden}.navbar-sidebar__brand{align-items:center;box-shadow:var(--ifm-navbar-shadow);display:flex;flex:1;height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.footer,article div.markdown p img,nav.navbar{box-shadow:var(--feature-img-shadow)}.navbar-sidebar__items{display:flex;height:calc(100% - var(--ifm-navbar-height));transition:transform var(--ifm-transition-fast) ease-in-out}.navbar-sidebar__items--show-secondary{transform:translate3d(calc((var(--ifm-navbar-sidebar-width))*-1),0,0)}.navbar-sidebar__item{flex-shrink:0;padding:.5rem;width:calc(var(--ifm-navbar-sidebar-width))}.navbar-sidebar__back{background:var(--ifm-menu-color-background-active);font-size:15px;font-weight:var(--ifm-button-font-weight);margin:0 0 .2rem -.5rem;padding:.6rem 1.5rem;position:relative;text-align:left;top:-.5rem;width:calc(100% + 1rem)}.navbar-sidebar__close{display:flex;margin-left:auto}.pagination{column-gap:var(--ifm-pagination-page-spacing);display:flex;font-size:var(--ifm-pagination-font-size);padding-left:0}.pagination--sm{--ifm-pagination-font-size:0.8rem;--ifm-pagination-padding-horizontal:0.8rem;--ifm-pagination-padding-vertical:0.2rem}.pagination--lg{--ifm-pagination-font-size:1.2rem;--ifm-pagination-padding-horizontal:1.2rem;--ifm-pagination-padding-vertical:0.3rem}.pagination__item,.property-detail[data-v-1295f965],.scalar-app .inline-flex{display:inline-flex}.pagination__item>span{padding:var(--ifm-pagination-padding-vertical)}.pagination__item--active .pagination__link{color:var(--ifm-pagination-color-active)}.pagination__item--active .pagination__link,.pagination__item:not(.pagination__item--active):hover .pagination__link{background:var(--ifm-pagination-item-active-background)}.pagination__item--disabled,.pagination__item[disabled]{opacity:.25;pointer-events:none}.pagination__link{border-radius:var(--ifm-pagination-border-radius);color:var(--ifm-font-color-base);display:inline-block;padding:var(--ifm-pagination-padding-vertical) var(--ifm-pagination-padding-horizontal);transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav{display:grid;grid-gap:var(--ifm-spacing-horizontal);gap:var(--ifm-spacing-horizontal);grid-template-columns:repeat(2,1fr)}.pagination-nav__link{border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-pagination-nav-border-radius);display:block;height:100%;line-height:var(--ifm-heading-line-height);padding:var(--ifm-global-spacing);transition:border-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav__link:hover{border-color:var(--ifm-pagination-nav-color-hover)}.content_knG7 a,.disclaimerLink[data-v-56f7e8dd],.hitFooter_E9YW a,.link2_y3x6.link2_y3x6,.link2_y3x6.link2_y3x6:hover,.link_ecgS.link2_y3x6,.link_ecgS.link2_y3x6:hover,.suggestion_fB_2.cursor_eG29 mark,.toggleButton[data-v-0eb5f95d]:hover,li a:hover,p a:hover{-webkit-text-decoration:underline;text-decoration:underline}.pagination-nav__link--next{grid-column:2/3;text-align:right}.pagination-nav__label{font-size:var(--ifm-h4-font-size);font-weight:var(--ifm-heading-font-weight);word-break:break-word}.pagination-nav__link--prev .pagination-nav__label:before{content:"« "}.pagination-nav__link--next .pagination-nav__label:after{content:" »"}.pagination-nav__sublabel{color:var(--ifm-color-content-secondary);font-size:var(--ifm-h5-font-size);font-weight:var(--ifm-font-weight-semibold);margin-bottom:.25rem}.pills__item,.tabs{font-weight:var(--ifm-font-weight-bold)}.pills{display:flex;gap:var(--ifm-pills-spacing);padding-left:0}.pills__item{border-radius:.5rem;cursor:pointer;display:inline-block;padding:.25rem 1rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pills__item--active{color:var(--ifm-pills-color-active)}.pills__item--active,.pills__item:not(.pills__item--active):hover{background:var(--ifm-pills-color-background-active)}.pills--block,.scalar-app .justify-stretch,.tabs--block{justify-content:stretch}.pills--block .pills__item{flex-grow:1;text-align:center}.tabs{color:var(--ifm-tabs-color);display:flex;margin-bottom:0;overflow-x:auto;padding-left:0}.tabs__item{border-bottom:3px solid #0000;border-radius:var(--ifm-global-radius);cursor:pointer;display:inline-flex;padding:var(--ifm-tabs-padding-vertical) var(--ifm-tabs-padding-horizontal);transition:background-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.tabs__item--active{border-bottom-color:var(--ifm-tabs-color-active-border);border-bottom-left-radius:0;border-bottom-right-radius:0;color:var(--ifm-tabs-color-active)}.tabs__item:hover{background-color:var(--ifm-hover-overlay)}.tabs--block .tabs__item{flex-grow:1;justify-content:center}html[data-theme=dark]{--ifm-color-scheme:dark;--ifm-color-emphasis-0:var(--ifm-color-gray-1000);--ifm-color-emphasis-100:var(--ifm-color-gray-900);--ifm-color-emphasis-200:var(--ifm-color-gray-800);--ifm-color-emphasis-300:var(--ifm-color-gray-700);--ifm-color-emphasis-400:var(--ifm-color-gray-600);--ifm-color-emphasis-600:var(--ifm-color-gray-400);--ifm-color-emphasis-700:var(--ifm-color-gray-300);--ifm-color-emphasis-800:var(--ifm-color-gray-200);--ifm-color-emphasis-900:var(--ifm-color-gray-100);--ifm-color-emphasis-1000:var(--ifm-color-gray-0);--ifm-background-color:#1b1b1d;--ifm-background-surface-color:#242526;--ifm-hover-overlay:#ffffff0d;--ifm-color-content:#e3e3e3;--ifm-color-content-secondary:#fff;--ifm-breadcrumb-separator-filter:invert(64%) sepia(11%) saturate(0%) hue-rotate(149deg) brightness(99%) contrast(95%);--ifm-code-background:#ffffff1a;--ifm-scrollbar-track-background-color:#444;--ifm-scrollbar-thumb-background-color:#686868;--ifm-scrollbar-thumb-hover-background-color:#7a7a7a;--ifm-table-stripe-background:#ffffff12;--ifm-toc-border-color:var(--ifm-color-emphasis-200);--ifm-color-primary-contrast-background:#102445;--ifm-color-primary-contrast-foreground:#ebf2fc;--ifm-color-secondary-contrast-background:#474748;--ifm-color-secondary-contrast-foreground:#fdfdfe;--ifm-color-success-contrast-background:#003100;--ifm-color-success-contrast-foreground:#e6f6e6;--ifm-color-info-contrast-background:#193c47;--ifm-color-info-contrast-foreground:#eef9fd;--ifm-color-warning-contrast-background:#4d3800;--ifm-color-warning-contrast-foreground:#fff8e6;--ifm-color-danger-contrast-background:#4b1113;--ifm-color-danger-contrast-foreground:#ffebec;--primary:#db78fc;--background:var(--dark-grey);--text-color:var(--white);--hero-background:var(--black);--kinda-transparent:#000000d9;--hyperlink:var(--primary);--feature-img-shadow:4px 4px 6px #00000080,-2px -2px 4px #0006;--footer-color:var(--black);--footer-text-color:var(--white);--heading-shadow:-5px 4px 0px #000;--sub-heading-shadow:-3px 3px 1px #000}:root{--docusaurus-progress-bar-color:var(--ifm-color-primary);--bright-white:#fff;--white:#f7f7f7;--pale-grey:#e9e9e8;--mid-grey:#a9a9a9;--dark-grey:#18191a;--black:#121212;--pitch-black:#000;--info:#35c9fa;--success:#8f8;--warning:#ece715;--danger:#f80363;--accent-green:#41ef90;--accent-green-dark:#1e9554;--accent-blue:#5c85f7;--accent-blue-dark:#3d48dd;--accent-purple:#9083ed;--accent-purple-dark:#6b3cd6;--accent-pink:#db78fc;--accent-pink-dark:#b83ddd;--accent-yellow:#dcff5a;--accent-yellow-dark:#ceb73f;--ifm-color-primary:var(--accent-pink,#ff62ce);--ifm-color-primary-dark:#21af90;--ifm-color-primary-darker:#1fa588;--ifm-color-primary-darkest:#1a8870;--ifm-color-primary-light:#46cbae;--ifm-color-primary-lighter:#66d4bd;--ifm-color-primary-lightest:#92e0d0;--ifm-code-font-size:95%;--ifm-navbar-background-color:var(--hero-background);--ifm-navbar-link-color:var(--text-color);--ifm-breadcrumb-color-active:var(--text-color);--docusaurus-announcement-bar-height:auto;--docusaurus-collapse-button-bg:#0000;--docusaurus-collapse-button-bg-hover:#0000001a;--doc-sidebar-width:300px;--doc-sidebar-hidden-width:30px;--scalar-loaded-api-reference:true;--docusaurus-blog-social-icon-size:1rem;--docusaurus-tag-list-border:var(--ifm-color-emphasis-300)}#nprogress .bar{background:var(--docusaurus-progress-bar-color);height:2px;left:0;position:fixed;top:0;width:100%;z-index:1031}#nprogress .peg{box-shadow:0 0 10px var(--docusaurus-progress-bar-color),0 0 5px var(--docusaurus-progress-bar-color);height:100%;opacity:1;position:absolute;right:0;transform:rotate(3deg) translateY(-4px);width:100px}h1{font-family:Racing Sans One,mono;font-size:4rem}button,h2,h3,h4,h5{font-family:Roboto Mono,monospace}h3{font-size:2rem}button,h4,h5{font-size:1.2rem}a,body,div,p,section,span,ul li{font-family:Poppins,sans-serif;font-size:1.2rem}.link2_y3x6,.link_ecgS,code span{font-family:Roboto Mono,monospace}.hero{--ifm-hero-background-color:var(--hero-background);--ifm-hero-text-color:var(--text-color)}.footer{--ifm-footer-background-color:var(--footer-color);--ifm-footer-color:var(--footer-text-color);--ifm-footer-link-color:var(--footer-text-color);--ifm-footer-title-color:var(--footer-text-color);--ifm-footer-link-hover-color:var(--primary);--ifm-link-color:var(--footer-text-color);--ifm-link-hover-color:var(--primary)}.footer .footer__link-item,.scalar-app .markdown h4,.scalar-app .markdown h5,.scalar-app .markdown h6,.sidebar-ad #carbonads .carbon-text,article div.markdown a,article div.markdown code span,article div.markdown li,article div.markdown ol,article div.markdown p,article div.markdown ul,ul.table-of-contents li a{font-size:1rem}.footer .footer__copyright,.footer .footer__copyright a{font-family:Roboto Mono,monospace;font-size:.9rem;font-weight:700;opacity:.9}.docusaurus-highlight-code-line{background-color:#0000001a;display:block;margin:0 calc(var(--ifm-pre-padding)*-1);padding:0 var(--ifm-pre-padding)}nav.navbar a.navbar__brand .navbar__logo,nav.navbar a.navbar__brand b{transition:.3s}nav.navbar a.navbar__brand:hover .navbar__logo{transform:scale(1.1) rotate(-3deg)}nav.navbar a.navbar__brand:hover b{color:#00eed6}nav.navbar a.navbar__brand b{font-size:1.5rem;font-weight:700}.authorLink_YWtN:hover,nav.navbar .navbar__link--active,nav.navbar .navbar__link:hover{color:var(--primary)}.fetchErrorCard_M8w3 a:hover,.meta_GplF a:hover,.pagination-nav__label,html[data-theme=light] main div.container.padding-top--md.padding-bottom--lg .pagination-nav__label{color:var(--text-color)}li a,li a:hover,p a,p a:hover,ul.table-of-contents li a:hover,ul.table-of-contents li a:hover code{color:var(--hyperlink)}h1,h2,h3,h4,h5{cursor:default}article div.markdown{--ifm-h1-font-size:3.0rem;--ifm-h2-font-size:2.5rem;--ifm-h3-font-size:1.4rem}article div.markdown p img{border-radius:4px}.sponsor_JDQ1,.sponsor_JDQ1 img,article div.markdown table>tr>td>a>img{border-radius:6px}ul.table-of-contents li a.table-of-contents__link--active,ul.table-of-contents li a.table-of-contents__link--active code{color:var(--hyperlink);font-weight:700}.form-group[data-v-43df1726],.form-group[data-v-694018d6],ul.menu__list{margin-bottom:1rem}ul.menu__list li.menu__list-item a.active,ul.menu__list li.menu__list-item a.menu__link--active{color:var(--primary);font-weight:700}code span{font-size:.9rem!important}html[data-theme=light] main div.container.padding-top--md.padding-bottom--lg{padding:0 1rem!important}html[data-theme=light] main div.container.padding-top--md.padding-bottom--lg div>div.col[class*=docItemCol]{background:var(--bright-white);border-right:1px solid var(--ifm-toc-border-color);padding:1rem}html[data-theme=light] main div.container.padding-top--md.padding-bottom--lg div>div.col[class*=docItemCol] ul li a{font-weight:700}.scalar-app .markdown td:first-of-type,.scalar-app .markdown th:first-of-type,html[data-theme=light] main div.container.padding-top--md.padding-bottom--lg .table-of-contents__left-border{border-left:none}#carbonads *{line-height:normal;margin:initial;padding:initial}#carbonads{--carbon-font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",Helvetica,Arial,sans-serif;--carbon-font-size:14px;--carbon-padding:1.5ch;--carbon-max-char:20ch;--carbon-bg-primary:#fafafa;--carbon-bg-secondary:#ebebeb;--carbon-text-color:#333;font-family:var(--carbon-font-family);font-size:var(--carbon-font-size);margin:0 auto;width:fit-content;z-index:10}#carbonads>span{background-color:var(--carbon-bg-primary);box-shadow:0 0 1px rgba(0,0,0,.085),0 0 2px rgba(0,0,0,.085),0 0 4px rgba(0,0,0,.085),0 0 8px rgba(0,0,0,.085);display:flex;flex-direction:column;gap:var(--carbon-padding);max-inline-size:calc(130px + var(--carbon-max-char) + 8ch);min-inline-size:130px;padding:var(--carbon-padding)}#carbonads .carbon-wrap{display:flex;flex-wrap:wrap;gap:1.5ex}#carbonads .carbon-img{flex:0 0 130px}#carbonads .carbon-img img,.cursor_eG29 .hideAction_vcyE>svg,.scalar-card-checkbox .scalar-card-checkbox-input:checked~.scalar-card-checkbox-checkmark[data-v-dc47ca65]:after{display:block}#carbonads .carbon-text{flex-basis:var(--carbon-max-char);flex-grow:1;line-height:1.4;text-align:left}#carbonads .carbon-poweredby{background:var(--carbon-bg-secondary);font-size:.6em;font-weight:600;letter-spacing:.2ch;line-height:1.4;padding:6px 8px;text-align:center;text-transform:uppercase}.sidebar-ad{--carbon-bg-primary:var(--background,#18191a);--carbon-bg-secondary:#282a36;--carbon-text-color:#e6e6e6}.sidebar-ad #carbonads{margin:.5rem}.sidebar-ad #carbonads .carbon-img img,.sidebar-ad .avatar__photo-link{border-radius:5px}.sidebar-ad #carbonads .carbon-wrap{justify-content:center}.sidebar-ad #carbonads>span{box-shadow:none}.sidebar-ad #carbonads .carbon-poweredby{border-radius:5px;font-size:.7rem}::selection{background:var(--primary);color:var(--background);text-shadow:none}@keyframes a{0%{opacity:0;transform:translateY(-100%)}to{opacity:1;transform:translateY(0)}}@keyframes b{0%{opacity:1}to{opacity:0;transform:translateY(-100%)}}.banner_woPo{align-items:center;animation:.3s ease-out a;background:var(--accent-green);color:var(--bright-white);display:flex;flex-wrap:wrap;font-size:.95rem;font-weight:600;gap:.25rem 1.25rem;justify-content:center;padding:.25rem 1rem;position:relative;width:100%;z-index:100}.dismissing_ZrNM{animation:.25s ease-in forwards b}.link2_y3x6,.link_ecgS{color:var(--black);font-size:.95rem;font-weight:400;transition:.2s}.link2_y3x6.link2_y3x6,.link_ecgS.link2_y3x6{opacity:.85}.link2_y3x6.link2_y3x6:hover,.link_ecgS.link2_y3x6:hover{opacity:1;transform:scale(1.03)}.closeBtn_fC0A{background:none;border:none;border-radius:4px;color:var(--black);cursor:pointer;font-size:1.5rem;line-height:1;opacity:.8;padding:0 .25rem .1rem;position:absolute;right:1rem;transition:.2s}.closeBtn_fC0A:hover{background:#1d8c4f;opacity:1}#__docusaurus-base-url-issue-banner-container,.docSidebarContainer_YfHR,.hideAction_vcyE>svg,.icons-only[data-v-b59b0acf] span,.introduction-card-item[data-v-dfab866f]:empty,.introduction-card-row[data-v-5764c94a] .scalar-card:nth-of-type(2) .scalar-card-header,.navbarSearchContainer_Bca1:empty,.scalar-app .empty\:hidden:empty,.sidebarLogo_isFc,.themedComponent_mlkZ,.toggleIcon_g3eP,html[data-announcement-bar-initially-dismissed=true] .announcementBar_mb4j{display:none}.backToTopButton_sjWU{background-color:var(--ifm-color-emphasis-200);border-radius:50%;bottom:1.3rem;box-shadow:var(--ifm-global-shadow-lw);height:3rem;opacity:0;position:fixed;right:1.3rem;transform:scale(0);transition:all var(--ifm-transition-fast) var(--ifm-transition-timing-default);visibility:hidden;width:3rem;z-index:calc(var(--ifm-z-index-fixed) - 1)}.backToTopButton_sjWU:after{background-color:var(--ifm-color-emphasis-1000);content:" ";display:inline-block;height:100%;-webkit-mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;width:100%}.backToTopButtonShow_xfvO{opacity:1;transform:scale(1);visibility:visible}.skipToContent_fXgn{background-color:var(--ifm-background-surface-color);color:var(--ifm-color-emphasis-900);left:100%;padding:calc(var(--ifm-global-spacing)/2) var(--ifm-global-spacing);position:fixed;top:1rem;z-index:calc(var(--ifm-z-index-fixed) + 1)}.skipToContent_fXgn:focus{box-shadow:var(--ifm-global-shadow-md);left:1rem}.closeButton_CVFx{line-height:0;padding:0}.content_knG7{font-size:85%;padding:5px 0;text-align:center}.content_knG7 a{color:inherit}.announcementBar_mb4j{align-items:center;background-color:var(--ifm-color-white);border-bottom:1px solid var(--ifm-color-emphasis-100);color:var(--ifm-color-black);display:flex;height:var(--docusaurus-announcement-bar-height)}.announcementBarPlaceholder_vyr4{flex:0 0 10px}.announcementBarClose_gvF7{align-self:stretch;flex:0 0 30px}.toggle_vylO{height:2rem;width:2rem}.toggleButton_gllP{align-items:center;border-radius:50%;display:flex;height:100%;justify-content:center;transition:background var(--ifm-transition-fast);width:100%}.toggleButton_gllP:hover{background:var(--ifm-color-emphasis-200)}[data-theme-choice=dark] .darkToggleIcon_wfgR,[data-theme-choice=light] .lightToggleIcon_pyhR,[data-theme-choice=system] .systemToggleIcon_QzmC,[data-theme=dark] .themedComponent--dark_xIcU,[data-theme=light] .themedComponent--light_NVdE,html:not([data-theme]) .themedComponent--light_NVdE{display:initial}.scalar-app .cursor-not-allowed,.toggleButtonDisabled_aARS{cursor:not-allowed}.darkNavbarColorModeToggle_X3D1:hover{background:var(--ifm-color-gray-800)}[data-theme=dark]:root{--docusaurus-collapse-button-bg:#ffffff0d;--docusaurus-collapse-button-bg-hover:#ffffff1a}.collapseSidebarButton_PEFL{display:none;margin:0}.categoryLinkLabel_W154,.linkLabel_WmDU{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical}.iconExternalLink_nPIU{margin-left:.3rem}.linkLabel_WmDU{line-clamp:2;-webkit-line-clamp:2}.categoryLink_byQd,.property-detail-truncate[data-v-1295f965],.scalar-app .overflow-hidden{overflow:hidden}.menu__link--sublist-caret:after{margin-left:var(--ifm-menu-link-padding-vertical)}.categoryLinkLabel_W154{flex:1;line-clamp:2;-webkit-line-clamp:2}.docMainContainer_TBSr,.docRoot_UBD9{display:flex;width:100%}.docsWrapper_hBAB{display:flex;flex:1 0 auto}.notFoundPage_Wsy5{align-items:center;background:var(--hero-background);display:flex;justify-content:center;min-height:calc(100vh - 4rem);overflow:hidden;position:relative}.container_a6qs{padding:2rem;position:relative;text-align:center;z-index:2}.glitchCode_Pt6F{animation:3s ease-in-out infinite d;color:var(--primary);font-family:Racing Sans One,monospace;font-size:10rem;font-weight:900;line-height:1;position:relative;text-shadow:0 0 20px color-mix(in srgb,var(--primary) 40%,#0000),0 0 60px color-mix(in srgb,var(--primary) 20%,#0000)}.title_B9uO{color:var(--text-color);font-size:2rem;font-weight:700;margin:1rem 0 .5rem}.subtitle_Z226{color:var(--mid-grey);font-size:1.1rem;line-height:1.6;margin-bottom:2.5rem;margin-left:auto;margin-right:auto;max-width:480px}.navLinks_NElv{display:flex;flex-wrap:wrap;gap:1rem;justify-content:center;margin-top:1rem}.navLink_KmGT{align-items:center;border-radius:8px;cursor:pointer;display:inline-flex;font-size:1rem;font-weight:600;gap:.5rem;padding:.75rem 1.5rem;transition:.25s}.navLink_KmGT:hover{transform:translateY(-2px)}.primaryLink_UP3n{background:var(--primary);color:var(--hero-background)}.primaryLink_UP3n:hover{box-shadow:0 4px 20px color-mix(in srgb,var(--primary) 50%,#0000);color:var(--hero-background)}.secondaryLink_VStI{background:#0000;border:2px solid var(--primary);color:var(--primary)}.secondaryLink_VStI:hover{background:color-mix(in srgb,var(--primary) 10%,#0000);color:var(--primary)}.particles_xv3x{inset:0;overflow:hidden;position:absolute;z-index:1}.particle_Q0bD{animation:linear infinite c;background:var(--primary);border-radius:50%;height:4px;opacity:.3;position:absolute;width:4px}.particle_Q0bD:first-child{animation-delay:-5s;animation-duration:19s;height:4px;left:48%;opacity:.11;top:66%;width:3px}.particle_Q0bD:nth-child(2){animation-delay:-3s;animation-duration:11s;height:4px;left:23%;opacity:.3;top:30%;width:6px}.particle_Q0bD:nth-child(3){animation-delay:-5s;animation-duration:11s;height:6px;left:78%;opacity:.3;top:10%;width:5px}.particle_Q0bD:nth-child(4){animation-delay:-1s;animation-duration:14s;height:4px;left:66%;opacity:.16;top:12%;width:4px}.particle_Q0bD:nth-child(5){animation-delay:-5s;animation-duration:10s;height:6px;left:15%;opacity:.35;top:49%;width:5px}.particle_Q0bD:nth-child(6){animation-delay:-5s;animation-duration:16s;height:3px;left:48%;opacity:.25;top:25%;width:4px}.particle_Q0bD:nth-child(7){animation-delay:-4s;animation-duration:20s;height:4px;left:49%;opacity:.3;top:78%;width:6px}.particle_Q0bD:nth-child(8){animation-delay:-1s;animation-duration:14s;height:6px;left:71%;opacity:.28;top:37%;width:5px}.particle_Q0bD:nth-child(9){animation-delay:-4s;animation-duration:19s;height:4px;left:21%;opacity:.38;top:72%;width:5px}.particle_Q0bD:nth-child(10){animation-delay:-3s;animation-duration:12s;height:5px;left:16%;opacity:.35;top:96%;width:5px}.particle_Q0bD:nth-child(11){animation-delay:-2s;animation-duration:13s;height:6px;left:46%;opacity:.12;top:38%;width:5px}.particle_Q0bD:nth-child(12){animation-delay:-2s;animation-duration:13s;height:3px;left:55%;opacity:.37;top:71%;width:5px}.particle_Q0bD:nth-child(13){animation-delay:-5s;animation-duration:17s;height:4px;left:91%;opacity:.4;top:5%;width:3px}.particle_Q0bD:nth-child(14){animation-delay:-5s;animation-duration:19s;height:4px;left:29%;opacity:.34;top:73%;width:6px}.particle_Q0bD:nth-child(15){animation-delay:-4s;animation-duration:9s;height:5px;left:24%;opacity:.31;top:27%;width:3px}.particle_Q0bD:nth-child(16){animation-delay:-1s;animation-duration:9s;height:5px;left:2%;opacity:.22;top:43%;width:4px}.particle_Q0bD:nth-child(17){animation-delay:-3s;animation-duration:20s;height:3px;left:2%;opacity:.13;top:27%;width:4px}.particle_Q0bD:nth-child(18){animation-delay:-5s;animation-duration:14s;height:5px;left:3%;opacity:.24;top:99%;width:6px}.particle_Q0bD:nth-child(19){animation-delay:-2s;animation-duration:20s;height:3px;left:42%;opacity:.32;top:37%;width:6px}.particle_Q0bD:nth-child(20){animation-delay:-1s;animation-duration:13s;height:5px;left:21%;opacity:.19;top:92%;width:4px}@keyframes c{0%,to{opacity:0;transform:translateY(0) translateX(0)}10%,90%{opacity:.3}50%{transform:translateY(-100px) translateX(30px)}}@keyframes d{0%,to{opacity:1}50%{opacity:.85}}.dropdownNavbarItemMobile_J0Sd,button.headers-card-title[data-v-ab19704d],button.schema-card-title[data-v-d9bd8110]{cursor:pointer}.iconLanguage_nlXk{margin-right:5px;vertical-align:text-bottom}.searchBar_RVTs .dropdownMenu_qbY6{background:var(--search-local-modal-background,#f5f6f7);border-radius:6px;box-shadow:var(--search-local-modal-shadow,inset 1px 1px 0 0 #ffffff80,0 3px 8px 0 #555a64);left:auto!important;margin-top:8px;padding:var(--search-local-spacing,12px);position:relative;right:0!important;width:var(--search-local-modal-width,560px)}.searchInput_YFbd:focus{outline:2px solid var(--search-local-input-active-border-color,var(--ifm-color-primary));outline-offset:0}div.ask-ai,html[data-theme=dark] div.ask-ai{--ask-ai-primary:var(--ifm-color-primary);--ask-ai-primary-hover:var(--ifm-color-primary-light);--ask-ai-foreground:var(--ifm-color-content);--ask-ai-border:var(--ifm-color-emphasis-300);--ask-ai-error:var(--ifm-color-danger);--ask-ai-button-bg:var(--ifm-color-emphasis-200)}.ask-ai{--ask-ai-background:var(--search-local-modal-background,#f5f6f7);--ask-ai-muted:var(--search-local-muted-color,#969faf)}html[data-theme=dark] .ask-ai{--ask-ai-background:var(--search-local-modal-background,var(--ifm-background-color));--ask-ai-muted:var(--search-local-muted-color,var(--ifm-color-secondary-darkest))}html[data-theme=dark] .searchBar_RVTs .dropdownMenu_qbY6{background:var(--search-local-modal-background,var(--ifm-background-color));box-shadow:var(--search-local-modal-shadow,inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309)}.searchBar_RVTs .dropdownMenu_qbY6 .suggestion_fB_2{align-items:center;background:var(--search-local-hit-background,#fff);border-radius:4px;box-shadow:var(--search-local-hit-shadow,0 1px 3px 0 #d4d9e1);color:var(--search-local-hit-color,#444950);cursor:pointer;display:flex;flex-direction:row;height:var(--search-local-hit-height,56px);padding:0 var(--search-local-spacing,12px);width:100%}.hitTree_kk6K,.noResults_l6Q3{align-items:center;display:flex}html[data-theme=dark] .dropdownMenu_qbY6 .suggestion_fB_2{background:var(--search-local-hit-background,var(--ifm-color-emphasis-100));box-shadow:var(--search-local-hit-shadow,none);color:var(--search-local-hit-color,var(--ifm-font-color-base))}.scalar-app .mb-1,.searchBar_RVTs .dropdownMenu_qbY6 .suggestion_fB_2:not(:last-child){margin-bottom:4px}.searchBar_RVTs .dropdownMenu_qbY6 .suggestion_fB_2.cursor_eG29{background-color:var(--search-local-highlight-color,var(--ifm-color-primary))}.hitFooter_E9YW a,.hitIcon_a7Zy,.hitPath_ieM4,.hitTree_kk6K,.noResultsIcon_EBY5{color:var(--search-local-muted-color,#969faf)}html[data-theme=dark] .hitIcon_a7Zy,html[data-theme=dark] .hitPath_ieM4,html[data-theme=dark] .hitTree_kk6K,html[data-theme=dark] .noResultsIcon_EBY5{color:var(--search-local-muted-color,var(--ifm-color-secondary-darkest))}.hitTree_kk6K>svg{height:var(--search-local-hit-height,56px);opacity:.5;width:24px}.hitIcon_a7Zy,.hitTree_kk6K>svg{stroke-width:var(--search-local-icon-stroke-width,1.4)}.hitAction_NqkB,.hitIcon_a7Zy,.icon[data-v-9551cf83],.scalar-app .size-5{height:20px;width:20px}.hitWrapper_sAK8{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;margin:0 8px;overflow-x:hidden;width:80%}.hitWrapper_sAK8 mark{background:none;color:var(--search-local-highlight-color,var(--ifm-color-primary))}.hitTitle_vyVt{font-size:.9em}.hitPath_ieM4{font-size:.75em}.hitPath_ieM4,.hitTitle_vyVt{overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.noResults_l6Q3{flex-direction:column;justify-content:center;padding:var(--search-local-spacing,12px) 0}.noResultsIcon_EBY5{margin-bottom:var(--search-local-spacing,12px)}.hitFooter_E9YW{font-size:.85em;margin-top:var(--search-local-spacing,12px);text-align:center}.suggestion_fB_2.cursor_eG29,.suggestion_fB_2.cursor_eG29 .hitIcon_a7Zy,.suggestion_fB_2.cursor_eG29 .hitPath_ieM4,.suggestion_fB_2.cursor_eG29 .hitTree_kk6K,.suggestion_fB_2.cursor_eG29 mark{color:var(--search-local-hit-active-color,var(--ifm-color-white))!important}.searchBarContainer_NW3z{margin-left:16px}.searchBarContainer_NW3z .searchBarLoadingRing_YnHq{display:none;left:10px;position:absolute;top:6px}.searchBarContainer_NW3z .searchClearButton_qk4g{background:none;border:none;line-height:1rem;padding:0;position:absolute;right:.8rem;top:50%;transform:translateY(-50%)}.dragover-above[data-v-a89d6a6e],.dragover-asChild[data-v-a89d6a6e],.dragover-below[data-v-a89d6a6e],.navbar__search,.scalar-app .relative{position:relative}.scalar-app .bg-none,.searchIndexLoading_EJ1f .navbar__search-input{background-image:none}.searchHintContainer_Pkmr{align-items:center;display:flex;gap:4px;height:100%;justify-content:center;pointer-events:none;position:absolute;right:10px;top:0}.searchHint_iIMx{background-color:var(--ifm-navbar-search-input-background-color);border:1px solid var(--ifm-color-emphasis-500);box-shadow:inset 0 -1px 0 var(--ifm-color-emphasis-500);color:var(--ifm-navbar-search-input-placeholder-color)}html[dir=rtl] .searchHintContainer_Pkmr{left:10px;right:auto}html[dir=rtl] .searchBarContainer_NW3z .searchClearButton_qk4g{left:.8rem;right:auto}html[dir=rtl] .searchBarContainer_NW3z .searchBarLoadingRing_YnHq{left:auto;right:10px}html[dir=rtl] .navbar__search-input{padding:0 2.25em 0 .5em}.loadingRing_RJI3{display:inline-block;height:20px;opacity:var(--search-local-loading-icon-opacity,.5);position:relative;width:20px}.loadingRing_RJI3 div{animation:1.2s cubic-bezier(.5,0,.5,1) infinite e;border:2px solid var(--search-load-loading-icon-color,var(--ifm-navbar-search-input-color));border-color:var(--search-load-loading-icon-color,var(--ifm-navbar-search-input-color)) #0000 #0000 #0000;border-radius:50%;box-sizing:border-box;display:block;height:16px;margin:2px;position:absolute;width:16px}.loadingRing_RJI3 div:first-child{animation-delay:-.45s}.loadingRing_RJI3 div:nth-child(2){animation-delay:-.3s}.loadingRing_RJI3 div:nth-child(3){animation-delay:-.15s}@keyframes e{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.navbarHideable_m1mJ{transition:transform var(--ifm-transition-fast) ease}.navbarHidden_jGov{transform:translate3d(0,calc(-100% - 2px),0)}.errorBoundaryError_a6uf{color:red;white-space:pre-wrap}.errorBoundaryFallback_VBag{color:red;padding:.55rem}.navbar__items--right>:last-child,.scalar-app .pr-0{padding-right:0}.footerLogoLink_BH7S{opacity:.5;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.footerLogoLink_BH7S:hover,.hash-link:focus,:hover>.hash-link{opacity:1}.anchorTargetStickyNavbar_Vzrq{scroll-margin-top:calc(var(--ifm-navbar-height) + .5rem)}.anchorTargetHideOnScrollNavbar_vjPI{scroll-margin-top:.5rem}.hash-link{opacity:0;padding-left:.5rem;transition:opacity var(--ifm-transition-fast);user-select:none}.hash-link:before{content:"#"}.mainWrapper_z2l0{display:flex;flex:1 0 auto;flex-direction:column}.docusaurus-mt-lg{margin-top:3rem}#__docusaurus{display:flex;flex-direction:column;min-height:100%}.searchContextInput_mXoe,.searchQueryInput_CFBF{background:var(--ifm-background-color);border:var(--ifm-global-border-width) solid var(--ifm-color-content-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-font-color-base);font-size:var(--ifm-font-size-base);margin-bottom:1rem;padding:.5rem;width:100%}.searchResultItem_U687{border-bottom:1px solid #dfe3e8;padding:1rem 0}.searchResultItemPath_uIbk{color:var(--ifm-color-content-secondary);font-size:.8rem;margin:.5rem 0 0}.searchResultItemSummary_oZHr{font-style:italic;margin:.5rem 0 0}.dashy-api-page{font-size:10px}.api-loading,.dashy-api-page main{min-height:calc(100vh - var(--ifm-navbar-height,60px))}.api-loading{color:var(--mid-grey,#888);display:grid;place-items:center}.dashy-api-page .parameter-item-trigger{align-items:normal}.dashy-api-page ol,.dashy-api-page ul,:not(.containsTaskList_mC6p>li)>.containsTaskList_mC6p{padding-left:0}.dashy-api-page .section{padding:30px 0}.dashy-api-page div.flex-col:nth-child(4)>div:nth-child(2),.scalar-app .\!hidden{display:none!important}.scalar-app .text-wrap{text-wrap:wrap}.scalar-app .-translate-x-full,.scalar-app .-translate-y-1\.5,.scalar-app .-translate-y-1\/2,.scalar-app .centered,.scalar-app .translate-x-0,.scalar-app .translate-x-2\.5,.scalar-app .translate-x-\[14px\],.scalar-app .translate-x-full,.scalar-app .translate-y-0,.scalar-app .translate-y-1\.5{translate:var(--tw-translate-x) var(--tw-translate-y)}.scalar-app .from-b-1,.scalar-app .to-b-1\.5,.scalar-app .to-b-2{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.scalar-app .from-b-1,.scalar-app .to-b-1\.5,.scalar-app .to-b-2,.scalar-app .to-transparent{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from) var(--tw-gradient-from-position),var(--tw-gradient-to) var(--tw-gradient-to-position))}.scalar-app .mask-y-from-\[calc\(100\%-8px\)\],.scalar-app .mask-y-to-100\%{--tw-mask-top:linear-gradient(to top,var(--tw-mask-top-from-color)var(--tw-mask-top-from-position),var(--tw-mask-top-to-color)var(--tw-mask-top-to-position));--tw-mask-bottom:linear-gradient(to bottom,var(--tw-mask-bottom-from-color)var(--tw-mask-bottom-from-position),var(--tw-mask-bottom-to-color)var(--tw-mask-bottom-to-position));-webkit-mask-composite:source-in;mask-composite:intersect;-webkit-mask-image:var(--tw-mask-linear),var(--tw-mask-radial),var(--tw-mask-conic);mask-image:var(--tw-mask-linear),var(--tw-mask-radial),var(--tw-mask-conic);--tw-mask-linear:var(--tw-mask-left),var(--tw-mask-right),var(--tw-mask-bottom),var(--tw-mask-top);--tw-mask-top:linear-gradient(to top,var(--tw-mask-top-from-color) var(--tw-mask-top-from-position),var(--tw-mask-top-to-color) var(--tw-mask-top-to-position));--tw-mask-bottom:linear-gradient(to bottom,var(--tw-mask-bottom-from-color) var(--tw-mask-bottom-from-position),var(--tw-mask-bottom-to-color) var(--tw-mask-bottom-to-position))}.scalar-app .backdrop-blur{--tw-backdrop-blur:blur(8px)}.scalar-app .active\:brightness-90:active{--tw-brightness:brightness(90%);filter:var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, )}.parameter-item-headers[data-v-be55ce3a],.scalar-app .markdown table{border:var(--scalar-border-width) solid var(--scalar-border-color)}.scalar-app .markdown pre code{margin:var(--markdown-spacing-sm) 0}:where(body.dark-mode)>.scalar-tooltip:before{--tw-shadow:inset 0 0 0 var(--tw-shadow-color,var(--scalar-border-width)) var(--scalar-border-color)}.full-size-styles[data-v-c36b47da]{border-right:var(--scalar-border-width) solid var(--scalar-border-color)}.apiPillRemove[data-v-e7c7c266]:hover,.authenticationProvided[data-v-e3416cd5],.collapsible-section-header[data-v-999a158a],.collapsible-section:hover .collapsible-section-trigger svg[data-v-999a158a],.documentNameActive[data-v-5e4bca68],.enum-toggle-button[data-v-d5367294]:hover,.property-const[data-v-b69e3089],.property-default-value[data-v-4da5c70a]:hover svg,.property-default:hover .property-default-label span[data-v-4da5c70a],.property-example-value[data-v-72def0ea]:hover svg,.property-example:hover .property-example-label span[data-v-72def0ea],.requestSuccess[data-v-acc2c0d8],.section-accordion-button:hover .section-accordion-chevron[data-v-9419dd23],.section-accordion-button:hover .section-accordion-chevron[data-v-ff689b94],.termsAgree[data-v-e7c7c266]:hover,button.headers-card-title[data-v-ab19704d]:hover,button.schema-card-title[data-v-d9bd8110]:hover{color:var(--scalar-color-1)}.authenticationProvided[data-v-e3416cd5],.authenticationRequired[data-v-d15ef40b],.requestApproved[data-v-bb311586],.requestFailed[data-v-bc27e533],.requestRejected[data-v-9803a54c],.requestSuccess[data-v-acc2c0d8]{align-items:center;display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);gap:6px;min-height:40px;position:relative}.authenticationRequired[data-v-d15ef40b],.hljs.language-curl .hljs-string,.hljs.language-php .hljs-variable,.scalar-app .hljs-built_in,.scalar-app .hljs-bullet,.scalar-app .hljs-doctag,.scalar-app .hljs-link,.scalar-app .hljs-meta,.scalar-app .hljs-name,.scalar-app .hljs-regexp,.scalar-app .hljs-section,.scalar-app .hljs-string,.scalar-app .hljs-subst,.scalar-app .hljs-symbol,.scalar-app .hljs-title,.scalar-app .markdown .markdown-alert.markdown-alert-note .markdown-alert-icon:before,.scalar-app .text-blue{color:var(--scalar-color-blue)}.askForAuthentication[data-v-8af07610]{border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);border-top:var(--scalar-border-width) solid var(--scalar-border-color);box-shadow:0 var(--scalar-border-width) 0 var(--scalar-background-1),0 calc(var(--scalar-border-width)*-1) 0 var(--scalar-background-1);display:flex;flex-direction:column;margin-bottom:12px;padding:0;position:relative;width:100%}.authContent[data-v-8af07610]{display:grid;margin:auto;max-width:520px;min-height:0;overflow:hidden;width:100%}.authContentInner[data-v-8af07610]>div{margin:36px 0 48px}.askForAuthentication.open .authContent[data-v-8af07610],.requestPreview.open .requestContent[data-v-0eb5f95d]{grid-template-rows:1fr}.continueButton[data-v-8af07610],.scalar-app .self-end{align-self:flex-end}.toggleButton[data-v-0eb5f95d],.toggleButton[data-v-8af07610]{align-items:center;background:none;border:none;border-radius:var(--scalar-radius-lg);color:var(--scalar-color-3);cursor:pointer;display:flex;justify-content:space-between;position:relative;text-align:left}.authContentInner[data-v-8af07610],.requestContentInner[data-v-0eb5f95d]{min-height:0;overflow:hidden}.authorizeButton[data-v-8af07610]{background:var(--scalar-color-blue)!important;color:#fff!important;display:flex;gap:5px;margin:0!important;z-index:1}.autosendPaused[data-v-d08225db],.buildingRequest[data-v-5749c429],.sendingRequest[data-v-65dc6dfb]{align-items:center;color:var(--scalar-color-blue);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);gap:6px;min-height:40px;position:relative}.playIcon[data-v-5749c429],.playIcon[data-v-653c66b3],.playIcon[data-v-65dc6dfb],.playIcon[data-v-9d9724d2]{align-items:center;background:var(--scalar-background-1);border-radius:50%;display:flex;height:16px;justify-content:center;padding:4px;position:relative;width:16px;z-index:1}.playIcon[data-v-5749c429]:before,.playIcon[data-v-65dc6dfb]:before{animation:.42s linear infinite g;background:var(--scalar-background-1);border:1.75px solid;border-bottom:1.75px solid #0000;border-image:initial;border-radius:50%;box-sizing:border-box;content:"";display:inline-block;height:16px;position:absolute;width:16px}.buildingRequest svg[data-v-5749c429],.loadingApiSpecs svg[data-v-9d9724d2],.sendingRequest svg[data-v-653c66b3],.sendingRequest svg[data-v-65dc6dfb]{border-radius:50%;height:100%;width:100%;z-index:1}.openapi-color,.requestApproved[data-v-bb311586],.scalar-app .hljs-addition,.scalar-app .hljs-literal,.scalar-app .hljs-selector-tag,.scalar-app .hljs-type,.scalar-app .text-green{color:var(--scalar-color-green)}.requestFailed[data-v-bc27e533],.requestRejected[data-v-9803a54c],.scalar-app .hljs-deletion,.scalar-app .text-red{color:var(--scalar-color-red)}.requestFailedIcon[data-v-bc27e533]{border-radius:50%;box-shadow:inset 0 0 0 1.5px currentColor;height:16px;padding:4px;width:16px}.light-mode .bg-preview[data-v-0956ad2d],.light-mode .bg-preview[data-v-92f84612],.light-mode .bg-preview[data-v-c02b5bb8]{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill-opacity='10%25'%3E%3Cpath d='M0 0h8v8H0zM8 8h8v8H8z'/%3E%3C/svg%3E")}.dark-mode .bg-preview[data-v-0956ad2d],.dark-mode .bg-preview[data-v-92f84612],.dark-mode .bg-preview[data-v-c02b5bb8]{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23FFF' fill-opacity='10%25'%3E%3Cpath d='M0 0h8v8H0zM8 8h8v8H8z'/%3E%3C/svg%3E")}.requestHeaderContainer[data-v-0eb5f95d]{align-items:center;display:flex;justify-content:space-between;padding:0 5px}.requestPreview[data-v-0eb5f95d]{border-radius:12px;display:flex;flex-direction:column;position:relative;width:100%}.requestContent[data-v-0eb5f95d]{display:grid;min-height:0;overflow:hidden}.code[data-v-0eb5f95d]{background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));border-radius:12px;display:flex;flex-direction:column;font-size:var(--scalar-font-size-4);margin-bottom:12px;overflow:hidden}.apiPill:hover .apiPillRemove[data-v-e7c7c266],.dark-mode .code[data-v-0eb5f95d],.dark-mode .nav-item[data-v-507381a3],.dark-mode .paymentInfo[data-v-7ab1200e],.dark-mode .promptForm[data-v-e7c7c266],.searchItem[data-v-7945f74c]:hover,.settingsButton[aria-disabled=true][data-v-e7c7c266]{background:var(--scalar-background-2)}.code h1[data-v-0eb5f95d]{color:var(--scalar-color-3);font-size:var(--scalar-font-size-3);padding:8px}.code[data-v-0eb5f95d] .codeBlock{max-height:calc(50vh - 100px);padding-top:0}.autosendContainer[data-v-0eb5f95d],.operation-title[data-v-55addca4]{display:flex;justify-content:space-between}.sendButton[data-v-0eb5f95d]{background:var(--scalar-color-blue);color:#fff;font-weight:var(--scalar-semibold);padding:5px 10px}.sendButton[data-v-0eb5f95d]:active,.sendButton[data-v-0eb5f95d]:hover{background:color-mix(in srgb,var(--scalar-color-blue),#000 10%);color:#fff!important}.executeRequestTool[data-v-3e825a81]{border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);border-top:var(--scalar-border-width) solid var(--scalar-border-color);box-shadow:0 var(--scalar-border-width) 0 var(--scalar-background-1),0 calc(var(--scalar-border-width)*-1) 0 var(--scalar-background-1);display:flex;flex-direction:column;gap:10px;margin-bottom:12px}.tool[data-v-3e825a81]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:15px;margin-bottom:20px;padding:15px}.contextItem[data-v-b6e5aa96]{align-items:center;background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));border-radius:12px;color:var(--scalar-color-2);cursor:pointer;display:inline-block;display:flex;font-size:10px;padding:5px 10px;vertical-align:middle}.light-mode .shimmer[data-v-b6e5aa96],.shimmer[data-v-b6e5aa96]{animation:1.4s ease-in-out infinite f;background:var(--scalar-background-2)}.shimmer[data-v-b6e5aa96]{background-image:linear-gradient(90deg,#202020 0,var(--scalar-background-2) 40%,var(--scalar-background-3) 80%);background-size:200% 100%}.light-mode .shimmer[data-v-b6e5aa96]{background-image:linear-gradient(90deg,#fafafa 0,var(--scalar-background-2) 40%,var(--scalar-background-3) 80%);background-size:200% 100%}@keyframes f{0%{background-position:200% 0}to{background-position:-200% 0}}.playIcon[data-v-653c66b3]:before,.playIcon[data-v-9d9724d2]:before{animation:.42s linear infinite g;background:var(--scalar-background-1);border:1.5px solid;border-bottom:1.5px solid #0000;border-image:initial;border-radius:50%;box-sizing:border-box;content:"";display:inline-block;height:16px;position:absolute;width:16px}.loadingApiSpecs[data-v-9d9724d2],.sendingRequest[data-v-653c66b3]{align-items:center;color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);gap:6px;margin-bottom:10px}.operations[data-v-ecee6203]{align-items:center;display:flex;gap:5px;margin-bottom:12px}.operations[data-v-ecee6203]:empty{margin-bottom:-12px}@keyframes g{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.catalogModal .scalar-modal-body,.children[data-v-84242873],.right[data-v-bc24f891]{display:flex;flex-direction:column}.searchInput[data-v-bc24f891]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);margin-bottom:10px}.catalog[data-v-bc24f891]{display:grid;font-size:var(--scalar-font-size-3);gap:10px;grid-template-columns:1fr 1fr;overflow-y:scroll}.item[data-v-bc24f891]{align-items:center;background-color:var(--scalar-background-2);border-radius:var(--scalar-radius-lg);display:flex;gap:10px;padding:15px;transition:background-color .16s}.item[data-v-bc24f891]:hover{background-color:color-mix(in srgb,var(--scalar-background-3),#0000 40%)!important}.logo[data-v-bc24f891]{width:25px}.item-top[data-v-bc24f891]{display:flex;gap:10px}.version[data-v-bc24f891]{background:var(--scalar-background-3);border-radius:var(--scalar-radius);color:var(--scalar-color-3);font-size:var(--scalar-font-size-5);padding:2px 5px}.description[data-v-bc24f891],.endpoint-label-path[data-v-55addca4] em,.property-detail-prefix[data-v-1295f965],.property-optional[data-v-b69e3089],.property-required[data-v-b69e3089],.schema-card-description[data-v-d9bd8110]{color:var(--scalar-color-2)}.dropdown-item[data-v-2d142bb5]{align-items:center;display:flex;gap:10px}.approvalSection[data-v-a7e6c699],.paymentSection[data-v-7ab1200e],.uploadSection[data-v-9551cf83]{align-items:center;background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 95%);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px 16px 0 0;display:flex;justify-content:space-between;margin-bottom:-16px;padding:8px 8px 24px 12px;position:absolute;top:0;transform:translate3d(0,calc(-100% + 16px),0);width:100%}.approvalText[data-v-7ab1200e],.approvalText[data-v-a7e6c699],.infoText[data-v-913a3815],.uploadText[data-v-9551cf83]{font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold)}.approveContainer[data-v-a7e6c699],.paymentContainer[data-v-7ab1200e]{display:flex;gap:5px}.actionButton[data-v-a7e6c699]{align-items:center;border-radius:50px;display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);padding:6px 12px}.rejectButton[data-v-7ab1200e],.rejectButton[data-v-a7e6c699]{background:var(--scalar-color-red);color:#fff}.rejectButton[data-v-7ab1200e]:active,.rejectButton[data-v-7ab1200e]:hover,.rejectButton[data-v-a7e6c699]:active,.rejectButton[data-v-a7e6c699]:hover{background:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 10%);color:#fff!important}.approveButton[data-v-a7e6c699]{background:var(--scalar-color-blue);color:#fff}.approveButton[data-v-7ab1200e]:active,.approveButton[data-v-7ab1200e]:hover,.approveButton[data-v-a7e6c699]:active,.approveButton[data-v-a7e6c699]:hover,.upgradeButton[data-v-913a3815]:active,.upgradeButton[data-v-913a3815]:hover{background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 10%);color:#fff!important}.error[data-v-63a481da]{background:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);position:absolute;top:0;transform:translate3d(0,calc(-100% + 16px),0)}.error[data-v-63a481da],.freeMessagesInfoSection[data-v-913a3815]{align-items:center;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px 16px 0 0;display:flex;margin-bottom:-16px;padding:8px 8px 24px 12px}.freeMessagesInfoSection[data-v-913a3815]{background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 95%);justify-content:space-between;position:relative;width:100%}.actionsContainer[data-v-913a3815]{align-items:center;display:flex;gap:8px}.actionButton[data-v-7ab1200e],.actionButton[data-v-913a3815]{align-items:center;border-radius:50px;display:flex;font-weight:var(--scalar-semibold);padding:6px 12px}.approveButton[data-v-7ab1200e],.upgradeButton[data-v-913a3815]{background:var(--scalar-color-blue);color:#fff;font-size:var(--scalar-font-size-3)}.closeButton[data-v-913a3815]{align-items:center;background:#0000;border:none;border-radius:50%;color:var(--scalar-color-2);cursor:pointer;display:flex;height:28px;justify-content:center;width:28px}.closeButton[data-v-913a3815]:hover{background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 80%);color:var(--scalar-color-1)}.paymentInfo[data-v-7ab1200e]{background:var(--scalar-background-1);border-radius:16px;bottom:70px;box-shadow:var(--scalar-shadow-2);opacity:0;padding:12px;pointer-events:none;position:absolute;right:0;transform:translate3d(0,-5px,0);transition:.2s ease-in-out;width:300px}.paymentInfo h3[data-v-7ab1200e]{font-size:var(--scalar-font-size-1);font-weight:var(--scalar-bold);margin-bottom:18px}.paymentInfo h3 span[data-v-7ab1200e]{font-size:var(--scalar-font-size-2)}.paymentContainer:hover .paymentInfo[data-v-7ab1200e]{opacity:1;transform:translateZ(0)}.paymentInfoItem[data-v-7ab1200e]{color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);justify-content:space-between;margin-top:8px}.paymentInfoSection[data-v-7ab1200e]:not(:last-child){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);padding-bottom:8px}.searchItem[data-v-7945f74c]{align-items:center;display:flex;font-size:var(--scalar-font-size-3);gap:9px;padding:8px 10px}.searchInput[data-v-7945f74c]{margin-bottom:5px}.apiPillLogo[data-v-e7c7c266],.searchItemLogo[data-v-7945f74c]{width:15px}.searchIcon[data-v-7945f74c]{margin-right:7px}.searchResultsEmpty[data-v-7945f74c]{color:var(--scalar-color-2);font-size:var(--scalar-font-size-3);margin:10px}.uploadSection.error[data-v-9551cf83]{background:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%)}.uploadSection.done[data-v-9551cf83]{background:color-mix(in srgb,var(--scalar-color-green),var(--scalar-background-1) 95%)}.actionContainer[data-v-e7c7c266]{background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px;box-shadow:0 24px 0 2px var(--scalar-background-1);position:relative;width:100%}.promptForm[data-v-e7c7c266]{background:var(--scalar-background-1);border-radius:16px;box-shadow:var(--scalar-shadow-1),0 0 0 var(--scalar-border-width) var(--scalar-border-color);display:flex;flex-direction:column;position:relative;width:100%}.addAPIButton[data-v-e7c7c266],.addAPIButton[data-v-e7c7c266]:hover,.scalar-app .markdown code{box-shadow:0 0 0 var(--scalar-border-width) var(--scalar-border-color)}.inputActionsContainer[data-v-e7c7c266]{display:flex;justify-content:space-between;padding:0 8px 8px}.inputActionsLeft[data-v-e7c7c266]{align-items:center;display:flex;flex-wrap:wrap;gap:5px}.inputActionsRight[data-v-e7c7c266]{display:flex;gap:5px;position:relative}.apiPill[data-v-e7c7c266]{align-items:center;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px;color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);gap:4px;height:28px;padding:0 8px;pointer-events:all;-webkit-user-select:none;user-select:none;z-index:1}.apiPillRemove[data-v-e7c7c266]{align-items:center;border-radius:50%;display:flex;height:24px;justify-content:center;margin-right:-6px;width:24px}.address-bar-history-button[data-v-a93fa60f]:hover,.address-bar-history-button[data-v-c15c6573]:hover,.dark-mode .addAPIButton[data-v-e7c7c266]:hover,.dark-mode .apiPill:hover .apiPillRemove[data-v-e7c7c266],.dark-mode .settingsButton[data-v-e7c7c266]:hover,.start-item[data-v-673f10b6]:hover{background:var(--scalar-background-3)}.addAPIButton[data-v-e7c7c266]:hover,code.property-detail-value[data-v-1295f965]{background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}.prompt[data-v-e7c7c266]{border:none;field-sizing:content;max-height:250px;max-width:100%;min-height:64px;outline:0;overflow-y:auto;resize:none;scrollbar-width:thin;width:100%;z-index:1;font-family:var(--scalar-font);font-size:16px;padding:12px 12px 14px}.prompt[data-v-e7c7c266]:disabled,.property-default-value[data-v-4da5c70a] svg,.property-enum-value-description[data-v-f4b54bdd],.property-example-value[data-v-72def0ea] svg,.scalar-app .text-c-3,.scalar-app .text-grey,[data-v-103d9d56] .cm-placeholder{color:var(--scalar-color-3)}.addAPIButton[data-v-e7c7c266]{align-items:center;border-radius:100%;color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-bold);gap:4px;height:28px;justify-content:center;pointer-events:all;width:28px;z-index:1}.settingsButton[data-v-e7c7c266]{border-radius:50%!important;color:var(--scalar-color-3)!important;margin:0!important;z-index:1}.sendButton[data-v-e7c7c266]{background:var(--scalar-color-blue)!important;border:var(--scalar-border-width) solid var(--scalar-color-blue);border-radius:50%!important;margin:0!important;z-index:1}.document[data-v-5e4bca68],.section-accordion-content[data-v-ff689b94]{border-top:var(--scalar-border-width) solid var(--scalar-border-color)}.sendButton[data-v-e7c7c266]:not([aria-disabled=true]){color:#fff!important}.ask-agent-scalar-send[data-v-f43e32ab]:hover,.sendButton[data-v-e7c7c266]:not([aria-disabled=true]):hover{background:color-mix(in srgb,var(--scalar-color-blue),#0000 10%)!important}.sendButton[aria-disabled=true][data-v-e7c7c266]{background:var(--scalar-background-2)!important;border:var(--scalar-border-width) solid var(--scalar-border-color);color:var(--scalar-color-3)!important}.cm-pill.bg-grey,.dark-mode .sendButton[aria-disabled=true][data-v-e7c7c266],.light-mode .cm-pill{background:var(--scalar-background-3)!important}.contextContainer[data-v-e7c7c266]{color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);justify-content:space-between;padding:10px 12px 12px;-webkit-user-select:none;user-select:none;width:100%}.settingsButton[data-v-e7c7c266]{border-radius:var(--scalar-radius-lg);font-weight:var(--scalar-semibold);padding:4px 6px}.settingsButton[data-v-e7c7c266]:hover{background:var(--scalar-background-2);box-shadow:0 0 var(--scalar-border-width) 0 var(--scalar-border-color);cursor:pointer}.agentLabel[data-v-e7c7c266]{cursor:text;font-size:0;height:100%;position:absolute;width:100%}.sendCheckboxContinue[data-v-e7c7c266]:has(input){align-items:center;background:var(--scalar-background-2);border-radius:14px;box-shadow:0 0 0 1.5px var(--scalar-background-2);color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);height:28px;-webkit-user-select:none;user-select:none}.dark-mode .sendCheckboxContinue[data-v-e7c7c266]:has(input){background:var(--scalar-background-3);box-shadow:0 0 0 1.5px var(--scalar-background-3)}.addMoreContext[data-v-e7c7c266]{align-items:center;color:var(--scalar-color-3);display:flex;font-size:var(--scalar-font-size-3);height:40px;padding:0 8px 0 12px;position:relative}.addMoreContext[data-v-e7c7c266]:before{background:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2));box-shadow:-.5px -.5px 0 var(--scalar-border-color),inset .5px .5px 1px var(--scalar-border-color);content:"";height:8px;left:18px;position:absolute;top:-3px;transform:rotate(45deg);width:8px}.dark-mode .addMoreContext[data-v-e7c7c266]:before{box-shadow:-.5px -.5px 0 var(--scalar-border-color)}.addAPIContext[data-v-e7c7c266]{align-items:center;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:50%;display:flex;height:28px;justify-content:center;width:28px}.termsAgree[data-v-e7c7c266]{align-items:center;border-radius:14px;cursor:pointer;display:flex;gap:5px;height:inherit;margin:0 5px}.termsAgree:hover .termsAgreeIcon[data-v-e7c7c266]{background:var(--scalar-color-1);color:var(--scalar-background-1)}.termsAgreeIcon[data-v-e7c7c266]{background:var(--scalar-background-2);border-radius:50%;height:inherit;padding:2px;width:inherit}.chat[data-v-db4ff6c9]{display:flex;flex:1;flex-direction:column;max-width:744px;padding:24px 0;width:100%}.userMessage[data-v-db4ff6c9]{background:var(--scalar-background-2);border-radius:18px;color:var(--scalar-color-1);font-size:16px;line-height:24px;margin-bottom:12px;margin-left:auto;max-width:80%;padding-bottom:6px;padding-top:6px;padding-inline:16px;width:fit-content}div+.userMessage[data-v-db4ff6c9]{margin-top:64px}.chat[data-v-db4ff6c9]>div:has(.executeRequestTool)+div:has(.executeRequestTool){margin-top:-12px}.spacer[data-v-db4ff6c9]{min-height:280px;width:100%}.formContainer[data-v-db4ff6c9]{bottom:20px;max-width:744px;position:fixed;width:100%;z-index:1}.chat[data-v-db4ff6c9] .markdown,.operation-details-card[data-v-55addca4] .parameter-list .parameter-list,.scalar-app .mb-3{margin-bottom:12px}.agentLogo[data-v-56f7e8dd]{margin-bottom:15px}.startContainer[data-v-56f7e8dd]{align-items:center;display:flex;flex-direction:column;height:100%;justify-content:center;max-width:720px;position:relative;width:100%}.heading[data-v-56f7e8dd]{font-size:1.5rem;font-weight:var(--scalar-font-bold);margin-bottom:50px}.documentName[data-v-31f052ad],.documentName[data-v-5e4bca68],.proxyUrlContainer label[data-v-5e4bca68],.scalar-app .hljs-name,.scalar-app .hljs-section,.scalar-app .hljs-strong,.schema-card-title[data-v-d9bd8110],.settingsHeading[data-v-5e4bca68]{font-weight:var(--scalar-semibold)}.disclaimerText[data-v-56f7e8dd]{color:var(--scalar-color-3);font-size:var(--scalar-font-size-3);line-height:1.44;margin-top:40px;text-align:center;text-wrap:balance}.wrapper[data-v-f1eee0af]{align-items:center;display:flex;flex-direction:column;height:100%;width:100%}.docSettings[data-v-31f052ad],.documentList[data-v-5e4bca68]{flex-direction:column;font-size:var(--scalar-font-size-3);margin-bottom:12px;display:flex}.docSettings[data-v-31f052ad]{gap:12px;max-height:600px}.settingsModal .scalar-modal-layout{z-index:10!important}.settingsModal .scalar-modal-body{overflow-x:hidden;overflow-y:auto}.document[data-v-5e4bca68]{border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);display:flex;flex-direction:column;left:-12px;padding:0 12px;position:relative;width:calc(100% + 24px)}.document[data-v-5e4bca68]:first-of-type:not(:last-of-type),.scalar-app .markdown tr:last-of-type td{border-bottom:none}.documentName[data-v-5e4bca68]{align-items:center;color:var(--scalar-color-2);display:flex;gap:4px;padding:12px 0}.settingsHeading[data-v-5e4bca68]{align-items:center;display:flex;font-size:19px;gap:5px;margin-bottom:12px}.proxyUrlContainer[data-v-5e4bca68]{display:flex;flex-direction:column;font-size:var(--scalar-font-size-3);gap:5px}.noDocuments[data-v-5e4bca68]{color:var(--scalar-color-2);margin-bottom:10px}.references-classic-header[data-v-8a3822ca]{align-items:center;display:flex;gap:12px;margin:auto;max-width:var(--refs-content-max-width);padding:12px 0}.references-classic-header-content[data-v-8a3822ca]{display:flex;flex-grow:1;gap:12px}.references-classic-header-container[data-v-8a3822ca]{padding:0 60px;position:relative}.references-classic-header-icon[data-v-8a3822ca]{color:var(--scalar-color-1);height:24px}.section-wrapper[data-v-ff689b94]{color:var(--scalar-color-1);margin-top:-12px;padding-top:12px}.section-accordion[data-v-ff689b94]{background:var(--scalar-background-2);border-radius:var(--scalar-radius-lg);display:flex;flex-direction:column;scroll-margin-top:var(--refs-viewport-offset)}.section-accordion-transparent[data-v-ff689b94]{background:#0000;border:var(--scalar-border-width) solid var(--scalar-border-color)}.section-accordion-button[data-v-ff689b94]{align-items:center;cursor:pointer;display:flex;gap:6px;padding:6px}.section-accordion-button-content[data-v-ff689b94],.section-column[data-v-699c28e3]{flex:1;min-width:0}.section-accordion-button-actions[data-v-ff689b94]{align-items:center;color:var(--scalar-color-3);display:flex;gap:6px}.section-accordion-chevron[data-v-ff689b94]{color:var(--scalar-color-3);cursor:pointer;margin-right:4px;opacity:1}.section-accordion-content[data-v-ff689b94]{display:flex;flex-direction:column}.section-accordion-description[data-v-ff689b94]{color:var(--scalar-color--1);font-size:var(--scalar-mini);font-weight:var(--scalar-semibold);padding:10px 12px 0}.section-accordion-content-card[data-v-ff689b94] .property:last-of-type{padding-bottom:9px}.screenreader-only[data-v-df2e1026]{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0}.schema-type-icon[data-v-70cb5c13]{color:var(--scalar-color-1);display:none}.schema-type[data-v-70cb5c13]{color:var(--scalar-color-1);font-family:var(--scalar-font-code)}.error[data-v-d9bd8110],.scalar-app .bg-red{background-color:var(--scalar-color-red)}.schema-card[data-v-d9bd8110]{color:var(--scalar-color-1);font-size:var(--scalar-font-size-4)}.schema-card-title[data-v-d9bd8110]{align-items:center;border-bottom:var(--scalar-border-width) solid #0000;color:var(--scalar-color-2);display:flex;font-size:var(--scalar-mini);gap:4px;height:var(--schema-title-height);padding:6px 8px}.schema-card-title-icon--open[data-v-d9bd8110]{transform:rotate(45deg)}.headers-properties-open>.headers-card-title[data-v-ab19704d],.schema-properties-open>.schema-card-title[data-v-d9bd8110]{border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom-left-radius:0;border-bottom-right-radius:0}.scalar-app .w-fit,.schema-properties-open>.schema-properties[data-v-d9bd8110]{width:fit-content}.schema-card-description+.schema-properties[data-v-d9bd8110]{margin-top:8px;width:fit-content}.schema-card--level-0:first-of-type>.schema-card-description[data-v-d9bd8110]:has(+.schema-properties){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);margin-bottom:-8px;padding-bottom:8px}.schema-card--level-0~.schema-card--level-0>.schema-card-description[data-v-d9bd8110]:has(+.schema-properties){padding-top:8px}.scalar-app .container,.scalar-app .w-full,.schema-properties-name[data-v-d9bd8110],.schema-properties-open.schema-properties[data-v-d9bd8110],.schema-properties-open>.schema-card--open[data-v-d9bd8110],.schema-properties-open[data-v-d9bd8110]{width:100%}.schema-properties[data-v-d9bd8110]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);display:flex;flex-direction:column;width:fit-content}.schema-properties .schema-properties[data-v-d9bd8110]{border-radius:13.5px}.scalar-app .rounded-lg,.schema-properties .schema-properties.schema-properties-open[data-v-d9bd8110]{border-radius:var(--scalar-radius-lg)}.scalar-app .self-start,.schema-card--compact[data-v-d9bd8110]{align-self:flex-start}.headers-card.headers-card--open[data-v-ab19704d],.schema-card--compact.schema-card--open[data-v-d9bd8110]{align-self:auto}.schema-card-title--compact[data-v-d9bd8110]{border-bottom:none;color:var(--scalar-color-2);height:auto;padding:6px 10px 6px 8px}.fetchErrorCard_M8w3 p,.home-page-features-wrapper .feature .feature-title h3,.markdown[data-v-dc47ca65] *,.scalar-app .m-0,.schema-card-title--compact>.schema-card-title-icon[data-v-d9bd8110]{margin:0}.headers-card-title-icon--open[data-v-ab19704d],.schema-card-title--compact>.schema-card-title-icon--open[data-v-d9bd8110]{transform:rotate(45deg)}.scalar-app .static,.schema-properties-open>.schema-card-title--compact[data-v-d9bd8110]{position:static}.property--level-0>.schema-properties>.schema-card--level-0>.schema-properties[data-v-d9bd8110],:not(.composition-panel)>.schema-card--compact.schema-card--level-0>.schema-properties[data-v-d9bd8110]{border:none}.property--level-0 .schema-card--level-0:not(.schema-card--compact) .property--level-1[data-v-d9bd8110]{padding:0 0 8px}[data-v-d9bd8110] .schema-card-description p{color:var(--scalar-color-2);display:block;font-size:var(--scalar-small,var(--scalar-paragraph));line-height:1.5;margin-bottom:6px}.children .schema-card-description[data-v-d9bd8110]:first-of-type{padding-top:0}.property-enum-value[data-v-f4b54bdd]{align-items:stretch;color:var(--scalar-color-3);display:flex;line-height:1.5;overflow-wrap:break-word;position:relative;--decorator-width:1px;--decorator-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-color-1) 25%)}.property-enum-value-content[data-v-f4b54bdd]{display:flex;flex-direction:column;padding:3px 0}.property-enum-value-label[data-v-f4b54bdd]{color:var(--scalar-color-1);font-family:var(--scalar-font-code);font-size:var(--scalar-font-size-4);position:relative}.property-enum-value[data-v-f4b54bdd]:before{background-color:var(--decorator-color);content:"";display:block;margin-right:12px;width:var(--decorator-width)}.property-enum-value[data-v-f4b54bdd]:last-of-type:before,.property-enum-values:has(.enum-toggle-button) .property-enum-value[data-v-f4b54bdd]:nth-last-child(2):before{height:calc(.5lh + 4px)}.property-enum-value-label[data-v-f4b54bdd]:after{background-color:var(--decorator-color);content:"";height:var(--decorator-width);left:-12px;position:absolute;top:.5lh;width:8px}.property-enum-value[data-v-f4b54bdd]:last-of-type:after{background:var(--scalar-background-1);border-top:var(--scalar-border-width) solid var(--decorator-color);bottom:0;height:50%}.property-list[data-v-d5367294]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius);margin-top:10px}.property-list .property[data-v-d5367294]:last-of-type{padding-bottom:10px}.property-enum-values[data-v-d5367294]{font-size:var(--scalar-font-size-3);list-style:none;margin-top:8px;padding-left:2px}.property-default[data-v-4da5c70a],.property-example[data-v-72def0ea]{display:flex;flex-direction:column;font-size:var(--scalar-mini);position:relative}.property-default[data-v-4da5c70a]:hover:before,.property-example[data-v-72def0ea]:hover:before{border-radius:var(--scalar-radius);content:"";height:20px;left:0;position:absolute;top:0;width:100%}.property-default-label span[data-v-4da5c70a],.property-example-label span[data-v-72def0ea]{border-bottom:var(--scalar-border-width) dotted;color:var(--scalar-color-3);position:relative}.property-default-value[data-v-4da5c70a],.property-example-value[data-v-72def0ea]{align-items:center;display:flex;font-family:var(--scalar-font-code);gap:8px;padding:6px;width:100%;background:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius)}.property-default-value span[data-v-4da5c70a],.property-example-value span[data-v-72def0ea]{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.property-default-value-list[data-v-4da5c70a],.property-example-value-list[data-v-72def0ea]{background-color:var(--scalar-background-1);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);box-shadow:var(--scalar-shadow-1);display:none;flex-direction:column;gap:3px;left:50%;max-width:300px;min-width:200px;overflow:auto;padding:9px;position:absolute;top:18px;transform:translate3d(-50%,0,0);z-index:2}.property-default:focus-within .property-default-value-list[data-v-4da5c70a],.property-default:hover .property-default-value-list[data-v-4da5c70a]{display:flex}.property-detail+.property-detail[data-v-1295f965]:before,.schema>span[data-v-4df72868]:not(:first-child):before,.schema>span[data-v-f2ab7aa3]:not(:first-child):before{content:"·";display:block;margin:0 .5ch}code.property-detail-value[data-v-1295f965]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius);color:var(--scalar-color-2);font-family:var(--scalar-font-code);font-size:var(--scalar-font-size-3);padding:0 4px}.property-example:focus-within .property-example-value-list[data-v-72def0ea],.property-example:hover .property-example-value-list[data-v-72def0ea]{display:flex}.badge[data-v-3dedb7e4]{background:var(--badge-background-color,var(--scalar-background-2));border:var(--scalar-border-width) solid var(--badge-border-color,var(--scalar-border-color));border-radius:12px;color:var(--badge-text-color,var(--scalar-color-2));display:inline-block;font-size:var(--scalar-mini);padding:2px 6px}.badge.text-orange[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-orange),#0000 90%);border:#0000}.badge.text-yellow[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-yellow),#0000 90%);border:#0000}.badge.text-red[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-red),#0000 90%);border:#0000}.badge.text-purple[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-purple),#0000 90%);border:#0000}.badge.text-green[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-green),#0000 90%);border:#0000}.property[data-v-84242873]:not(:last-of-type),.section[data-v-be4443e9]:not(:last-of-type){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.property-heading[data-v-b69e3089]{align-items:baseline;display:flex;flex-wrap:wrap;row-gap:9px;white-space:nowrap}.property-heading[data-v-b69e3089]:has(+.children),.property-heading[data-v-b69e3089]:has(+.property-rule){margin-bottom:9px}.operation-details-card[data-v-55addca4] .contents button,.operation-details-card[data-v-55addca4] .selected-content-type,.property-heading[data-v-b69e3089]>*{margin-right:9px}.property-heading>.property-detail[data-v-b69e3089]:not(:last-of-type),.property-heading[data-v-b69e3089]:last-child{margin-right:0}.property-name[data-v-b69e3089]{font-size:var(--scalar-font-size-4);font-weight:var(--scalar-bold);max-width:100%;overflow-wrap:break-word;white-space:normal}.property-additional[data-v-b69e3089],.property-name[data-v-b69e3089],.scalar-app .\[\&_code\]\:font-code code,.scalar-app .font-code{font-family:var(--scalar-font-code)}.property-required[data-v-b69e3089]{color:var(--scalar-color-orange);font-size:var(--scalar-mini)}.property-read-only[data-v-b69e3089]{color:var(--scalar-color-blue);font-size:var(--scalar-mini)}.property-write-only[data-v-b69e3089]{color:var(--scalar-color-green);font-size:var(--scalar-mini)}.property-discriminator[data-v-b69e3089]{color:var(--scalar-color-purple);font-size:var(--scalar-mini)}.property-detail[data-v-b69e3089]{align-items:center;color:var(--scalar-color-2);display:flex;font-size:var(--scalar-mini);min-width:0}.deprecated[data-v-3c3c5405] *,.deprecated[data-v-ad8530a6],.deprecated[data-v-b69e3089],.deprecated[data-v-ec6c8861],.scalar-app .markdown del{-webkit-text-decoration:line-through;text-decoration:line-through}.property[data-v-84242873]{color:var(--scalar-color-1);display:flex;flex-direction:column;font-size:var(--scalar-small);padding:10px;position:relative}.property.property--level-0[data-v-84242873]:has(>.property-rule>.schema-card>.schema-properties.schema-properties-open>ul>li.property){padding-top:0}.property--compact.property--level-0[data-v-84242873],.property--compact.property--level-1[data-v-84242873]{padding:10px 0}.property--compact.property--level-0 .composition-panel .property--compact.property--level-1[data-v-84242873],.scalar-app .p-2{padding:8px}.property[data-v-84242873]:has(>.property-rule:first-of-type):not(.property--compact){padding-bottom:8px;padding-top:8px}.property--deprecated[data-v-84242873]{background:repeating-linear-gradient(-45deg,var(--scalar-background-2) 0,var(--scalar-background-2) 2px,#0000 2px,#0000 5px);background-size:100%}.property--deprecated[data-v-84242873]>*{opacity:.75}.property-description[data-v-84242873]{font-size:var(--scalar-small);line-height:1.4;margin-top:6px}.property-description[data-v-84242873]:has(+.property-rule){margin-bottom:9px}.request-body-description[data-v-17941e59] .markdown *,[data-v-84242873] .property-description *{color:var(--scalar-color-2)!important}.children+.property-rule[data-v-84242873],.property-description+.children[data-v-84242873]{margin-top:9px}.children .property--compact.property--level-1[data-v-84242873]{padding:12px}.property-example-value[data-v-84242873]{all:unset;border-top:var(--scalar-border-width) solid var(--scalar-border-color);font-family:var(--scalar-font-code)}.property-rule[data-v-84242873]{border-radius:var(--scalar-radius-lg);display:flex;flex-direction:column}.property-rule[data-v-84242873] .composition-panel .schema-card--level-1>.schema-properties.schema-properties-open,.start-section-colors .start-item-active[data-v-673f10b6]:last-of-type:before,.start-section-colors .start-item[data-v-673f10b6]:last-of-type{border-radius:0 0 var(--scalar-radius-lg) var(--scalar-radius-lg)}.property-rule[data-v-84242873] .composition-panel>.schema-card>.schema-card-description{border-left:var(--scalar-border-width) solid var(--scalar-border-color);border-right:var(--scalar-border-width) solid var(--scalar-border-color);padding:10px}.callback-operation-container[data-v-df994c31] .parameter-list,.callback-operation-container[data-v-df994c31] .request-body,.callback-operation-container[data-v-df994c31] .request-body-description,.callback-operation-container[data-v-df994c31] .request-body-header,.introduction-card-row[data-v-5764c94a] .scalar-card:nth-of-type(2) .scalar-card-header.scalar-card--borderless+.scalar-card-content,.property-rule[data-v-84242873] .composition-panel>.schema-card>.schema-card-description+.schema-properties,.scalar-app .mt-0{margin-top:0}.property-example[data-v-84242873]{background:#0000;border:none;display:flex;flex-direction:row;gap:8px}.property-example-label[data-v-84242873],.property-example-value[data-v-84242873]{padding:3px 0 0}.property-example-value[data-v-84242873]{background:var(--scalar-background-2);border-radius:var(--scalar-radius);border-top:0;padding:3px 4px}.collapsible-section[data-v-999a158a],.section-container[data-v-20a1472a]{border-top:var(--scalar-border-width) solid var(--scalar-border-color);position:relative}.property-name[data-v-84242873]{font-family:var(--scalar-font-code);font-weight:var(--scalar-semibold)}.property-name-additional-properties[data-v-84242873]:before,.property-name-pattern-properties[data-v-84242873]:before{background-color:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius);color:var(--scalar-color-1);display:inline-block;font-size:var(--scalar-micro);margin-right:4px;padding:2px 4px;text-transform:uppercase}.property-name-pattern-properties[data-v-84242873]:before{content:"regex"}.property-name-additional-properties[data-v-84242873]:before{content:"unknown property name"}.section-header-label[data-v-f1ac6c38]{display:inline}.reference-models-anchor[data-v-e4ca3c0f]{align-items:center;color:var(--scalar-color-1);display:flex;font-size:20px;padding-left:6px}.reference-models-label[data-v-e4ca3c0f]{display:block;font-size:var(--scalar-mini)}.reference-models-label[data-v-e4ca3c0f] em,.scalar-app .markdown b,.scalar-app .markdown strong{font-weight:var(--scalar-bold)}.section[data-v-be4443e9]{display:flex;flex-direction:column;margin:auto;max-width:var(--refs-content-max-width);padding:90px 0;position:relative;scroll-margin-top:var(--refs-viewport-offset)}.section[data-v-be4443e9]:has(~div.contents){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.references-classic .section[data-v-be4443e9]{gap:24px;padding:48px 0}.collapsible-section .collapsible-section-trigger[data-v-999a158a]{align-items:center;cursor:pointer;display:flex;font-size:var(--scalar-font-size-3);padding:10px 0;position:relative;z-index:1}.collapsible-section-trigger svg[data-v-999a158a]{color:var(--scalar-color-3);left:-19px;position:absolute}.collapsible-section .collapsible-section-trigger[data-v-999a158a] .anchor-copy{line-height:18.5px}.collapsible-section-content[data-v-999a158a]{margin:0 0 10px;padding:0;scroll-margin-top:140px}.section-container[data-v-20a1472a]{padding:0 60px;width:100%}.section-container[data-v-20a1472a]:has(.introduction-section){border-top:none}.section-accordion-wrapper[data-v-9419dd23]{padding:0 60px}.section-accordion[data-v-9419dd23]{margin:auto;max-width:var(--refs-content-max-width);position:relative;width:100%}.section-accordion-content[data-v-9419dd23]{display:flex;flex-direction:column;gap:12px;padding-top:12px}.section-accordion-button[data-v-9419dd23]{border-radius:var(--scalar-radius);cursor:pointer;display:flex;margin:-6px 0;padding:6px 0;width:100%}.section-accordion-chevron[data-v-9419dd23]{color:var(--scalar-color-3);left:-22px;position:absolute;top:12px}.section-accordion-title[data-v-9419dd23]{align-items:flex-start;display:flex;flex:1;flex-direction:column;padding:0 6px}.section-accordion-title[data-v-9419dd23] .section-header-wrapper{grid-template-columns:1fr}.show-more[data-v-d1c2b649]{align-items:center;appearance:none;border:none;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:30px;color:var(--scalar-color-1);display:flex;font-size:var(--scalar-small);font-weight:var(--scalar-semibold);gap:6px;justify-content:center;margin:auto;padding:8px 12px 8px 16px;position:relative;top:-48px}.show-more[data-v-d1c2b649]:hover{background:var(--scalar-background-2);cursor:pointer}.show-more[data-v-d1c2b649]:active{box-shadow:0 0 0 1px var(--scalar-border-color)}.loading[data-v-8e0226d7]{animation:1.5s infinite alternate h;background:var(--scalar-background-3);border-radius:var(--scalar-radius-lg);margin:.6em 0;max-width:100%;min-height:1.6em}.loading[data-v-8e0226d7]:first-of-type{margin-bottom:24px;margin-top:0;min-height:3em}.loading[data-v-8e0226d7]:last-of-type{width:60%}.loading.single-line[data-v-8e0226d7]{margin:.6em 0;max-width:80%;min-height:3em}@keyframes h{0%{opacity:1}to{opacity:.33}}.section-header-wrapper[data-v-465a7a78]{display:grid;grid-template-columns:1fr}.section-header[data-v-465a7a78]{color:var(--scalar-color-1);font-size:var(--font-size,var(--scalar-heading-1));font-weight:var(--font-weight,var(--scalar-bold));word-wrap:break-word;line-height:1.45;margin-bottom:12px;margin-top:0}.scalar-app .mb-1\.5,.section-header.tight[data-v-465a7a78]{margin-bottom:6px}.section-header.loading[data-v-465a7a78]{width:80%}.operation-path[data-v-ec6c8861]{overflow:hidden;font-weight:var(--scalar-semibold);line-break:anywhere}.headers-card[data-v-ab19704d]{align-self:flex-start;color:var(--scalar-color-1);font-size:var(--scalar-font-size-4);margin-bottom:6px;margin-top:12px;position:relative;z-index:0}.headers-card-title[data-v-ab19704d]{align-items:center;border-radius:13.5px;color:var(--scalar-color-3);display:flex;font-size:var(--scalar-micro);font-weight:var(--scalar-semibold);gap:4px;padding:6px 10px}.headers-properties[data-v-ab19704d]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:13.5px;display:flex;flex-direction:column;width:fit-content}.headers-properties-open[data-v-ab19704d]{border-radius:var(--scalar-radius-lg);width:100%}.headers-card .property[data-v-ab19704d]:last-of-type{padding-bottom:10px}.headers-card-title>.headers-card-title-icon[data-v-ab19704d]{height:14px;margin:0;width:14px}.headers-card-title>.headers-card-title-icon--open[data-v-ab19704d],.toggle-icon-sun-ray[data-v-60be8692]:nth-of-type(3){transform:rotate(45deg)}.parameter-item[data-v-be55ce3a]{border-top:var(--scalar-border-width) solid var(--scalar-border-color);display:flex;flex-direction:column;position:relative}.codeBlockStandalone_MEMb,.operation-details-card[data-v-55addca4] .request-body-schema .property--level-0,.parameter-item-container[data-v-be55ce3a],.scalar-app .p-0,.scalar-card-header.scalar-card-header-tabs[data-v-fec8fbbb],[data-v-04661eb4] .cm-editor,[data-v-28c8509c] .cm-editor,[data-v-3157c3c7] .cm-editor,[data-v-36811e28] .cm-editor,[data-v-83bfcc8a] .cm-editor,[data-v-c1a50a6e] .cm-editor,[data-v-cb2a35da] .cm-line,[data-v-ddfccc08] .cm-editor,[data-v-f5565642] .cm-line{padding:0}.parameter-item-name[data-v-be55ce3a]{color:var(--scalar-color-1);font-family:var(--scalar-font-code);font-size:var(--scalar-font-size-4);font-weight:var(--scalar-bold);overflow-wrap:break-word;position:relative}.parameter-item-description-summary[data-v-be55ce3a],.parameter-item-description[data-v-be55ce3a]{color:var(--scalar-color-2);font-size:var(--scalar-mini)}.parameter-item-description-summary.parameter-item-description-summary[data-v-be55ce3a]>*{--markdown-line-height:1}.parameter-item-trigger+.parameter-item-container[data-v-be55ce3a] .property--level-0>.property-heading .property-detail-value{font-size:var(--scalar-micro)}.parameter-item-required-optional[data-v-be55ce3a]{color:var(--scalar-color-2);font-weight:var(--scalar-semibold);margin-right:6px;position:relative}.parameter-item--required[data-v-be55ce3a]{color:var(--scalar-color-orange);font-size:var(--scalar-micro);font-weight:var(--scalar-semibold);text-transform:uppercase}.parameter-item-description[data-v-be55ce3a]{color:var(--scalar-color-2);font-size:var(--scalar-small);line-height:1.4;margin-top:6px}.parameter-item-description[data-v-be55ce3a] p{color:var(--scalar-color-2);font-size:var(--scalar-small);line-height:1.4;margin-top:4px}.parameter-schema[data-v-be55ce3a]{margin-top:3px;padding-bottom:9px}.parameter-item-trigger[data-v-be55ce3a]{align-items:baseline;display:flex;flex-wrap:wrap;gap:6px;line-height:var(--scalar-line-height-5);outline:0;padding:10px 0}.parameter-item-trigger-open[data-v-be55ce3a],.scalar-app .pb-0{padding-bottom:0}.parameter-item-icon[data-v-be55ce3a]{color:var(--scalar-color-3);left:-19px;position:absolute;top:.5lh;translate:0 -50%}.scalar-app .-translate-x-1\/2,.scalar-app .-translate-x-full,.scalar-app .-translate-y-1\.5,.scalar-app .-translate-y-1\/2,.scalar-app .centered,.scalar-app .centered-x,.scalar-app .centered-y,.scalar-app .translate-x-0,.scalar-app .translate-x-1\/2,.scalar-app .translate-x-2\.5,.scalar-app .translate-x-\[14px\],.scalar-app .translate-x-full,.scalar-app .translate-y-0,.scalar-app .translate-y-1\.5,.scalar-app .translate-y-1\/2{translate:var(--tw-translate-x) var(--tw-translate-y)}.parameter-item-trigger:focus-visible .parameter-item-icon[data-v-be55ce3a],.parameter-item-trigger:hover .parameter-item-icon[data-v-be55ce3a]{color:var(--scalar-color-1)}.parameter-item-trigger:focus-visible .parameter-item-icon[data-v-be55ce3a]{border-radius:var(--scalar-radius);outline:1px solid var(--scalar-color-accent);outline-offset:2px}.request-body[data-v-17941e59],.scalar-app .mt-6,.start-h1[data-v-673f10b6]:not(:first-of-type){margin-top:24px}.request-body-header[data-v-17941e59]{align-items:center;border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);display:flex;flex-flow:wrap;justify-content:space-between;padding-bottom:12px}.request-body-title[data-v-17941e59]{align-items:center;color:var(--scalar-color-1);display:flex;font-size:var(--scalar-font-size-2);font-weight:var(--scalar-semibold);gap:8px}.request-body-required[data-v-17941e59]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px;color:var(--scalar-color-orange);font-size:var(--scalar-micro);font-weight:400;height:20px;padding:2px 8px}.request-body-description[data-v-17941e59]{font-size:var(--scalar-small);margin-top:6px;width:100%}.request-body-header+.request-body-schema[data-v-17941e59]:has(>.schema-card>.schema-card-description),.request-body-header+.request-body-schema[data-v-17941e59]:has(>.schema-card>.schema-properties>*>.property--level-0){padding-top:8px}.callback-sticky-offset[data-v-df994c31]{top:var(--refs-viewport-offset,0);z-index:1}.callback-operation-container[data-v-df994c31] .request-body-header{--scalar-font-size-2:var(--scalar-font-size-4);background:color-mix(in srgb,var(--scalar-background-2) 50%,#0000);border-bottom:none;border:.5px solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg) var(--scalar-radius-lg) 0 0;padding:10px}.callback-operation-container[data-v-df994c31] .request-body-schema>.schema-card>.schema-card-description,.scalar-app .px-2{padding-inline:8px}.callback-operation-container[data-v-df994c31] ul li.property.property--level-1,.scalar-app .p-2\.5{padding:10px}.callback-operation-container[data-v-df994c31] .request-body-schema{background-color:var(--scalar-background-1);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:0 0 var(--scalar-radius-lg) var(--scalar-radius-lg);border-top:none;overflow:hidden}.callback-operation-container[data-v-df994c31] .parameter-list-title{background:color-mix(in srgb,var(--scalar-background-2) 50%,#0000);border:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom:none;border-radius:var(--scalar-radius-lg) var(--scalar-radius-lg) 0 0;margin-bottom:0;padding:10px;--scalar-font-size-2:var(--scalar-font-size-4)}.callback-operation-container[data-v-df994c31] .parameter-list-items{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:0 0 var(--scalar-radius-lg) var(--scalar-radius-lg)}.callback-operation-container[data-v-df994c31] .parameter-list-items>li:first-of-type{border-top:none}.callback-operation-container[data-v-df994c31] .parameter-list-items>li{padding:0 8px}.empty-state[data-v-fa0d0b45]{align-items:center;border-radius:var(--scalar-radius-lg);color:var(--scalar-color-2);display:flex;font-size:var(--scalar-mini);justify-content:center;margin:10px 0 10px 12px;min-height:56px;text-align:center}.rule-title[data-v-fa0d0b45]{border-radius:var(--scalar-radius);color:var(--scalar-color-1);display:inline-block;font-family:var(--scalar-font-code);margin:12px 0 6px}.endpoint-type[data-v-55addca4],.show-api-client-button[data-v-342ba62a],.tab[data-v-804dba49]{font-family:var(--scalar-font);position:relative}.rule[data-v-fa0d0b45]{border-radius:var(--scalar-radius-lg);margin:0 12px}.rule-items[data-v-fa0d0b45]{border-left:1px solid var(--scalar-border-color);counter-reset:list-number;display:flex;flex-direction:column;gap:12px;padding:12px 0}.rule-item[data-v-fa0d0b45]{border:1px solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);counter-increment:list-number;margin-left:24px;overflow:hidden}.rule-item[data-v-fa0d0b45]:before{border:1px solid var(--scalar-border-color);border-radius:0 0 0 var(--scalar-radius-lg);border-right:0;border-top:0;color:var(--scalar-color-2);color:var(--scalar-color-1);content:" ";display:block;height:6px;margin-top:6px;position:absolute;transform:translate(-25px);width:24px}.tab[data-v-804dba49]{background:none;border:none;color:var(--scalar-color-2);cursor:pointer;font-size:var(--scalar-small);font-weight:var(--scalar-font-normal);line-height:calc(var(--scalar-small) + 2px);line-height:22px;margin-right:3px;padding:0;text-transform:uppercase;white-space:nowrap}.tab[data-v-804dba49]:before{background:var(--scalar-background-3);border-radius:var(--scalar-radius);content:"";height:calc(100% + 4px);left:-6px;opacity:0;position:absolute;top:-2px;width:calc(100% + 12px);z-index:0}.tab[data-v-804dba49]:focus-visible:before,.tab[data-v-804dba49]:hover:before{opacity:1}.tab[data-v-804dba49]:focus-visible:before{outline:1px solid var(--scalar-color-accent)}.tab span[data-v-804dba49]{position:relative;z-index:1}.tab-selected[data-v-804dba49]{color:var(--scalar-color-1);font-weight:var(--scalar-semibold)}.tab-selected[data-v-804dba49]:after{background:currentColor;bottom:calc(var(--tab-list-padding-y)*-1);content:"";height:1px;left:0;position:absolute;width:100%}.tab-list[data-v-fec8fbbb]{display:flex;flex:1;gap:6px;position:relative;--tab-list-padding-y:7px;--tab-list-padding-x:12px;overflow:auto;padding:var(--tab-list-padding-y) var(--tab-list-padding-x)}.request-card[data-v-cbc307e3],.response-card[data-v-dc47ca65]{font-size:var(--scalar-font-size-3)}.code-copy[data-v-dc47ca65]{align-items:center;appearance:none;-webkit-appearance:none;background:#0000;border:none;color:var(--scalar-color-3);cursor:pointer;display:flex;justify-content:center;margin-right:12px;outline:0;padding:0}.client-libraries[data-v-6a49c111]:active,.code-copy[data-v-dc47ca65]:hover,.hljs.language-curl .hljs-literal,.hljs.language-html,.hljs.language-objectivec .hljs-meta,.scalar-app .\[\&_a\:hover\]\:text-c-1 a:hover,.scalar-app .\[\&_em\]\:text-c-1 em,.scalar-app .hljs-attr,.scalar-app .hljs-attribute,.scalar-app .hljs-selector-id,.scalar-app .hljs-template-variable,.scalar-app .hljs-title.class_,.scalar-app .hljs-variable,.scalar-app .text-c-1{color:var(--scalar-color-1)}.code-copy svg[data-v-dc47ca65],.scalar-app .size-3\.25{height:13px;width:13px}.response-card-footer[data-v-dc47ca65]{display:flex;flex-direction:row;flex-shrink:0;gap:8px;justify-content:space-between;padding:7px 12px}.response-example-selector[data-v-dc47ca65]{align-self:flex-start;margin:-4px}.response-description[data-v-dc47ca65]{align-items:center;box-sizing:border-box;color:var(--scalar-color--1);display:flex;font-size:var(--scalar-small);font-weight:var(--scalar-semibold)}.schema-type[data-v-dc47ca65]{background:var(--scalar-background-3);border-radius:4px;margin-right:4px;padding:2px 4px}.schema-example[data-v-dc47ca65],.schema-type[data-v-dc47ca65]{color:var(--scalar-color-2);font-size:var(--scalar-micro);font-weight:var(--scalar-semibold)}.example-response-tab[data-v-dc47ca65]{display:block;margin:6px}.scalar-card-checkbox[data-v-dc47ca65]{align-items:center;color:var(--scalar-color-2);cursor:pointer;display:flex;font-size:var(--scalar-small);font-weight:var(--scalar-font-normal);gap:6px;justify-content:center;min-height:17px;padding:7px 6px;position:relative;-webkit-user-select:none;user-select:none;white-space:nowrap;width:fit-content}.scalar-card-checkbox:has(.scalar-card-checkbox-input:focus-visible) .scalar-card-checkbox-checkmark[data-v-dc47ca65]{outline:1px solid var(--scalar-color-accent)}.scalar-card-checkbox[data-v-dc47ca65]:hover{color:var(--scalar-color--1)}.scalar-card-checkbox .scalar-card-checkbox-input[data-v-dc47ca65]{cursor:pointer;height:0;opacity:0;position:absolute;width:0}.scalar-card-checkbox-checkmark[data-v-dc47ca65]{background-color:initial;background-color:var(--scalar-background-3);border-radius:var(--scalar-radius);box-shadow:inset 0 0 0 var(--scalar-border-width) var(--scalar-border-color);height:16px;width:16px}.scalar-card-checkbox[data-v-dc47ca65]:has(.scalar-card-checkbox-input:checked){color:var(--scalar-color-1);font-weight:var(--scalar-semibold)}.scalar-card-checkbox .scalar-card-checkbox-input:checked~.scalar-card-checkbox-checkmark[data-v-dc47ca65]{background-color:var(--scalar-button-1);box-shadow:none}.scalar-card-checkbox-checkmark[data-v-dc47ca65]:after{content:"";display:none;position:absolute}.scalar-card-checkbox .scalar-card-checkbox-checkmark[data-v-dc47ca65]:after{border-width:1px;border-bottom:1.5px solid var(--scalar-button-1-color);border-left:0 solid var(--scalar-button-1-color);border-right:1.5px solid var(--scalar-button-1-color);border-top:0 solid var(--scalar-button-1-color);height:9px;right:11.5px;top:12.5px;transform:rotate(45deg);width:5px}.show-api-client-button[data-v-342ba62a]{align-items:center;appearance:none;background:var(--scalar-button-1);border:none;border-radius:var(--scalar-radius);box-shadow:inset 0 0 0 1px #0000001a;box-sizing:border-box;color:var(--scalar-background-2);cursor:pointer;display:flex;font-size:var(--scalar-small);font-weight:var(--scalar-semibold);justify-content:center;line-height:22px;outline-offset:2px;padding:1px 6px;white-space:nowrap}.show-api-client-button span[data-v-342ba62a],.show-api-client-button svg[data-v-342ba62a]{color:var(--scalar-button-1-color);fill:currentColor;z-index:1}.show-api-client-button[data-v-342ba62a]:hover{background:var(--scalar-button-1-hover)}.scalar-app .mr-1,.show-api-client-button svg[data-v-342ba62a]{margin-right:4px}.operation-details[data-v-55addca4]{align-items:center;display:flex;flex-shrink:1;gap:9px;margin-top:0;min-width:0}.operation-details[data-v-55addca4] .endpoint-anchor .scalar-button svg{height:16px;width:16px}.endpoint-type[data-v-55addca4]{align-items:center;display:flex;flex-shrink:0;font-size:var(--scalar-small);font-weight:var(--scalar-bold);gap:6px;justify-content:center;padding:6px;text-transform:uppercase;width:60px;z-index:0}.endpoint-type[data-v-55addca4]:after{background:currentColor;border-radius:var(--scalar-radius);content:"";inset:0;opacity:.15;position:absolute;z-index:-1}.endpoint-anchor[data-v-55addca4]{align-items:center;display:flex;flex-shrink:1;min-width:0}.endpoint-anchor.label[data-v-55addca4]{display:flex}.endpoint-label[data-v-55addca4]{align-items:baseline;color:var(--scalar-color-1);display:flex;flex-shrink:1;gap:9px;min-width:0}.endpoint-label-path[data-v-55addca4]{font-family:var(--scalar-font-code);font-size:var(--scalar-mini);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.endpoint-label-name[data-v-55addca4]{color:var(--scalar-color-2);flex-shrink:1000000000;font-size:var(--scalar-small);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.endpoint-try-hint[data-v-55addca4]{flex-shrink:0;padding:2px}.endpoint-copy[data-v-55addca4],.scalar-app .text-current{color:currentColor}.endpoint-copy[data-v-55addca4] svg,.scalar-app .stroke-2{stroke-width:2px}.endpoint-content[data-v-55addca4]{display:grid;gap:9px;grid-auto-columns:1fr;grid-auto-flow:row;padding:9px}.endpoint-content[data-v-55addca4]>*,.scalar-app .min-w-0{min-width:0}.operation-details-card[data-v-55addca4]{display:flex;flex-direction:column;gap:12px;min-width:0}:is(.operation-details-card-item[data-v-55addca4] .parameter-list,.operation-details-card-item[data-v-55addca4] .callbacks-list){border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);margin-top:0}.introduction-card[data-v-5764c94a],.operation-details-card-item[data-v-55addca4]{display:flex;flex-direction:column;gap:12px}.operation-details-card[data-v-55addca4] .parameter-item:last-of-type .parameter-schema,.scalar-app .pb-3{padding-bottom:12px}.operation-details-card[data-v-55addca4] .parameter-item{margin:0;padding:0}.operation-details-card[data-v-55addca4] .property{margin:0;padding:9px}:is(.operation-details-card[data-v-55addca4] .parameter-list-title,.operation-details-card[data-v-55addca4] .request-body-title,.operation-details-card[data-v-55addca4] .callbacks-title){color:var(--scalar-color-2);font-size:var(--scalar-mini);font-weight:var(--scalar-bold);line-height:1.33;margin:0;padding:9px;text-transform:uppercase}.operation-details-card[data-v-55addca4] .callback-list-item-title{padding-left:28px;padding-right:12px}.operation-details-card[data-v-55addca4] .callback-list-item-icon{left:6px}.operation-details-card[data-v-55addca4] .callback-operation-container{padding-inline:9px;padding-bottom:9px}:is(.operation-details-card[data-v-55addca4] .callback-operation-container>.request-body,.operation-details-card[data-v-55addca4] .callback-operation-container>.parameter-list){border:none}.operation-details-card[data-v-55addca4] .callback-operation-container>.request-body>.request-body-header{border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);padding:0 0 9px}.operation-details-card[data-v-55addca4] .request-body-description{border-top:var(--scalar-border-width) solid var(--scalar-border-color);margin-top:0;padding:9px 9px 0}.operation-details-card[data-v-55addca4] .request-body{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);margin-top:0}.operation-details-card[data-v-55addca4] .request-body .schema-card--level-0>.schema-card-description{padding-inline:9px}.operation-details-card[data-v-55addca4] .request-body-header{border-bottom:0;padding-bottom:0}.operation-details-card[data-v-55addca4] .schema-card--open+.schema-card:not(.schema-card--open){margin-inline:9px;margin-bottom:9px}.operation-example-card[data-v-55addca4]{max-height:calc(var(--refs-viewport-height) - 48px);position:sticky;top:calc(var(--refs-viewport-offset) + 24px)}.ask-agent-scalar-input[data-v-f43e32ab]{all:initial;align-items:center;appearance:none;border:none;box-sizing:border-box;color:var(--scalar-color-1);cursor:pointer;display:flex;field-sizing:content;font-family:var(--scalar-font);font-size:var(--scalar-small);font-weight:var(--scalar-regular);justify-content:center;line-height:22px;max-width:88px;outline:0;outline-offset:2px;position:relative;white-space:nowrap}.ask-agent-scalar-input[data-v-f43e32ab]:focus{cursor:text;max-width:calc(100% - 50px);width:100%!important}.ask-agent-scalar-input[data-v-f43e32ab]:not(:placeholder-shown){cursor:text;field-sizing:border-box;height:100%;max-width:calc(100% - 50px);width:100%!important}.ask-agent-scalar-input[data-v-f43e32ab]::placeholder{color:var(--scalar-color-1);font-family:var(--scalar-font)}.ask-agent-scalar-input[data-v-f43e32ab]:focus::placeholder{color:var(--scalar-color-2)}.agent-button-container[data-v-f43e32ab]{align-items:center;background:color-mix(in srgb,var(--scalar-background-3),#fff 15%);border-radius:var(--scalar-radius);color:var(--scalar-color-1);display:flex;gap:4px;height:100%;margin-right:4px;padding:1px 6px;position:relative;z-index:2}.agent-button-container[data-v-f43e32ab]:hover:not(:focus-within){background:color-mix(in srgb,var(--scalar-background-3),#fff 20%)}.agent-button-container[data-v-f43e32ab]:focus-within,.agent-button-container[data-v-f43e32ab]:has(.ask-agent-scalar-input:not(:placeholder-shown)){border-radius:var(--scalar-radius);height:calc(100% - 4px);left:2px;position:absolute;top:2px;width:calc(100% - 4px)}.ask-agent-scalar-send[data-v-f43e32ab]{align-items:center;background:var(--scalar-color-blue);border-radius:var(--scalar-radius);color:#fff;display:none;flex-shrink:0;height:24px;justify-content:center;margin-left:auto;width:24px}.agent-button-container:has(.ask-agent-scalar-input:not(:placeholder-shown)) .ask-agent-scalar-send[data-v-f43e32ab]{display:flex}.section-columns[data-v-8b9602bf]{display:flex;gap:48px}@container narrow-references-container (max-width: 900px){.references-classic-header[data-v-8a3822ca]{padding:12px 24px}.references-classic-header-container[data-v-8a3822ca],.section-container[data-v-20a1472a]{padding:0}.references-classic .section[data-v-be4443e9],.section[data-v-be4443e9]{padding:48px 24px}.section-accordion-chevron[data-v-9419dd23]{left:-16px;top:14px;width:16px}.section-accordion-wrapper[data-v-9419dd23]{padding:calc(var(--refs-viewport-offset)) 24px 0 24px}.show-more[data-v-d1c2b649]{top:-24px}.section-columns[data-v-8b9602bf],.section-content--with-columns[data-v-9735459e]{flex-direction:column;gap:24px}.section-column[data-v-699c28e3]:nth-of-type(2){padding-top:0}}.examples[data-v-3c3c5405]{position:sticky;top:calc(var(--refs-viewport-offset) + 24px)}.examples[data-v-3c3c5405]>*{max-height:calc((var(--refs-viewport-height) - 60px)/ 2);position:relative}.examples[data-v-3c3c5405]>:first-of-type:last-of-type{max-height:calc(var(--refs-viewport-height) - 60px)}.start-section[data-v-673f10b6]:last-of-type,.tag-section[data-v-1124be5d]{margin-bottom:48px}.tag-description[data-v-1124be5d]{padding-bottom:4px;text-align:left}.endpoint[data-v-ad8530a6]{cursor:pointer;display:flex;-webkit-text-decoration:none;text-decoration:none;white-space:nowrap}.download-button span[data-v-59b5011b],.download-link[data-v-59b5011b],.scalar-app .markdown a{--font-visited:var(--scalar-link-color-visited,var(--scalar-color-2));font-weight:var(--scalar-link-font-weight,var(--scalar-semibold));-webkit-text-decoration:var(--scalar-text-decoration);text-decoration:var(--scalar-text-decoration);text-decoration-thickness:1px;text-underline-offset:.25rem}.endpoint:focus-visible .endpoint-path[data-v-ad8530a6],.endpoint:hover .endpoint-path[data-v-ad8530a6]{-webkit-text-decoration:underline;text-decoration:underline}.endpoint .delete[data-v-ad8530a6],.endpoint .get[data-v-ad8530a6],.endpoint .post[data-v-ad8530a6],.endpoint .put[data-v-ad8530a6],.scalar-app .whitespace-nowrap{white-space:nowrap}.endpoint-method[data-v-ad8530a6],.endpoint-path[data-v-ad8530a6]{color:var(--scalar-color-1);cursor:pointer;display:inline-flex;font-family:var(--scalar-font-code);font-size:var(--scalar-small);line-height:1.55;min-width:62px}.endpoint-method[data-v-ad8530a6],.scalar-app .markdown tr>[align=right],.scalar-app .text-right{text-align:right}.endpoint-path[data-v-ad8530a6]{margin-left:12px;text-transform:none}.endpoints-card[data-v-f726f753]{font-size:var(--scalar-font-size-3);position:sticky;top:calc(var(--refs-viewport-offset) + 24px)}.endpoints[data-v-f726f753]{background:var(--scalar-background-2);overflow:auto;padding:10px 12px;width:100%}.section-container[data-v-0d42fbc8]{border-top:var(--scalar-border-width) solid var(--scalar-border-color)}.section-container[data-v-0d42fbc8]:has(.show-more){background-color:color-mix(in srgb,var(--scalar-background-2),#0000)}.section-flare[data-v-2a9c8c02]{pointer-events:none;right:0;top:0}.download-container[data-v-59b5011b]{display:flex;flex-direction:column;gap:16px;margin:0 .5px 8px;position:relative;width:fit-content;z-index:1}.download-container.download-both[data-v-59b5011b]:hover:before,.download-container[data-v-59b5011b]:has(:focus-visible):before{background:var(--scalar-background-1);border-radius:var(--scalar-radius-lg);box-shadow:var(--scalar-shadow-2);content:"";height:90px;left:-12px;pointer-events:none;position:absolute;top:-11px;width:calc(100% + 24px)}.download-button[data-v-59b5011b]{align-items:center;color:var(--scalar-link-color);cursor:pointer;display:flex;gap:4px;height:fit-content;justify-content:center;outline:0;padding:0;position:relative;white-space:nowrap!important}.download-button[data-v-59b5011b]:before{border-radius:var(--scalar-radius);content:"";height:calc(100% + 16px);left:-9px;position:absolute;top:-8px;width:calc(100% + 18px)}.download-button[data-v-59b5011b]:last-of-type:before{width:calc(100% + 15px)}.download-button[data-v-59b5011b]:hover:before{background:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color)}.download-button[data-v-59b5011b]:focus-visible:before{background:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color);outline-style:var(--tw-outline-style);outline-width:1px}.download-button span[data-v-59b5011b]{--font-color:var(--scalar-link-color,var(--scalar-color-accent));color:var(--font-color);text-decoration-color:var(--font-color);align-items:center;display:flex;gap:6px;line-height:1.625;z-index:1}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}.scalar-app .markdown a{text-decoration-color:color-mix(in srgb,var(--font-color)30%,#0000)}.scalar-app .markdown .markdown-alert.markdown-alert-note{border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-blue),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-2),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-orange),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-red),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-success{border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-green),#0000 50%)}.download-button span[data-v-59b5011b]{text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000)}}.download-link[data-v-59b5011b],.download-link[data-v-59b5011b]:hover,.scalar-app .markdown a{--font-color:var(--scalar-link-color,var(--scalar-color-accent));text-decoration-color:var(--font-color)}.download-button:hover span[data-v-59b5011b],.scalar-app .markdown a:hover{color:var(--scalar-link-color-hover,var(--scalar-color-accent));text-decoration-color:var(--scalar-color-1,currentColor);-webkit-text-decoration:var(--scalar-text-decoration-hover);text-decoration:var(--scalar-text-decoration-hover)}.download-button[data-v-59b5011b]:nth-of-type(2){border-width:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.download-container:has(:focus-visible) .download-button[data-v-59b5011b]:nth-of-type(2),.download-container:hover .download-button[data-v-59b5011b]:nth-of-type(2){clip-path:none;height:auto;margin:0;overflow:visible;padding:0;position:absolute;top:42px;white-space:normal;width:auto}.extension[data-v-59b5011b]{background:var(--scalar-link-color,var(--scalar-color-accent));color:var(--scalar-background-1);z-index:1}.download-container:has(:focus-visible) .extension[data-v-59b5011b],.download-container:hover .extension[data-v-59b5011b]{opacity:1}.download-link[data-v-59b5011b],.scalar-app .markdown a{color:var(--font-color)}.introduction-card-row[data-v-5764c94a],.scalar-app .gap-6{gap:24px}@container (max-width: 900px){.endpoint-content[data-v-55addca4]{grid-template-columns:1fr}.introduction-card-row[data-v-5764c94a]{align-items:stretch;flex-direction:column;gap:0}}.introduction-card[data-v-5764c94a] .security-scheme-label{font-weight:var(--scalar-semibold);text-transform:uppercase}.references-classic .introduction-description[data-v-5065bb4a] img,.scalar-app .max-w-\[720px\]{max-width:720px}.sticky-cards[data-v-0b1e2255]{display:flex;flex-direction:column;position:sticky;top:calc(var(--refs-viewport-offset) + 24px)}.introduction-card-item[data-v-dfab866f]{display:flex;flex-direction:column;justify-content:flex-start}.introduction-card-item[data-v-dfab866f]:has(.description) .server-form-container{border-bottom-left-radius:0;border-bottom-right-radius:0}.introduction-card-item[data-v-dfab866f] .request-item{border-bottom:0}.client-libraries-content[data-v-6a49c111]{background-color:var(--scalar-background-1);border-left:var(--scalar-border-width) solid var(--scalar-border-color);border-right:var(--scalar-border-width) solid var(--scalar-border-color);container:client-libraries-content/inline-size;display:flex;justify-content:center;overflow:hidden;padding:0 12px}.client-libraries[data-v-6a49c111]{align-items:center;border-bottom:1px solid #0000;color:var(--scalar-color-3);cursor:pointer;display:flex;gap:6px;justify-content:center;padding:8px 2px;position:relative;-webkit-user-select:none;user-select:none;white-space:nowrap;width:100%}.client-libraries[data-v-6a49c111]:not(.client-libraries__active):hover:before{background:var(--scalar-background-2);border-radius:var(--scalar-radius);content:"";height:calc(100% - 4px);left:2px;position:absolute;top:2px;width:calc(100% - 4px);z-index:0}.client-libraries[data-v-6a49c111]:focus-visible{box-shadow:inset 0 0 0 1px var(--scalar-color-accent);outline:0}.client-libraries-icon[data-v-6a49c111]{align-items:center;aspect-ratio:1;box-sizing:border-box;color:currentColor;display:flex;justify-content:center;max-height:14px;max-width:14px;min-width:14px;position:relative;width:100%}.client-libraries-icon__more svg[data-v-6a49c111],.img_ev3q,.scalar-app .h-auto{height:auto}@container client-libraries-content (width < 400px){.client-libraries__select[data-v-6a49c111]{width:fit-content}.client-libraries__select .client-libraries-icon__more+span[data-v-6a49c111]{display:none}}@container client-libraries-content (width < 380px){.client-libraries[data-v-6a49c111]{width:100%}.client-libraries span[data-v-6a49c111]{display:none}}.client-libraries__active[data-v-6a49c111]{border-bottom:1px solid var(--scalar-color-1);color:var(--scalar-color-1)}.client-libraries .client-libraries-text[data-v-6a49c111]{align-items:center;display:flex;font-size:var(--scalar-small);position:relative}.client-libraries__active .client-libraries-text[data-v-6a49c111]{color:var(--scalar-color-1);font-weight:var(--scalar-semibold)}.selected-client[data-v-dd2e9b07]{background:var(--scalar-background-1);border-top:none;border:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom-left-radius:var(--scalar-radius-xl);border-bottom-right-radius:var(--scalar-radius-xl);color:var(--scalar-color-1);font-family:var(--scalar-font-code);font-size:var(--scalar-small);min-height:fit-content;overflow:hidden;padding:9px 12px;text-overflow:ellipsis;white-space:nowrap}.client-libraries-heading[data-v-dd2e9b07]{align-items:center;background-color:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color);border-top-left-radius:var(--scalar-radius-xl);border-top-right-radius:var(--scalar-radius-xl);color:var(--scalar-color-1);display:flex;font-size:var(--scalar-small);font-weight:var(--scalar-font-medium);max-height:32px;padding:9px 12px}.scalar-app .mt-2,[data-v-dd2e9b07] .scalar-codeblock-pre .hljs{margin-top:8px}.narrow-references-container{container-name:narrow-references-container;container-type:inline-size}.ref-search-meta[data-v-c1c368f9]{background:var(--scalar-background-1);border-bottom-left-radius:var(--scalar-radius-lg);border-bottom-right-radius:var(--scalar-radius-lg);border-top:var(--scalar-border-width) solid var(--scalar-border-color);color:var(--scalar-color-3);display:flex;font-size:var(--scalar-font-size-4);font-weight:var(--scalar-semibold);gap:12px;padding:6px 12px}.agent-scalar[data-v-b45a83b1]{animation:.35s forwards i;background:var(--scalar-background-1);border-right:var(--scalar-border-width) solid var(--scalar-border-color);box-shadow:var(--scalar-shadow-2);height:100dvh;left:0;position:fixed;top:0;transform:translate3d(calc(-100% + var(--scalar-sidebar-width,288px)),0,0);width:calc(100% - 50px);z-index:2}.agent-scalar-container[data-v-b45a83b1]{height:100%;margin-left:auto;overflow:auto;padding:0 24px;width:calc(100% - var(--scalar-sidebar-width,288px))}.scalar-app-exit[data-v-b45a83b1]{cursor:pointer;height:100vh;left:0;position:fixed;top:0;transition:.3s ease-in-out;width:100vw;z-index:2}.scalar-app-exit-animation[data-v-b45a83b1]:before{animation:.5s cubic-bezier(.77,0,.175,1) forwards u;background:#00000038;content:"";height:100%;position:absolute;width:100%}.dark-mode .scalar .scalar-app-exit-animation[data-v-b45a83b1]:before,.dark-mode .scalar .scalar-app-exit[data-v-6118685a]{background:#00000073}@keyframes i{0%{transform:translate3d(calc(-100% + var(--scalar-sidebar-width,288px)),0,0)}to{transform:translateZ(0)}}.app-exit-button[data-v-192fe698],.app-exit-button[data-v-b45a83b1],.app-exit-button[data-v-c8df97c6]{background:#0000001a;color:#fff}.app-exit-button[data-v-192fe698]:hover,.app-exit-button[data-v-b45a83b1]:hover,.app-exit-button[data-v-c8df97c6]:hover{background:#ffffff1a}@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,::backdrop,:after,:before{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-mask-linear:linear-gradient(#fff,#fff);--tw-mask-radial:linear-gradient(#fff,#fff);--tw-mask-conic:linear-gradient(#fff,#fff);--tw-mask-left:linear-gradient(#fff,#fff);--tw-mask-right:linear-gradient(#fff,#fff);--tw-mask-bottom:linear-gradient(#fff,#fff);--tw-mask-top:linear-gradient(#fff,#fff);--tw-mask-top-from-position:0%;--tw-mask-top-to-position:100%;--tw-mask-top-from-color:#000;--tw-mask-top-to-color:#0000;--tw-mask-bottom-from-position:0%;--tw-mask-bottom-to-position:100%;--tw-mask-bottom-from-color:#000;--tw-mask-bottom-to-color:#0000;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-mask-linear:linear-gradient(#fff,#fff);--tw-mask-radial:linear-gradient(#fff,#fff);--tw-mask-conic:linear-gradient(#fff,#fff);--tw-mask-left:linear-gradient(#fff,#fff);--tw-mask-right:linear-gradient(#fff,#fff);--tw-mask-bottom:linear-gradient(#fff,#fff);--tw-mask-top:linear-gradient(#fff,#fff);--tw-mask-top-from-position:0%;--tw-mask-top-to-position:100%;--tw-mask-top-from-color:#000;--tw-mask-top-to-color:#0000;--tw-mask-bottom-from-position:0%;--tw-mask-bottom-to-position:100%;--tw-mask-bottom-from-color:#000;--tw-mask-bottom-to-color:#0000;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-space-x-reverse:0}[data-v-59b5011b],[data-v-59b5011b]::backdrop,[data-v-59b5011b]:after,[data-v-59b5011b]:before{--tw-outline-style:solid}}}@layer scalar-base{body{line-height:inherit;line-height:inherit;background-color:var(--scalar-background-1);margin:0}:host,:root{--leading-snug:1.375;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--leading-relaxed:1.625;--leading-snug:1.375;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--leading-normal:1.5}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}.dark-mode,.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red);--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1) 95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1) 20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1) 20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 20%)}}.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1) 20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1) 20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 20%)}.dark-mode,.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1) 95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));--scalar-sidebar-search-background:var(--scalar-background-2);--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red)}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red);--scalar-sidebar-search-background:var(--scalar-background-2)}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1) 95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1) 20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1) 20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 20%)}}}}:root{--scalar-border-width:.5px;--scalar-radius:3px;--scalar-radius-lg:6px;--scalar-radius-xl:8px;--scalar-font:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;--scalar-font-code:"JetBrains Mono",ui-monospace,Menlo,Monaco,"Cascadia Mono","Segoe UI Mono","Roboto Mono","Oxygen Mono","Ubuntu Monospace","Source Code Pro","Fira Mono","Droid Sans Mono","Courier New",monospace;--scalar-heading-1:24px;--scalar-page-description:16px;--scalar-heading-2:20px;--scalar-heading-3:16px;--scalar-heading-4:16px;--scalar-heading-5:16px;--scalar-heading-6:16px;--scalar-paragraph:16px;--scalar-small:14px;--scalar-mini:13px;--scalar-micro:12px;--scalar-bold:600;--scalar-semibold:500;--scalar-regular:400;--scalar-font-size-1:21px;--scalar-font-size-2:16px;--scalar-font-size-3:14px;--scalar-font-size-4:13px;--scalar-font-size-5:12px;--scalar-font-size-6:12px;--scalar-font-size-7:10px;--scalar-line-height-1:32px;--scalar-line-height-2:24px;--scalar-line-height-3:20px;--scalar-line-height-4:18px;--scalar-line-height-5:16px;--scalar-font-normal:400;--scalar-font-medium:500;--scalar-font-bold:700;--scalar-text-decoration:none;--scalar-link-font-weight:inherit;--scalar-sidebar-indent:20px;--scalar-text-decoration:underline;--scalar-text-decoration-hover:underline}.dark-mode{--csstools-color-scheme--light: ;color-scheme:dark;--scalar-scrollbar-color:#ffffff2e;--scalar-scrollbar-color-active:#ffffff5c;--scalar-shadow-1:0 1px 3px 0 #0000001a;--scalar-shadow-2:0 0 0 .5px var(--scalar-border-color),#0f0f0f33 0px 3px 6px,#0f0f0f66 0px 9px 24px;--scalar-lifted-brightness:1.45;--scalar-backdrop-brightness:.5}.light-mode{--csstools-color-scheme--light:initial;color-scheme:light;--scalar-scrollbar-color-active:#0000005c;--scalar-scrollbar-color:#0000002e;--scalar-shadow-1:0 1px 3px 0 #0000001c;--scalar-shadow-2:#00000014 0px 13px 20px 0px,#00000014 0px 3px 8px 0px,#eeeeed 0px 0 0 .5px;--scalar-lifted-brightness:1;--scalar-backdrop-brightness:1;--scalar-background-1:#fff;--scalar-background-2:#f6f6f6;--scalar-background-3:#e7e7e7;--scalar-background-accent:#8ab4f81f;--scalar-color-1:#1b1b1b;--scalar-color-2:#757575;--scalar-color-3:#8e8e8e;--scalar-color-accent:#09f;--scalar-border-color:#dfdfdf;--scalar-color-green:#069061;--scalar-color-red:#ef0006;--scalar-color-yellow:#edbe20;--scalar-color-blue:#0082d0;--scalar-color-orange:#ff5800;--scalar-color-purple:#5203d1;--scalar-button-1:#000;--scalar-button-1-hover:#000c;--scalar-button-1-color:#ffffffe6;--scalar-tooltip-background:#1a1a1ae6;--scalar-tooltip-color:#ffffffd9;--scalar-color-alert:var(--scalar-color-orange);--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red)}.light-mode .dark-mode{color-scheme:dark!important}.dark-mode,.light-mode{--scalar-text-decoration-color:currentColor;--scalar-text-decoration-color-hover:currentColor;--scalar-link-color:var(--scalar-color-1);--scalar-link-color-hover:var(--scalar-link-color);--scalar-sidebar-background-1:var(--scalar-background-1);--scalar-sidebar-color-1:var(--scalar-color-1);--scalar-sidebar-color-2:var(--scalar-color-2);--scalar-sidebar-border-color:var(--scalar-border-color);--scalar-sidebar-item-hover-background:var(--scalar-background-2);--scalar-sidebar-item-hover-color:var(--scalar-sidebar-color-2);--scalar-sidebar-item-active-background:var(--scalar-background-2);--scalar-sidebar-color-active:var(--scalar-sidebar-color-1);--scalar-sidebar-indent-border:var(--scalar-sidebar-border-color);--scalar-sidebar-indent-border-hover:var(--scalar-sidebar-border-color);--scalar-sidebar-indent-border-active:var(--scalar-sidebar-border-color);--scalar-sidebar-search-background:var(--scalar-background-2);--scalar-sidebar-search-color:var(--scalar-color-3);--scalar-sidebar-search-border-color:var(--scalar-border-color)}.dark-mode{--scalar-background-1:#0f0f0f;--scalar-background-2:#1a1a1a;--scalar-background-3:#272727;--scalar-color-1:#e7e7e7;--scalar-color-2:#a4a4a4;--scalar-color-3:#797979;--scalar-color-accent:#00aeff;--scalar-background-accent:#3ea6ff1f;--scalar-border-color:#2d2d2d;--scalar-color-green:#00b648;--scalar-color-red:#dc1b19;--scalar-color-yellow:#ffc90d;--scalar-color-blue:#4eb3ec;--scalar-color-orange:#ff8d4d;--scalar-color-purple:#b191f9;--scalar-button-1:#fff;--scalar-button-1-hover:#ffffffe6;--scalar-button-1-color:#000;--scalar-tooltip-background:var(--scalar-background-1);--scalar-tooltip-color:#fffffff2;--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red);--scalar-sidebar-search-background:var(--scalar-background-2)}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red);--scalar-sidebar-search-background:var(--scalar-background-2)}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red);--scalar-sidebar-search-background:var(--scalar-background-2)}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1) 95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1) 20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1) 20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 20%)}}}}}@supports (color:color(display-p3 1 1 1)){.light-mode{--scalar-color-accent:#009ced;--scalar-color-green:#008c64;--scalar-color-red:#f9000b;--scalar-color-yellow:#f1bd00;--scalar-color-blue:#0084c2;--scalar-color-orange:#ff702f;--scalar-color-purple:#5a00d8}.dark-mode{--scalar-color-accent:#00adf0;--scalar-color-green:#00b057;--scalar-color-red:#ea0011;--scalar-color-yellow:#ffca2e;--scalar-color-blue:#00b6f0;--scalar-color-orange:#ff925d;--scalar-color-purple:#b790ff}}}@layer scalar-theme;.scalar-app .\@container{container-type:inline-size}.scalar-app .-top-2{top:-8px}.scalar-app .top-\(--refs-header-height\){top:var(--refs-header-height)}.scalar-app .top-\(--scalar-custom-header-height\,0\){top:var(--scalar-custom-header-height,0)}.scalar-app .top-3\.5{top:14px}.scalar-app .top-\[calc\(9px\+0\.5lh\)\]{top:calc(9px + .5lh)}.scalar-app .-left-4\.5{left:-18px}.scalar-app .-left-5{left:-20px}.scalar-app .-left-6{left:-24px}.scalar-app .order-789{order:789}.scalar-app .-m-2{margin:-8px}.scalar-app .-mx-2{margin-inline:-8px}.scalar-app .my-2{margin-block:8px}.scalar-app .my-3{margin-block:12px}.scalar-app .-mt-1{margin-top:-4px}.scalar-app .size-4\.5,.t-editor__callout .callout-content__icon img[data-v-fa58cbab],.t-editor__callout .callout-content__icon svg[data-v-fa58cbab]{height:18px;width:18px}.scalar-app .h-\(--refs-sidebar-height\){height:var(--refs-sidebar-height)}.scalar-app .h-\(--scalar-header-height\){height:var(--scalar-header-height)}.scalar-app .h-\[calc\(100\%\+16px\)\]{height:calc(100% + 16px)}.scalar-app .max-h-\[60vh\]{max-height:60vh}.scalar-app .min-h-3{min-height:12px}.scalar-app .min-h-7{min-height:28px}.scalar-app .min-h-dvh{min-height:100dvh}.scalar-app .w-\(--refs-sidebar-width\){width:var(--refs-sidebar-width)}.scalar-app .w-4\.5{width:18px}.scalar-app .w-96{width:384px}.scalar-app .w-120{width:480px}.scalar-app .max-w-\(--refs-content-max-width\){max-width:var(--refs-content-max-width)}.scalar-app .max-w-64{max-width:256px}.scalar-app .min-w-3{min-width:12px}.scalar-app .min-w-7{min-width:28px}.scalar-app .rotate-45{rotate:45deg}.scalar-app .scroll-mt-16{scroll-margin-top:64px}.scalar-app .scroll-mt-24{scroll-margin-top:96px}.scalar-app .list-none{list-style-type:none}.scalar-app .content-end{align-content:flex-end}.scalar-app .gap-7{gap:28px}.scalar-app .\!rounded-b-xl{border-bottom-left-radius:var(--scalar-radius-xl)!important;border-bottom-right-radius:var(--scalar-radius-xl)!important}.scalar-app .bg-linear-to-l{--tw-gradient-position:to left;background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .from-40\%{--tw-gradient-from-position:40%}.scalar-app .to-transparent{--tw-gradient-to:#0000}.scalar-app .p-7{padding:28px}.scalar-app .px-15{padding-inline:60px}.scalar-app .py-2\.25{padding-block:9px}.scalar-app .pb-12{padding-bottom:48px}.scalar-app .text-base\/5\.25{font-size:var(--scalar-font-size-3);line-height:21px}.scalar-app .leading-\[1\.45\]{--tw-leading:1.45;line-height:1.45}.scalar-app .leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.scalar-app .\[--scalar-address-bar-height\:0px\]{--scalar-address-bar-height:0px}.scalar-app .\[grid-area\:header\]{grid-area:c}.scalar-app .\[grid-area\:navigation\]{grid-area:d}.scalar-app .group-last\:mr-0:is(:where(.group):last-child *){margin-right:0}.scalar-app .group-open\:rotate-90:is(:where(.group):is([open],:popover-open,:open) *){rotate:90deg}.scalar-app .group-open\:flex-wrap:is(:where(.group):is([open],:popover-open,:open) *){flex-wrap:wrap}.scalar-app .group-open\:whitespace-normal:is(:where(.group):is([open],:popover-open,:open) *){white-space:normal}.scalar-app .group-focus-within\/parameter-item\:opacity-100:is(:where(.group\/parameter-item):focus-within *){opacity:1}.scalar-app .group-has-focus-visible\/heading\:opacity-100:is(:where(.group\/heading):has(:focus-visible) *){opacity:1}:is(.scalar-app .\*\:first\:p-3>*):first-child{padding:12px}.scalar-app .focus-visible\:opacity-100:focus-visible{opacity:1}.scalar-app .\[\&_a\]\:underline a,.scalar-app .underline{text-decoration-line:underline}.scalar-app .\[\&_em\]\:not-italic em{font-style:normal}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-mask-linear{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-radial{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-conic{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-left{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-right{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-bottom{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-top{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-top-from-position{syntax:"*";inherits:false;initial-value:0}@property --tw-mask-top-to-position{syntax:"*";inherits:false;initial-value:100%}@property --tw-mask-top-from-color{syntax:"*";inherits:false;initial-value:#000}@property --tw-mask-top-to-color{syntax:"*";inherits:false;initial-value:#0000}@property --tw-mask-bottom-from-position{syntax:"*";inherits:false;initial-value:0}@property --tw-mask-bottom-to-position{syntax:"*";inherits:false;initial-value:100%}@property --tw-mask-bottom-from-color{syntax:"*";inherits:false;initial-value:#000}@property --tw-mask-bottom-to-color{syntax:"*";inherits:false;initial-value:#0000}@property --tw-ease{syntax:"*";inherits:false}@keyframes j{0%,70%{opacity:0}to{opacity:1}}@keyframes k{0%{transform:scale(3.5) rotate(0)}to{transform:scale(3.5) rotate(1turn)}}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@keyframes l{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translate(0)}}.scalar-app .bg-c-accent,.scalar-app .bg-c-accent\/10,.scalar-app .bg-c-accent\/5{background-color:var(--scalar-color-accent)}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@supports (color:color-mix(in lab,red,red)){.download-link[data-v-59b5011b]{text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000)}.scalar-app .bg-b-1\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.scalar-app .markdown a{text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000);-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}.scalar-app .markdown .markdown-alert{background-color:color-mix(in srgb,var(--scalar-background-2),#0000);background-color:var(--scalar-background-2)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:color-mix(in srgb,var(--scalar-color-blue),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-blue),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:color-mix(in srgb,var(--scalar-color-2),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-2),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:color-mix(in srgb,var(--scalar-color-orange),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-orange),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:color-mix(in srgb,var(--scalar-color-red),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-red),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:color-mix(in srgb,var(--scalar-color-green),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-green),#0000 50%)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2));background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2));--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background));border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.scalar-app .to-b-1\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}}.scalar-app .bg-b-1\.5{background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue);border:var(--scalar-border-width) solid var(--scalar-color-blue)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2);border:var(--scalar-border-width) solid var(--scalar-color-2)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange);border:var(--scalar-border-width) solid var(--scalar-color-orange)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red);border:var(--scalar-border-width) solid var(--scalar-color-red)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green);border:var(--scalar-border-width) solid var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.scalar-app .to-b-1\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000);text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000)}.scalar-app .markdown .markdown-alert{background-color:color-mix(in srgb,var(--scalar-background-2),#0000)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:color-mix(in srgb,var(--scalar-color-blue),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-blue),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:color-mix(in srgb,var(--scalar-color-2),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-2),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:color-mix(in srgb,var(--scalar-color-orange),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-orange),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:color-mix(in srgb,var(--scalar-color-red),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-red),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:color-mix(in srgb,var(--scalar-color-green),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-green),#0000 50%)}}}.scalar-app .right-0\.75{right:3px}.scalar-app .ml-2{margin-left:8px}.scalar-app .group-focus-visible\/button\:opacity-0:is(:where(.group\/button):focus-visible *),.scalar-app .group-has-\[\~\*\:focus-within\]\/button\:opacity-0:is(:where(.group\/button):has(~:focus-within) *),.scalar-app .group-has-\[\~\*\:hover\]\/button\:opacity-0:is(:where(.group\/button):has(~:hover) *),.scalar-app .group-has-\[\~\*_\[aria-expanded\=true\]\]\/button\:opacity-0:is(:where(.group\/button):has(~* [aria-expanded=true]) *){opacity:0}.scalar-app .peer-focus-visible\/button\:opacity-100:is(:where(.peer\/button):focus-visible~*){opacity:1}.scalar-app .after\:pointer-events-none:after,.scalar-app .before\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.scalar-app .after\:absolute:after,.scalar-app .before\:absolute:before{content:var(--tw-content);position:absolute}.scalar-app .after\:inset-0:after{content:var(--tw-content);inset:0}.scalar-app .after\:inset-x-0:after{content:var(--tw-content);inset-inline:0}.scalar-app .after\:-top-0\.5:after{content:var(--tw-content);top:-2px}.scalar-app .after\:-bottom-0\.5:after{bottom:-2px;content:var(--tw-content)}.scalar-app .after\:block:after{content:var(--tw-content);display:block}.scalar-app .after\:h-0\.75:after{content:var(--tw-content);height:3px}.scalar-app .after\:rounded:after{border-radius:var(--scalar-radius);content:var(--tw-content)}.scalar-app .after\:bg-blue:after{background-color:var(--scalar-color-blue);content:var(--tw-content)}.scalar-app .after\:opacity-15:after{content:var(--tw-content);opacity:.15}.scalar-app .focus-within\:opacity-100:focus-within{opacity:1}.scalar-app .has-\[\&\[aria-expanded\=true\]\]\:opacity-100:has([aria-expanded=true]){opacity:1}:where(.scalar-app){color:var(--scalar-color-1);font-family:var(--scalar-font);-webkit-text-size-adjust:100%;line-height:1.15;tab-size:4}:where(.scalar-app) *,:where(.scalar-app) :after,:where(.scalar-app) :before{border-color:var(--scalar-border-color);border-style:solid;border-width:0;box-sizing:border-box;color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:inherit;font-style:inherit;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:unset;outline-color:var(--scalar-color-accent);outline-style:none;outline-width:1px;padding:unset;text-align:inherit;-webkit-text-decoration:inherit;text-decoration:inherit;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}:where(.scalar-app) :after,:where(.scalar-app) :before{--tw-content:""}:where(.scalar-app) button,:where(.scalar-app) input,:where(.scalar-app) optgroup,:where(.scalar-app) select,:where(.scalar-app) textarea{background:none}:where(.scalar-app) ::file-selector-button{background:none}:where(.scalar-app) menu,:where(.scalar-app) ol,:where(.scalar-app) ul{list-style:none}:where(.scalar-app) input:where(:not([type=button]):not([type=reset]):not([type=submit])),:where(.scalar-app) select,:where(.scalar-app) textarea{border-radius:var(--scalar-radius);border-width:1px}:where(.scalar-app) input::placeholder{color:var(--scalar-color-3);font-family:var(--scalar-font)}:where(.scalar-app) input[type=search]::-webkit-search-cancel-button{appearance:none}:where(.scalar-app) input[type=search]::-webkit-search-decoration{appearance:none}:where(.scalar-app) summary::-webkit-details-marker{display:none}:where(.scalar-app) input:-webkit-autofill{-webkit-background-clip:text!important;background-clip:text!important}:where(.scalar-app) :focus-visible{border-radius:var(--scalar-radius);outline-style:solid}:where(.scalar-app) [role=button]:focus-visible,:where(.scalar-app) button:focus-visible{outline-offset:-1px}:where(.scalar-app) [role=button],:where(.scalar-app) button{cursor:pointer}:where(.scalar-app) :disabled{cursor:default}:where(.scalar-app) audio,:where(.scalar-app) canvas,:where(.scalar-app) embed,:where(.scalar-app) iframe,:where(.scalar-app) img,:where(.scalar-app) object,:where(.scalar-app) svg,:where(.scalar-app) video{display:block;vertical-align:middle}:where(.scalar-app) [hidden]{display:none}.scalar-app .cm-scroller,.scalar-app .custom-scroll{scrollbar-color:#0000 #0000;scrollbar-width:thin;-webkit-overflow-scrolling:touch;overflow-y:auto}.scalar-app .custom-scroll-self-contain-overflow,.scalar-app .overscroll-contain{overscroll-behavior:contain}.scalar-app .cm-scroller:hover,.scalar-app .custom-scroll:hover,.scalar-app.scalar-scrollbars-obtrusive .cm-scroller,.scalar-app.scalar-scrollbars-obtrusive .custom-scroll{scrollbar-color:var(--scalar-scrollbar-color,#0000) #0000}.scalar-app .cm-scroller:hover::-webkit-scrollbar-thumb,.scalar-app .custom-scroll:hover::-webkit-scrollbar-thumb{background:var(--scalar-scrollbar-color);background-clip:content-box;border:3px solid #0000}.scalar-app .cm-scroller::-webkit-scrollbar-thumb:active,.scalar-app .custom-scroll::-webkit-scrollbar-thumb:active{background:var(--scalar-scrollbar-color-active);background-clip:content-box;border:3px solid #0000}.scalar-app .cm-scroller::-webkit-scrollbar-corner,.scalar-app .custom-scroll::-webkit-scrollbar-corner{background:none}.scalar-app .cm-scroller::-webkit-scrollbar,.scalar-app .custom-scroll::-webkit-scrollbar{height:12px;width:12px}.scalar-app .cm-scroller::-webkit-scrollbar-track,.scalar-app .custom-scroll::-webkit-scrollbar-track{background:none}.scalar-app .cm-scroller::-webkit-scrollbar-thumb,.scalar-app .custom-scroll::-webkit-scrollbar-thumb{background:padding-box content-box;border:3px solid #0000;border-radius:20px}.scalar-app .invisible{visibility:hidden}.scalar-app .-inset-y-0\.5{inset-block:-2px}.scalar-app .-inset-y-0\.75{inset-block:-3px}.scalar-app .inset-y-0{inset-block:0}.scalar-app .inset-y-0\.5{inset-block:2px}.scalar-app .-top-1{top:-4px}.scalar-app .top-\(--nested-items-offset\){top:var(--nested-items-offset)}.scalar-app .top-0\.5{top:2px}.scalar-app .top-1\/2{top:50%}.scalar-app .top-2\.5{top:10px}.scalar-app .top-22{top:88px}.scalar-app .top-\[1lh\]{top:1lh}.scalar-app .top-\[calc\(10px\+0\.5lh\)\]{top:calc(10px + .5lh)}.scalar-app .top-px{top:1px}.scalar-app .-right-1{right:-4px}.scalar-app .-right-1\.5{right:-6px}.scalar-app .right-1\.25{right:5px}.scalar-app .right-2\.5{right:10px}.scalar-app .left-2{left:8px}.scalar-app .left-2\.5{left:10px}.scalar-app .left-4{left:16px}.scalar-app .left-border{left:var(--scalar-border-width)}.scalar-app .left-px{left:1px}.scalar-app .-z-2{z-index:-2}.scalar-app .z-tooltip{z-index:99999}.scalar-app .-m-1{margin:-4px}.scalar-app .-m-1\.5{margin:-6px}.scalar-app .-m-px{margin:-1px}.scalar-app .m-1{margin:4px}.scalar-app .-mx-0\.75{margin-inline:-3px}.scalar-app .-mx-0\.25,.scalar-app .-mx-px{margin-inline:-1px}.scalar-app .mx-px{margin-inline:1px}.scalar-app .-my-1\.5{margin-block:-6px}.scalar-app .-my-2{margin-block:-8px}.scalar-app .my-0\.75{margin-block:3px}.scalar-app .my-1\.5{margin-block:6px}.scalar-app .-mt-1\.5{margin-top:-6px}.scalar-app .mt-\[15svh\]{margin-top:15svh}.scalar-app .mt-\[20svh\]{margin-top:20svh}.scalar-app .-mr-0\.25{margin-right:-1px}.scalar-app .mr-0{margin-right:0}.scalar-app .mr-\[calc\(20px-var\(--scalar-sidebar-indent\)\)\]{margin-right:calc(20px - var(--scalar-sidebar-indent))}.scalar-app .-mb-1{margin-bottom:-4px}.scalar-app .-ml-0\.75{margin-left:-3px}.scalar-app .line-clamp-\(--markdown-clamp\){-webkit-line-clamp:var(--markdown-clamp);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.scalar-app .size-2{height:8px;width:8px}.scalar-app .size-2\.75{height:11px;width:11px}.scalar-app .size-6{height:24px;width:24px}.scalar-app .size-60{height:240px;width:240px}.scalar-app .size-\[23px\]{height:23px;width:23px}.group\/sidebar-section:first-of-type>.group\/spacer-before,.group\/sidebar-section:last-of-type>.group\/spacer-after,.scalar-app .h-0{height:0}.scalar-app .h-1{height:4px}.scalar-app .h-24{height:96px}.scalar-app .h-32{height:128px}.scalar-app .h-border{height:var(--scalar-border-width)}.scalar-app .h-lh{height:1lh}.scalar-app .max-h-20{max-height:80px}.scalar-app .max-h-\[80svh\]{max-height:80svh}.scalar-app .max-h-\[90svh\]{max-height:90svh}.scalar-app .max-h-dvh{max-height:100dvh}.scalar-app .max-h-radix-popper{max-height:calc(var(--radix-popper-available-height) - 8px)}.scalar-app .min-h-96{min-height:384px}.scalar-app .min-h-12,.scalar-app .min-h-header{min-height:48px}.scalar-app .w-12{width:48px}.scalar-app .w-24{width:96px}.scalar-app .w-32{width:128px}.scalar-app .w-40{width:160px}.scalar-app .w-48{width:192px}.scalar-app .w-\[38px\]{width:38px}.scalar-app .w-\[calc\(100vw-12px\)\]{width:calc(100vw - 12px)}.scalar-app .w-\[var\(--scalar-sidebar-indent\)\]{width:var(--scalar-sidebar-indent)}.scalar-app .w-border{width:var(--scalar-border-width)}.scalar-app .w-min{width:min-content}.scalar-app .w-screen{width:100vw}.scalar-app .max-w-\[360px\]{max-width:360px}.scalar-app .max-w-\[480px\]{max-width:480px}.scalar-app .max-w-\[540px\]{max-width:540px}.scalar-app .max-w-\[640px\]{max-width:640px}.scalar-app .max-w-\[800px\]{max-width:800px}.scalar-app .max-w-\[1000px\]{max-width:1000px}.scalar-app .max-w-xs{max-width:320px}.scalar-app .min-w-6{min-width:24px}.scalar-app .min-w-40{min-width:160px}.scalar-app .min-w-min{min-width:min-content}.scalar-app .flex-shrink,.scalar-app .shrink{flex-shrink:1}.scalar-app .-translate-x-full{--tw-translate-x:-100%}.scalar-app .translate-x-2\.5{--tw-translate-x:10px}.scalar-app .translate-x-\[14px\]{--tw-translate-x:14px}.scalar-app .translate-x-full{--tw-translate-x:100%}.scalar-app .centered,.scalar-app .centered-x{--tw-translate-x:-50%;left:50%}.scalar-app .-translate-y-1\.5{--tw-translate-y:-6px}.scalar-app .-translate-y-1\/2{--tw-translate-y:-50%}.scalar-app .translate-y-0{--tw-translate-y:0px}.scalar-app .translate-y-1\.5{--tw-translate-y:6px}.scalar-app .centered,.scalar-app .centered-y{--tw-translate-y:-50%}.scalar-app .scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%}.scalar-app .scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%}.scalar-app .appearance-none{appearance:none}.scalar-app .grid-flow-col{grid-auto-flow:column}.scalar-app .\!items-end{align-items:flex-end!important}.scalar-app .\!items-start{align-items:flex-start!important}.scalar-app .\!justify-end{justify-content:flex-end!important}.scalar-app .\!justify-start{justify-content:flex-start!important}.scalar-app .gap-2\.25{gap:9px}.scalar-app .gap-x-4{column-gap:16px}.scalar-app .gap-y-8{row-gap:32px}:where(.scalar-app .divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(var(--scalar-border-width)*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(var(--scalar-border-width)*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style)}.scalar-app .overflow-x-clip{overflow-x:clip}.scalar-app .rounded-\[inherit\]{border-radius:inherit}.scalar-app .border-1,.scalar-app .border-\[1px\]{border-style:var(--tw-border-style);border-width:1px}.scalar-app .border-c-alert{border-color:var(--scalar-color-alert)}.scalar-app .border-red{border-color:var(--scalar-color-red)}.scalar-app .border-sidebar-border{border-color:var(--scalar-sidebar-border-color,var(--scalar-border-color))}.scalar-app .border-sidebar-border-search{border-color:var(--scalar-sidebar-search-border-color,var(--scalar-border-color))}.scalar-app .bg-\(--bg-light\){background-color:var(--bg-light)}.scalar-app .bg-b-1,.scalar-app .bg-b-1\.5,.scalar-code-block.bg-b-1 .scalar-code-copy-backdrop{background-color:var(--scalar-background-1)}.scalar-app .bg-\(--scalar-background-alert\),.scalar-app .bg-b-alert{background-color:var(--scalar-background-alert)}.scalar-app .active\:bg-b-btn:active,.scalar-app .bg-b-btn{background-color:var(--scalar-button-1)}.scalar-app .bg-b-tooltip{background-color:var(--scalar-tooltip-background)}.scalar-app .bg-backdrop{background-color:#00000038}.scalar-app .bg-border{background-color:var(--scalar-border-color)}.scalar-app .bg-c-danger{background-color:var(--scalar-color-danger)}.scalar-app .bg-inherit{background-color:inherit}.scalar-app .bg-sidebar-b-search{background-color:var(--scalar-sidebar-search-background,var(--scalar-background-2))}.scalar-app .bg-sidebar-indent-border{background-color:var(--scalar-sidebar-indent-border,var(--scalar-border-color))}.scalar-app .bg-sidebar-indent-border-active{background-color:var(--scalar-sidebar-indent-border-active,var(--scalar-color-accent))}.scalar-app .bg-transparent{background-color:#0000}.scalar-app .bg-linear-to-b{--tw-gradient-position:to bottom;background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .from-b-1{--tw-gradient-from:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}.scalar-app .to-b-2{--tw-gradient-to:var(--scalar-background-2)}.scalar-app .mask-y-from-\[calc\(100\%-8px\)\]{--tw-mask-top-from-position:calc(100% - 8px);--tw-mask-bottom-from-position:calc(100% - 8px)}.scalar-app .mask-y-to-100\%{--tw-mask-top-to-position:100%;--tw-mask-bottom-to-position:100%}.scalar-app .mask-repeat{-webkit-mask-repeat:repeat;mask-repeat:repeat}.scalar-app .p-0\.25,.scalar-app .p-px{padding:1px}.scalar-app .p-6{padding:24px}.scalar-app .px-9{padding-inline:36px}.scalar-app .py-4{padding-block:16px}.scalar-app .py-\[6\.75px\]{padding-block:6.75px}.scalar-app .pr-\[100\%\]{padding-right:100%}.scalar-app .pl-8{padding-left:32px}.scalar-app .pl-\[100\%\]{padding-left:100%}.scalar-app .text-base\/4{font-size:var(--scalar-font-size-3);line-height:16px}.scalar-app .text-lg,[data-v-68d5218e] .markdown h2{font-size:var(--scalar-font-size-2);line-height:var(--tw-leading,1.25)}.scalar-app .text-sm\/4{font-size:var(--scalar-font-size-4);line-height:16px}.scalar-app .text-sm\/none{font-size:var(--scalar-font-size-4);line-height:1}.scalar-app .text-xs\/4{font-size:var(--scalar-font-size-5);line-height:16px}.scalar-app .font-sidebar{--tw-font-weight:var(--scalar-sidebar-font-weight,var(--scalar-regular));font-weight:var(--scalar-sidebar-font-weight,var(--scalar-regular))}.scalar-app .font-sidebar-active{--tw-font-weight:var(--scalar-sidebar-font-weight-active,var(--scalar-semibold));font-weight:var(--scalar-sidebar-font-weight-active,var(--scalar-semibold))}.scalar-app .break-words,.scalar-app .wrap-break-word{overflow-wrap:break-word}.scalar-app .text-\(--scalar-color-alert\),.scalar-app .text-c-alert{color:var(--scalar-color-alert)}.scalar-app .text-c-tooltip{color:var(--scalar-tooltip-color)}.scalar-app .text-sidebar-c-1{color:var(--scalar-sidebar-color-1,var(--scalar-color-1))}.scalar-app .text-sidebar-c-search{color:var(--scalar-sidebar-search-color,var(--scalar-color-3))}.scalar-app .opacity-40,.theme-code-block:hover .buttonGroup_M5ko button{opacity:.4}.scalar-app .-outline-offset-2{outline-offset:-2px}.scalar-app .outline-offset-1{outline-offset:1px}.scalar-app .-outline-offset-1,.scalar-app .outline-offset-\[-1px\]{outline-offset:-1px}.scalar-app .ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.scalar-app .ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}:is(.scalar-app .\*\:size-3>*){height:12px;width:12px}:is(.scalar-app .\*\:size-4>*){height:16px;width:16px}:is(.scalar-app .\*\:h-5>*){height:20px}:is(.scalar-app .\*\:min-w-5>*){min-width:20px}:is(.scalar-app .\*\:flex-1>*){flex:1}:is(.scalar-app .\*\:justify-center>*){justify-content:center}:is(.scalar-app .\*\:gap-px>*){gap:1px}:is(.scalar-app .\*\:rounded>*){border-radius:var(--scalar-radius)}:is(.scalar-app .\*\:border>*){border-style:var(--tw-border-style);border-width:var(--scalar-border-width)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}:is(.scalar-app .\*\:px-1>*){padding-inline:4px}:is(.scalar-app .\*\:text-xs>*){font-size:var(--scalar-font-size-5)}.scalar-app .group-focus-visible\/toggle\:outline:is(:where(.group\/toggle):focus-visible *){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .group-hocus\/copy-button\:sr-only:is(:is(:where(.group\/copy-button):hover,:where(.group\/copy-button):focus-visible) *){border-width:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.scalar-app .group-hocus\/copy-button\:not-sr-only:is(:is(:where(.group\/copy-button):hover,:where(.group\/copy-button):focus-visible) *){clip-path:none;height:auto;margin:0;overflow:visible;padding:0;position:static;white-space:normal;width:auto}.scalar-app .group-hocus\/copy-button\:block:is(:is(:where(.group\/copy-button):hover,:where(.group\/copy-button):focus-visible) *){display:block}.scalar-app .group-hocus-within\/code-block\:-left-0\.5:is(:is(:where(.group\/code-block):hover,:where(.group\/code-block):focus-within) *){left:-2px}.scalar-app .group-hocus-within\/code-block\:inline:is(:is(:where(.group\/code-block):hover,:where(.group\/code-block):focus-within) *){display:inline}.scalar-app .group-hocus-within\/code-block\:opacity-100:is(:is(:where(.group\/code-block):hover,:where(.group\/code-block):focus-within) *){opacity:1}.scalar-app .placeholder\:font-\[inherit\]::placeholder{font-family:inherit}.scalar-app .first\:rounded-t-\[inherit\]:first-child,:is(.scalar-app .\*\:first\:rounded-t-\[inherit\]>*):first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.scalar-app .last\:rounded-b-\[inherit\]:last-child,:is(.scalar-app .\*\:last\:rounded-b-\[inherit\]>*):last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.scalar-app .focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}.scalar-app .focus-visible\:border-c-btn:focus-visible{border-color:var(--scalar-button-1-color)}.scalar-app .focus-visible\:outline:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .has-\[\:focus-visible\]\:bg-sidebar-b-1:has(:focus-visible){background-color:var(--scalar-sidebar-background-1,var(--scalar-background-1))}.scalar-app .has-\[\:focus-visible\]\:outline:has(:focus-visible),.scalar-app .has-\[input\:focus-visible\]\:outline:has(:is(input:focus-visible)){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .dark\:bg-\(--bg-dark\):where(.dark-mode,.dark-mode *){background-color:var(--bg-dark)}.scalar-app .dark\:bg-b-3:where(.dark-mode,.dark-mode *){background-color:var(--scalar-background-3)}.scalar-app .dark\:bg-backdrop-dark:where(.dark-mode,.dark-mode *){background-color:#00000073}.scalar-app .dark\:bg-linear-to-t:where(.dark-mode,.dark-mode *){--tw-gradient-position:to top;background-image:linear-gradient(var(--tw-gradient-stops))}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .bg-linear-to-l{--tw-gradient-position:to left in oklab}.scalar-app .bg-linear-to-b{--tw-gradient-position:to bottom in oklab}.scalar-app .dark\:bg-linear-to-t:where(.dark-mode,.dark-mode *){--tw-gradient-position:to top in oklab}}[data-radix-popper-content-wrapper]{z-index:1!important}.loader-wrapper[data-v-27df5cd8]{--loader-size:50%;align-items:center;display:flex;justify-content:center;position:relative}.svg-loader[data-v-27df5cd8]{background-color:#0000;fill:none;height:var(--loader-size);overflow:visible;right:.9rem;stroke:currentColor;top:1rem;width:var(--loader-size)}.svg-path[data-v-27df5cd8]{fill:none;stroke-width:12px;transition:.3s}.svg-x-mark[data-v-27df5cd8]{stroke-dasharray:57;stroke-dashoffset:57px;transition-delay:0s}.svg-check-mark[data-v-27df5cd8]{stroke-dasharray:149;stroke-dashoffset:149px;transition-delay:0s}.icon-is-invalid .svg-x-mark[data-v-27df5cd8],.icon-is-valid .svg-check-mark[data-v-27df5cd8]{stroke-dashoffset:0;transition-delay:.3s}.circular-loader[data-v-27df5cd8]{animation:.7s linear infinite k,.4s j;background:none;transform:scale(3.5);transform-origin:50%}.loader-path[data-v-27df5cd8]{stroke-dasharray:50 200;stroke-dashoffset:-100px;stroke-linecap:round}.loader-path-off[data-v-27df5cd8]{opacity:0;stroke-dasharray:50 200;stroke-dashoffset:-100px;transition:opacity .3s}.scalar-app .bg-b-2,.scalar-app .markdown summary:hover,.scalar-code-block.bg-b-2 .scalar-code-copy-backdrop{background-color:var(--scalar-background-2)}.scalar-app .bg-b-3,.scalar-code-block.bg-b-2 .scalar-code-copy{background-color:var(--scalar-background-3)}.toggle-icon-ellipse[data-v-60be8692]{background:var(--scalar-background-1);border-radius:50%;box-shadow:inset 0 0 0 1px;display:inline-block;height:7px;overflow:hidden;position:relative;transition:width .3s ease-in-out,height .3s ease-in-out;width:7px}.toggle-icon-moon-mask[data-v-60be8692]{background:var(--scalar-background-1);border:1px solid;border-radius:50%;bottom:2.5px;display:block;height:100%;left:2.5px;position:absolute;transform:translate(4px,-4px);transition:transform .3s ease-in-out;width:100%}.toggle-icon-sun-ray[data-v-60be8692]{background:currentColor;border-radius:8px;height:1px;position:absolute;transition:transform .3s ease-in-out;width:12px}.scalar-app .transition,.scalar-app .transition-colors,.scalar-app .transition-opacity,.scalar-app .transition-transform{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.toggle-icon-sun-ray[data-v-60be8692]:nth-of-type(4){transform:rotate(-45deg)}.toggle-icon-dark .toggle-icon-ellipse[data-v-60be8692]{height:10px;-webkit-mask-image:radial-gradient(circle at 0 100%,pink 10px,#0000 12px);mask-image:radial-gradient(circle at 0 100%,pink 10px,#0000 12px);width:10px}.toggle-icon-dark .toggle-icon-sun-ray[data-v-60be8692]{transform:scale(0)}.scalar-icon[data-v-b651bb23],.scalar-icon[data-v-b651bb23] *{stroke-width:var(--c07589c2)}.scalar-app :where(code.hljs) *{font-family:var(--scalar-font-code);font-size:inherit;text-align:left;white-space:pre;word-break:normal;word-spacing:normal;word-wrap:normal;line-height:1.4;tab-size:4}.scalar-app code.hljs{all:unset;color:var(--scalar-color-2);counter-reset:linenumber;font-family:var(--scalar-font-code);font-size:inherit}.scalar-app .hljs{background:none;color:var(--scalar-color-2)}.scalar-app .hljs .line:before{color:var(--scalar-color-3);content:counter(linenumber);counter-increment:linenumber;display:inline-block;margin-right:.875rem;min-width:calc(var(--line-digits)*1ch);text-align:right}.scalar-app .hljs-comment,.scalar-app .hljs-quote{color:var(--scalar-color-3);font-style:italic}.hljs-built_in,.hljs.language-objectivec .hljs-built_in,.scalar-app .hljs-number,.scalar-app .hljs-selector-attr,.scalar-app .hljs-selector-pseudo,.scalar-app .hljs-title.function_,.scalar-app .markdown .markdown-alert.markdown-alert-warning .markdown-alert-icon:before,.scalar-app .text-orange{color:var(--scalar-color-orange)}.scalar-app .hljs-keyword,.scalar-app .markdown .markdown-alert.markdown-alert-important .markdown-alert-icon:before,.scalar-app .text-purple{color:var(--scalar-color-purple)}.scalar-app .hljs-formula{background:var(--scalar-color-1)}.scalar-app .credential .credential-value{color:#0000;font-size:0}.scalar-app .credential:after{color:var(--scalar-color-3);content:"·····";-webkit-user-select:none;user-select:none}.hljs.language-html .hljs-attr,.scalar-app .disabled\:text-c-2:disabled,.scalar-app .markdown .markdown-alert.markdown-alert-tip .markdown-alert-icon:before,.scalar-app .text-c-2{color:var(--scalar-color-2)}.scalar-app .markdown{--markdown-border:var(--scalar-border-width) solid var(--scalar-border-color);--scalar-refs-heading-spacing:24px;--markdown-border:var(--scalar-border-width) solid var(--scalar-border-color);--markdown-spacing-sm:12px;--markdown-spacing-md:16px;--markdown-line-height:1.625;--markdown-heading-line-height:1.15;font-family:var(--scalar-font);line-height:var(--markdown-line-height);word-break:break-word}.scalar-app .markdown>*{margin-bottom:var(--markdown-spacing-md)}.scalar-app .markdown h1,.scalar-app .markdown h2,.scalar-app .markdown h3,.scalar-app .markdown h4,.scalar-app .markdown h5,.scalar-app .markdown h6{display:block;font-weight:var(--scalar-bold);line-height:var(--markdown-heading-line-height,1.15);margin-bottom:var(--markdown-spacing-sm);margin-top:var(--scalar-refs-heading-spacing);scroll-margin-top:1rem}.scalar-app .markdown h2,.scalar-app .markdown h3{font-size:1.25rem}.scalar-app .markdown p{color:inherit;display:block;line-height:var(--markdown-line-height)}.scalar-app .markdown img{border-radius:var(--scalar-radius);display:inline-block;max-width:100%;overflow:hidden}.scalar-app .markdown ol,.scalar-app .markdown ul{display:flex;flex-direction:column;gap:2px;line-height:var(--markdown-line-height);padding-left:1.6em}.scalar-app .markdown li{margin-top:2px;padding-left:7px}.scalar-app ol>li::marker{font:var(--scalar-font);font-feature-settings:"tnum";font-variant-numeric:tabular-nums;font-weight:var(--scalar-semibold);white-space:nowrap}.scalar-app ol>*>li::marker{font:var(--scalar-font);font-feature-settings:"tnum";font-variant-numeric:tabular-nums;font-weight:var(--scalar-semibold);white-space:nowrap}.scalar-app .markdown ol,.scalar-app .markdown ol ol ol ol,.scalar-app .markdown ol ol ol ol ol ol ol{list-style-type:decimal}.scalar-app .markdown ul>*>li,.scalar-app .markdown ul>li{list-style-type:disc}.scalar-app .markdown table{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius);border-spacing:0;display:table;margin:1em 0;overflow-x:auto;position:relative;table-layout:fixed;width:100%}.scalar-app .markdown tbody,.scalar-app .markdown thead{vertical-align:middle}.scalar-app .markdown tbody{display:table-row-group}.scalar-app .markdown thead{display:table-header-group}.scalar-app .markdown tr{border-color:inherit;display:table-row;vertical-align:inherit}.scalar-app .markdown td,.scalar-app .markdown th{border-bottom:var(--markdown-border);border-right:var(--markdown-border);color:var(--scalar-color-1);display:table-cell;font-size:var(--scalar-small);line-height:var(--markdown-line-height);min-width:1em;padding:8.5px 16px;position:relative;vertical-align:top;word-break:break-word}.scalar-app .hidden,.scalar-app .markdown th:empty,.scalar-app .markdown.markdown-summary.markdown-summary hr,.scalar-app .markdown.markdown-summary.markdown-summary img,.scalar-app .markdown.markdown-summary.markdown-summary pre,.scalar-app .markdown.markdown-summary.markdown-summary svg{display:none}.scalar-app .markdown td:last-of-type,.scalar-app .markdown th:last-of-type{border-right:none}.scalar-app .markdown th{background:var(--scalar-background-2);border-left-color:#0000;font-weight:var(--scalar-bold);text-align:left}.scalar-app .markdown th:first-of-type{border-top-left-radius:var(--scalar-radius)}.scalar-app .markdown th:last-of-type{border-top-right-radius:var(--scalar-radius)}.scalar-app .markdown tr>[align=left],.scalar-app .text-left{text-align:left}.scalar-app .markdown tr>[align=center],.scalar-app .text-center{text-align:center}.scalar-app .markdown details{border:var(--markdown-border);border-radius:var(--scalar-radius-xl);color:var(--scalar-color-1)}.scalar-app .markdown details>:not(summary){margin:var(--markdown-spacing-md);margin-bottom:0}.scalar-app .markdown details>p:has(>strong):not(:has(:not(strong))){margin-bottom:8px}.scalar-app .markdown details>p:has(>strong):not(:has(:not(strong)))+*{margin-top:0}.scalar-app .markdown details>table{width:calc(100% - var(--markdown-spacing-md)*2)}.scalar-app .markdown summary{align-items:flex-start;border-radius:2.5px;cursor:pointer;display:flex;font-weight:var(--scalar-semibold);gap:8px;line-height:var(--markdown-line-height);min-height:40px;padding:7px 14px;position:relative;-webkit-user-select:none;user-select:none}.scalar-app .markdown details[open]{padding-bottom:var(--markdown-spacing-md)}.scalar-app .markdown details[open]>summary{border-bottom:var(--markdown-border);border-bottom-left-radius:0;border-bottom-right-radius:0}.scalar-app .markdown summary:before{background-color:var(--scalar-color-3);content:"";display:block;flex-shrink:0;height:var(--markdown-spacing-md);margin-top:5px;-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,');width:var(--markdown-spacing-md)}.scalar-app .markdown summary:hover:before{background-color:var(--scalar-color-1)}.scalar-app .markdown details[open]>summary:before{transform:rotate(90deg);transition:transform .1s ease-in-out}.scalar-app .markdown details:has(+details){border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0;margin-bottom:0}.scalar-app .markdown details:has(+details)+details,.scalar-app .markdown details:has(+details)+details>summary{border-top-left-radius:0;border-top-right-radius:0}.scalar-app .markdown a:visited{color:var(--font-visited)}.scalar-app .markdown sub,.scalar-app .markdown sup{font-size:var(--scalar-micro);font-weight:450}.scalar-app .markdown sup{vertical-align:super}.scalar-app .markdown sub{vertical-align:sub}.scalar-app .markdown code{background-color:var(--scalar-background-2);border-radius:2px;font-family:var(--scalar-font-code);font-size:var(--scalar-micro);padding:0 3px}.scalar-app .markdown .hljs,.scalar-code-block[data-v-8e5d8a14] .hljs *,.scalar-code-block[data-v-94c74c13] .hljs *,[data-v-2891f052] code.hljs *,[data-v-9aa4b63a] .cm-content,[data-v-fd127758] .cm-content{font-size:var(--scalar-small)}.scalar-app .markdown pre code{margin:var(--markdown-spacing-sm) 0;padding:var(--markdown-spacing-sm);white-space:pre;-webkit-overflow-scrolling:touch;display:block;line-height:1.5;max-width:100%;min-width:100px;overflow-x:auto}.scalar-app .markdown hr{border:none;border-bottom:var(--markdown-border)}.scalar-app .markdown blockquote{border-left:1px solid var(--scalar-color-1);display:block;font-size:var(--scalar-font-size-2);font-weight:var(--scalar-bold);margin:0;padding-left:var(--markdown-spacing-md)}.scalar-app .markdown li.task-list-item{list-style:none;position:relative}.scalar-app .markdown li.task-list-item>input{appearance:none;border:1px solid var(--scalar-color-3);border-radius:var(--scalar-radius);display:inline;height:var(--markdown-spacing-md);left:-1.4em;position:absolute;top:.225em;width:var(--markdown-spacing-md)}.scalar-app .markdown li.task-list-item>input[type=checkbox]:checked{background-color:var(--scalar-color-1);border-color:var(--scalar-color-1)}.scalar-app .markdown li.task-list-item>input[type=checkbox]:before{border:solid var(--scalar-background-1);border-width:0 1.5px 1.5px 0;content:"";height:10px;left:5px;opacity:0;position:absolute;top:1px;transform:rotate(45deg);width:5px}.scalar-app .floating-bg:hover:before,.scalar-app .markdown li.task-list-item>input[type=checkbox]:checked:before,.scalar-app .opacity-100{opacity:1}.scalar-app .markdown .markdown-alert{align-items:stretch;background-color:var(--scalar-background-2);border-radius:var(--scalar-radius);border:var(--markdown-border);display:flex;gap:var(--markdown-spacing-sm);padding:10px 14px;position:relative}.scalar-app .markdown .markdown-alert .markdown-alert-icon:before{background-color:currentColor;content:"";display:block;flex-shrink:0;height:18px;margin-top:3px;-webkit-mask-position:50%;mask-position:50%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:18px}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue);border:var(--scalar-border-width) solid var(--scalar-color-blue)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2);border:var(--scalar-border-width) solid var(--scalar-color-2)}.scalar-app .markdown .markdown-alert.markdown-alert-note .markdown-alert-icon:before,.scalar-app .markdown .markdown-alert.markdown-alert-tip .markdown-alert-icon:before{color:var(--scalar-color-blue);-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange);border:var(--scalar-border-width) solid var(--scalar-color-orange)}.scalar-app .markdown .markdown-alert.markdown-alert-important .markdown-alert-icon:before,.scalar-app .markdown .markdown-alert.markdown-alert-warning .markdown-alert-icon:before{-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red);border:var(--scalar-border-width) solid var(--scalar-color-red)}.scalar-app .markdown .markdown-alert.markdown-alert-caution .markdown-alert-icon:before{color:var(--scalar-color-red);-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green);border:var(--scalar-border-width) solid var(--scalar-color-green)}.scalar-app .markdown .markdown-alert.markdown-alert-success .markdown-alert-icon:before{color:var(--scalar-color-green);-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.scalar-app .markdown .markdown-alert .markdown-alert-content{line-height:var(--markdown-line-height);margin:0}.scalar-app .markdown.markdown-summary.markdown-summary :after,.scalar-app .markdown.markdown-summary.markdown-summary :before{content:none}.scalar-app .markdown.markdown-summary.markdown-summary :not(strong):not(em):not(a){display:contents;font-size:inherit;font-weight:inherit;line-height:var(--markdown-line-height)}.dark-mode .scalar-dropdown-item[data-v-3402682d]:hover,.dark-mode .scalar-dropdown-item[data-v-6660bbc5]:hover{filter:brightness(1.1)}.group\/item>*>.scalar-sidebar-indent .scalar-sidebar-indent-border[data-v-3e080c68]{inset-block:-1px}.group\/item:first-child>*>.scalar-sidebar-indent .scalar-sidebar-indent-border[data-v-3e080c68],.scalar-app .top-0{top:0}.group\/item:last-child>*>.scalar-sidebar-indent .scalar-sidebar-indent-border[data-v-3e080c68],.scalar-app .bottom-0{bottom:0}.group\/items.-translate-x-full .group\/button{display:none;max-height:0;transition-behavior:allow-discrete;transition-delay:.3s;transition-duration:0s;transition-property:display,max-height}.group\/item.group\/nested-items-open>*>.group\/items.translate-x-0 .group\/button{display:flex;max-height:3.40282e+38px}.group\/sidebar-section:has(+.group\/sidebar-section)>.group\/spacer-after{height:0;margin-bottom:-1px}:where(body)>.scalar-tooltip{--scalar-tooltip-padding:8px;font-size:var(--scalar-font-size-5);max-width:320px;padding:calc(var(--scalar-tooltip-padding) + var(--scalar-tooltip-offset));z-index:99999;--tw-font-weight:var(--scalar-semibold);color:var(--scalar-tooltip-color);font-weight:var(--scalar-semibold);line-height:16px;overflow-wrap:break-word}:where(body)>.scalar-tooltip:before{backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);background-color:var(--scalar-tooltip-background);border-radius:var(--scalar-radius);content:"";inset:var(--scalar-tooltip-offset);z-index:-1;--tw-backdrop-blur:blur(8px);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);position:absolute}:where(body.dark-mode)>.scalar-tooltip:before{--tw-shadow:inset 0 0 0 var(--tw-shadow-color,var(--scalar-border-width)) var(--scalar-border-color);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-modal-layout[data-v-c36b47da]{animation:.3s ease-in-out forwards u}.scalar-modal[data-v-c36b47da]{animation:.3s ease-in-out .1s forwards l;box-shadow:var(--scalar-shadow-2);transform:translateY(10px)}.scalar-modal-layout-full[data-v-c36b47da]{background:none!important;opacity:1!important}.modal-content-search .modal-body[data-v-c36b47da]{display:flex;flex-direction:column;max-height:440px;overflow:hidden;padding:0}.full-size-styles[data-v-c36b47da]{animation:.3s ease-in-out forwards u;background-color:var(--scalar-background-1)!important;border-radius:0!important;border-right:var(--scalar-border-width) solid var(--scalar-border-color);box-shadow:none!important;left:0;margin:initial;max-height:100%!important;position:absolute!important;top:0!important;transform:translate(0)}.scalar-app .shadow,.scalar-app .shadow-\[-8px_0_4px_var\(--scalar-background-1\)\],.scalar-app .shadow-\[0_-8px_0_8px_var\(--scalar-background-1\)\,0_0_8px_8px_var\(--scalar-background-1\)\],.scalar-app .shadow-border,.scalar-app .shadow-lg,.scalar-app .shadow-none{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.full-size-styles[data-v-c36b47da]:after{content:"";height:100dvh;position:absolute;right:-50dvw;top:0;width:50dvw}.sidebar-heading-type[data-v-1857170e]{align-items:center;color:var(--method-color,var(--scalar-color-1));display:inline-flex;flex-shrink:0;font-family:var(--scalar-font-code);font-size:10px;font-weight:var(--scalar-bold);gap:4px;line-height:14px;overflow:hidden;text-transform:uppercase;white-space:nowrap}.scalar-app .pointer-events-auto{pointer-events:auto}.scalar-app .collapse{visibility:collapse}.scalar-app .visible{visibility:visible}.scalar-app .floating-bg:before{background-color:var(--scalar-background-2);border-radius:var(--scalar-radius);content:"";height:calc(100% - 4px);left:-4px;opacity:0;position:absolute;top:2.5px;transition:opacity .2s ease-in-out;width:calc(100% + 8px);z-index:1}.scalar-app .centered,.scalar-app .centered-y{position:absolute;top:50%}.scalar-app .centered-x{position:absolute}.scalar-app .sr-only{border-width:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.scalar-app .absolute{position:absolute}.scalar-app .fixed{position:fixed}.scalar-app .sticky{position:sticky}.scalar-app .inset-0{inset:0}.scalar-app .inset-x-0{inset-inline:0}.scalar-app .inset-x-1{inset-inline:4px}.scalar-app .inset-x-px{inset-inline:1px}.scalar-app .-top-\(--scalar-address-bar-height\){top:calc(var(--scalar-address-bar-height)*-1)}.scalar-app .-top-1\.25{top:-5px}.scalar-app .-top-\[104px\]{top:-104px}.scalar-app .top-2{top:8px}.scalar-app .top-12{top:48px}.scalar-app .top-\[calc\(100\%\+4px\)\]{top:calc(100% + 4px)}.scalar-app .-right-1\.25{right:-5px}.scalar-app .-right-\[30px\]{right:-30px}.scalar-app .right-0{right:0}.scalar-app .right-1{right:4px}.scalar-app .right-1\.5{right:6px}.scalar-app .right-1\/2{right:50%}.scalar-app .right-2{right:8px}.scalar-app .right-4{right:16px}.scalar-app .right-7{right:28px}.scalar-app .right-14{right:56px}.scalar-app .right-16{right:64px}.scalar-app .bottom-1{bottom:4px}.scalar-app .bottom-1\/2{bottom:50%}.scalar-app .bottom-\[var\(--scalar-border-width\)\]{bottom:var(--scalar-border-width)}.scalar-app .left-0{left:0}.scalar-app .left-1\/2{left:50%}.scalar-app .left-3{left:12px}.scalar-app .-z-1{z-index:-1}.scalar-app .z-0{z-index:0}.scalar-app .z-1,.scalar-app .z-\[1\]{z-index:1}.scalar-app .z-2{z-index:2}.scalar-app .z-10{z-index:10}.scalar-app .z-20{z-index:20}.scalar-app .z-50{z-index:50}.scalar-app .z-\[1002\]{z-index:1002}.scalar-app .z-context{z-index:1000}.scalar-app .z-context-plus{z-index:1001}.scalar-app .z-overlay{z-index:10000}.scalar-app .order-last{order:9999}.scalar-app .col-span-full{grid-column:1/-1}.scalar-app .-m-0\.5{margin:-2px}.scalar-app .m-4{margin:16px}.scalar-app .m-auto{margin:auto}.scalar-app .m-header{margin:48px}.scalar-app .mx-1{margin-inline:4px}.scalar-app .mx-auto{margin-inline:auto}.scalar-app .-my-0\.5{margin-block:-2px}.scalar-app .-my-1{margin-block:-4px}.scalar-app .my-12{margin-block:48px}.scalar-app .-mt-\[\.5px\]{margin-top:-.5px}.scalar-app .mt-0\.25,.scalar-app .mt-px{margin-top:1px}.scalar-app .mt-1{margin-top:4px}.scalar-app .mt-1\.5{margin-top:6px}.scalar-app .mt-3{margin-top:12px}.scalar-app .mt-5{margin-top:20px}.scalar-app .mt-10{margin-top:40px}.scalar-app .mt-\[0\.5px\]{margin-top:.5px}.scalar-app .-mr-0\.5{margin-right:-2px}.scalar-app .-mr-1{margin-right:-4px}.scalar-app .-mr-1\.5{margin-right:-6px}.scalar-app .-mr-3{margin-right:-12px}.scalar-app .mr-0\.5{margin-right:2px}.scalar-app .mr-0\.75{margin-right:3px}.scalar-app .mr-1\.5{margin-right:6px}.scalar-app .mr-1\.25{margin-right:5px}.scalar-app .mr-2{margin-right:8px}.scalar-app .mr-2\.5{margin-right:10px}.scalar-app .mr-3{margin-right:12px}.scalar-app .mr-\[6\.25px\]{margin-right:6.25px}.scalar-app .mr-auto{margin-right:auto}.scalar-app .-mb-\[var\(--scalar-border-width\)\]{margin-bottom:calc(var(--scalar-border-width)*-1)}.scalar-app .mb-2{margin-bottom:8px}.scalar-app .mb-4{margin-bottom:16px}.scalar-app .mb-5{margin-bottom:20px}.scalar-app .mb-\[\.5px\]{margin-bottom:.5px}.scalar-app .-ml-0\.5{margin-left:-2px}.scalar-app .-ml-0\.25{margin-left:-1px}.scalar-app .-ml-1{margin-left:-4px}.scalar-app .-ml-2{margin-left:-8px}.scalar-app .-ml-12{margin-left:-48px}.scalar-app .ml-0\.5{margin-left:2px}.scalar-app .ml-0\.75{margin-left:3px}.scalar-app .ml-1{margin-left:4px}.scalar-app .ml-1\.25{margin-left:5px}.scalar-app .ml-3{margin-left:12px}.scalar-app .ml-auto{margin-left:auto}.scalar-app .box-content{box-sizing:initial}.home-page-features-wrapper .feature .feature-half.assets span.not-demo,.scalar-app .flex-center{align-items:center;display:flex;justify-content:center}.scalar-app .line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.scalar-app .\!block{display:block!important}.scalar-app .\!flex{display:flex!important}.scalar-app .block{display:block}.scalar-app .contents{display:contents}.scalar-app .flex{display:flex}.scalar-app .grid{display:grid}.scalar-app .inline,.tags_jXut{display:inline}.scalar-app .table{display:table}.scalar-app .aspect-\[4\/3\]{aspect-ratio:4/3}.scalar-app .aspect-square{aspect-ratio:1}.scalar-app .size-1\.5{height:6px;width:6px}.scalar-app .size-2\.5{height:10px;width:10px}.scalar-app .size-3{height:12px;width:12px}.scalar-app .size-3\.5{height:14px;width:14px}.scalar-app .size-3\/4{height:75%;width:75%}.scalar-app .size-4{height:16px;width:16px}.scalar-app .size-7{height:28px;width:28px}.scalar-app .size-8{height:32px;width:32px}.scalar-app .size-10{height:40px;width:40px}.scalar-app .size-full{height:100%;width:100%}.scalar-app .h-\(--scalar-address-bar-height\){height:var(--scalar-address-bar-height)}.scalar-app .h-1\.5{height:6px}.scalar-app .h-2\.5{height:10px}.scalar-app .h-2\.25{height:9px}.scalar-app .h-3{height:12px}.scalar-app .h-3\.5{height:14px}.scalar-app .h-4{height:16px}.scalar-app .h-5{height:20px}.scalar-app .h-6{height:24px}.scalar-app .h-7{height:28px}.scalar-app .h-8{height:32px}.scalar-app .h-9{height:36px}.scalar-app .h-10{height:40px}.scalar-app .h-12,.scalar-app .h-header{height:48px}.scalar-app .h-16{height:64px}.scalar-app .h-64{height:256px}.scalar-app .h-\[68px\]{height:68px}.scalar-app .h-\[calc\(100\%-273\.5px\)\]{height:calc(100% - 273.5px)}.scalar-app .h-\[calc\(100\%_-_50px\)\]{height:calc(100% - 50px)}.scalar-app .h-dvh{height:100dvh}.scalar-app .h-fit{height:fit-content}.scalar-app .h-min{height:min-content}.scalar-app .h-px{height:1px}.scalar-app .h-screen{height:100vh}.scalar-app .\!max-h-\[initial\]{max-height:none!important}.scalar-app .max-h-8{max-height:32px}.scalar-app .max-h-40{max-height:160px}.scalar-app .max-h-80{max-height:320px}.scalar-app .max-h-\[40dvh\]{max-height:40dvh}.scalar-app .max-h-\[50dvh\]{max-height:50dvh}.scalar-app .max-h-\[60svh\]{max-height:60svh}.scalar-app .max-h-\[auto\]{max-height:auto}.scalar-app .max-h-\[calc\(100\%-32px\)\]{max-height:calc(100% - 32px)}.scalar-app .max-h-\[inherit\]{max-height:inherit}.scalar-app .max-h-fit{max-height:fit-content}.scalar-app .max-h-screen{max-height:100vh}.scalar-app .min-h-0{min-height:0}.scalar-app .min-h-8{min-height:32px}.scalar-app .min-h-10{min-height:40px}.scalar-app .min-h-11{min-height:44px}.scalar-app .min-h-16,.scalar-app .min-h-\[64px\]{min-height:64px}.scalar-app .min-h-20{min-height:80px}.scalar-app .min-h-\[65px\]{min-height:65px}.scalar-app .min-h-\[calc\(1rem\*4\)\]{min-height:4rem}.scalar-app .min-h-\[calc\(4rem\+0\.5px\)\]{min-height:calc(4rem + .5px)}.scalar-app .min-h-\[calc\(4rem\+1px\)\]{min-height:calc(4rem + 1px)}.scalar-app .min-h-fit,[data-v-5997a667] .cm-content,[data-v-d486ea71] .cm-content{min-height:fit-content}.scalar-app .\!w-fit{width:fit-content!important}.scalar-app .w-0\.5{width:2px}.scalar-app .w-1\.5{width:6px}.scalar-app .w-1\/2{width:50%}.scalar-app .w-2\.5{width:10px}.scalar-app .w-2\.25{width:9px}.scalar-app .w-3{width:12px}.scalar-app .w-3\.5{width:14px}.scalar-app .w-4{width:16px}.scalar-app .w-5{width:20px}.scalar-app .w-6{width:24px}.scalar-app .w-7{width:28px}.scalar-app .w-8{width:32px}.scalar-app .w-10{width:40px}.scalar-app .w-16{width:64px}.scalar-app .w-20{width:80px}.scalar-app .w-56{width:224px}.scalar-app .w-64{width:256px}.scalar-app .w-72{width:288px}.scalar-app .w-\[60px\]{width:60px}.scalar-app .w-\[100px\]{width:100px}.scalar-app .w-\[150px\]{width:150px}.scalar-app .w-\[calc\(100\%-10px\)\]{width:calc(100% - 10px)}.scalar-app .w-\[calc\(100\%_-_8px\)\]{width:calc(100% - 8px)}.scalar-app .w-\[inherit\]{width:inherit}.scalar-app .w-auto{width:auto}.scalar-app .w-dvw{width:100dvw}.scalar-app .w-max{width:max-content}.scalar-app .max-w-8{max-width:32px}.scalar-app .max-w-40{max-width:160px}.scalar-app .max-w-\[14px\]{max-width:14px}.scalar-app .max-w-\[16rem\]{max-width:16rem}.scalar-app .max-w-\[37px\]{max-width:37px}.scalar-app .max-w-\[120px\]{max-width:120px}.scalar-app .max-w-\[150px\]{max-width:150px}.scalar-app .max-w-\[380px\]{max-width:380px}.scalar-app .max-w-\[420px\]{max-width:420px}.scalar-app .max-w-\[calc\(100dvw-24px\)\]{max-width:calc(100dvw - 24px)}.scalar-app .min-w-2\.25{min-width:9px}.scalar-app .min-w-3\.5{min-width:14px}.scalar-app .min-w-4{min-width:16px}.scalar-app .min-w-8{min-width:32px}.scalar-app .min-w-32{min-width:128px}.scalar-app .min-w-48{min-width:192px}.scalar-app .min-w-\[37px\]{min-width:37px}.scalar-app .min-w-\[100px\]{min-width:100px}.scalar-app .min-w-\[150px\]{min-width:150px}.scalar-app .min-w-\[296px\]{min-width:296px}.scalar-app .min-w-fit,[data-v-103d9d56] .cm-lineNumbers .cm-gutterElement,[data-v-776dfdde] .cm-lineNumbers .cm-gutterElement{min-width:fit-content}.scalar-app .min-w-full{min-width:100%}.scalar-app .flex-1{flex:1}.scalar-app .shrink-0{flex-shrink:0}.scalar-app .-translate-x-1\/2{--tw-translate-x:-50%}.scalar-app .translate-x-0{--tw-translate-x:0px}.scalar-app .translate-x-1\/2{--tw-translate-x:50%}.scalar-app .translate-y-1\/2{--tw-translate-y:50%}.scalar-app .scale-75{--tw-scale-x:75%;--tw-scale-y:75%;--tw-scale-z:75%}.scalar-app .rotate-90{rotate:90deg}.scalar-app .rotate-180{rotate:180deg}.scalar-app .transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.scalar-app .cursor-auto{cursor:auto}.scalar-app .cursor-default,.scalar-app .disabled\:cursor-default:disabled{cursor:default}.scalar-app .cursor-grab{cursor:grab}.scalar-app .cursor-help{cursor:help}.scalar-app .cursor-pointer{cursor:pointer}.scalar-app .cursor-text{cursor:text}.scalar-app .resize{resize:both}.scalar-app .resize-none{resize:none}.scalar-app .auto-rows-\[32px\]{grid-auto-rows:32px}.scalar-app .auto-rows-auto{grid-auto-rows:auto}.scalar-app .grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.scalar-app .grid-cols-\[44px_1fr_repeat\(3\,auto\)\]{grid-template-columns:44px 1fr repeat(3,auto)}.scalar-app .grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.scalar-app .grid-cols-\[repeat\(auto-fill\,minmax\(32px\,1fr\)\)\]{grid-template-columns:repeat(auto-fill,minmax(32px,1fr))}.scalar-app .flex-col{flex-direction:column}.scalar-app .flex-row{flex-direction:row}.scalar-app .flex-wrap{flex-wrap:wrap}.scalar-app .content-between{align-content:space-between}.scalar-app .content-start{align-content:flex-start}.scalar-app .justify-between{justify-content:space-between}.mdxPageWrapper_j9I6,.scalar-app .justify-center{justify-content:center}.scalar-app .justify-end{justify-content:flex-end}.scalar-app .justify-start{justify-content:flex-start}.scalar-app .\!gap-2{gap:8px!important}.scalar-app .gap-0\.5{gap:2px}.scalar-app .gap-0\.75{gap:3px}.scalar-app .gap-1{gap:4px}.scalar-app .gap-1\.5{gap:6px}.scalar-app .gap-1\.75{gap:7px}.scalar-app .gap-2{gap:8px}.scalar-app .gap-2\.5{gap:10px}.scalar-app .gap-3{gap:12px}.scalar-app .gap-4{gap:16px}.scalar-app .gap-8{gap:32px}.scalar-app .gap-10{gap:40px}.scalar-app .gap-12{gap:48px}.scalar-app .gap-\[1\.5px\]{gap:1.5px}.scalar-app .gap-px{gap:1px}.scalar-app .gap-x-2\.5{column-gap:10px}:where(.scalar-app .space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(4px*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(4px*var(--tw-space-x-reverse))}:where(.scalar-app .divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(var(--scalar-border-width)*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(var(--scalar-border-width)*var(--tw-divide-y-reverse))}.scalar-app .self-center{align-self:center}.scalar-app .overflow-auto,[data-v-103d9d56] .cm-scroller,[data-v-776dfdde] .cm-scroller{overflow:auto}.scalar-app .overflow-visible{overflow:visible}.scalar-app .overflow-x-auto,.scroll-timeline-x-hidden[data-v-cb2a35da],.scroll-timeline-x-hidden[data-v-f5565642]{overflow-x:auto}.scalar-app .overflow-x-hidden{overflow-x:hidden}.scalar-app .overflow-x-scroll{overflow-x:scroll}.scalar-app .overflow-y-auto{overflow-y:auto}.scalar-app .overflow-y-hidden{overflow-y:hidden}.scalar-app .overflow-y-scroll{overflow-y:scroll}.scalar-app .\!rounded-none{border-radius:0!important}.scalar-app .rounded,.scalar-app .rounded-md{border-radius:var(--scalar-radius)}.scalar-app .rounded-\[10px\]{border-radius:10px}.scalar-app .rounded-full{border-radius:9999px}.scalar-app .rounded-none{border-radius:0}.scalar-app .rounded-px{border-radius:1px}.scalar-app .rounded-xl{border-radius:var(--scalar-radius-xl)}.scalar-app .rounded-t{border-top-left-radius:var(--scalar-radius);border-top-right-radius:var(--scalar-radius)}.scalar-app .rounded-t-lg{border-top-left-radius:var(--scalar-radius-lg);border-top-right-radius:var(--scalar-radius-lg)}.codeBlockTitle_OeMC+.codeBlockContent_QJqH .codeBlock_a8dz,.scalar-app .rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.scalar-app .rounded-t-xl{border-top-left-radius:var(--scalar-radius-xl);border-top-right-radius:var(--scalar-radius-xl)}.scalar-app .rounded-b{border-bottom-left-radius:var(--scalar-radius);border-bottom-right-radius:var(--scalar-radius)}.scalar-app .last\:rounded-b-lg:last-child,.scalar-app .rounded-b-lg{border-bottom-left-radius:var(--scalar-radius-lg);border-bottom-right-radius:var(--scalar-radius-lg)}.scalar-app .rounded-b-none{border-bottom-left-radius:0;border-bottom-right-radius:0}.scalar-app .rounded-b-xl{border-bottom-left-radius:var(--scalar-radius-xl);border-bottom-right-radius:var(--scalar-radius-xl)}.scalar-app .\!border-0{border-style:var(--tw-border-style)!important;border-width:0!important}.scalar-app .border-b,.scalar-app .border-b-0,.scalar-app .border-b-\[1px\]{border-bottom-style:var(--tw-border-style)}.scalar-app .border{border-style:var(--tw-border-style);border-width:var(--scalar-border-width)}.scalar-app .border-0{border-style:var(--tw-border-style);border-width:0}.scalar-app .border-\[1\.5px\]{border-style:var(--tw-border-style);border-width:1.5px}.scalar-app .border-x{border-inline-style:var(--tw-border-style);border-inline-width:var(--scalar-border-width)}.scalar-app .border-x-0{border-inline-style:var(--tw-border-style);border-inline-width:0}.scalar-app .border-y{border-block-style:var(--tw-border-style);border-block-width:var(--scalar-border-width)}.scalar-app .border-t{border-top-style:var(--tw-border-style);border-top-width:var(--scalar-border-width)}.scalar-app .border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.scalar-app .\!border-r{border-right-style:var(--tw-border-style)!important;border-right-width:var(--scalar-border-width)!important}.scalar-app .border-r{border-right-style:var(--tw-border-style);border-right-width:var(--scalar-border-width)}.scalar-app .border-r-0,.scalar-app .last\:border-r-0:last-child{border-right-style:var(--tw-border-style);border-right-width:0}.scalar-app .border-r-1{border-right-style:var(--tw-border-style);border-right-width:1px}.scalar-app .border-b{border-bottom-width:var(--scalar-border-width)}.scalar-app .border-b-0{border-bottom-width:0}.scalar-app .border-b-\[1px\]{border-bottom-width:1px}.scalar-app .border-l{border-left-style:var(--tw-border-style);border-left-width:var(--scalar-border-width)}.scalar-app .border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.scalar-app .border-dashed{--tw-border-style:dashed;border-style:dashed}.scalar-app .border-none{--tw-border-style:none;border-style:none}.scalar-app .border-none\!{--tw-border-style:none!important;border-style:none!important}.scalar-app .\!border-current{border-color:currentColor!important}.scalar-app .border-\(--scalar-background-3\){border-color:var(--scalar-background-3)}.scalar-app .border-\(--scalar-color-alert\){border-color:var(--scalar-color-alert)}.scalar-app .border-c-1{border-color:var(--scalar-color-1)}.scalar-app .border-c-3{border-color:var(--scalar-color-3)}.scalar-app .border-c-accent,.scalar-app .border-c-accent\/30{border-color:var(--scalar-color-accent)}.scalar-app .border-c-danger{border-color:var(--scalar-color-danger)}.scalar-app .border-transparent{border-color:#0000}.scalar-app .border-r-transparent{border-right-color:#0000}.scalar-app .bg-b-danger{background-color:var(--scalar-background-danger)}.scalar-app .bg-c-3\/5,.scalar-app .bg-grey{background-color:var(--scalar-color-3)}.scalar-app .bg-current{background-color:currentColor}.scalar-app .bg-orange{background-color:var(--scalar-color-orange)}.scalar-app .bg-sidebar-b-1{background-color:var(--scalar-sidebar-background-1,var(--scalar-background-1))}.scalar-app .bg-sidebar-b-active{background-color:var(--scalar-sidebar-item-active-background,var(--scalar-background-2))}.scalar-app .fill-current{fill:currentColor}.scalar-app .stroke-\[1\.5\]{stroke-width:1.5px}.scalar-app .stroke-\[1\.75\]{stroke-width:1.75px}.scalar-app .stroke-\[2\.25\]{stroke-width:2.25px}.scalar-app .object-contain{object-fit:contain}.cm-foldGutter .cm-gutterElement .cm-foldMarker,.scalar-app .p-0\.5{padding:2px}.scalar-app .p-0\.75,.scalar-app .p-\[3px\]{padding:3px}.scalar-app .p-1{padding:4px}.scalar-app .p-1\.5{padding:6px}.scalar-app .p-1\.25,.scalar-app .p-\[5px\]{padding:5px}.scalar-app .p-1\.75{padding:7px}.scalar-app .p-3{padding:12px}.scalar-app .p-4{padding:16px}.scalar-app .\!px-3{padding-inline:12px!important}.scalar-app .px-0{padding-inline:0}.scalar-app .px-0\.5{padding-inline:2px}.scalar-app .px-0\.75{padding-inline:3px}.scalar-app .px-1{padding-inline:4px}.scalar-app .px-1\.5{padding-inline:6px}.scalar-app .px-1\.25{padding-inline:5px}.scalar-app .px-2\.5{padding-inline:10px}.scalar-app .px-3{padding-inline:12px}.scalar-app .px-3\.5{padding-inline:14px}.scalar-app .px-4{padding-inline:16px}.scalar-app .px-5{padding-inline:20px}.scalar-app .px-6{padding-inline:24px}.scalar-app .px-8{padding-inline:32px}.scalar-app .\!py-1\.5{padding-block:6px!important}.scalar-app .py-0{padding-block:0}.scalar-app .py-0\.5{padding-block:2px}.scalar-app .py-0\.25,.scalar-app .py-px{padding-block:1px}.scalar-app .py-0\.75{padding-block:3px}.scalar-app .py-1{padding-block:4px}.scalar-app .py-1\.5{padding-block:6px}.scalar-app .py-1\.25{padding-block:5px}.scalar-app .py-1\.75{padding-block:7px}.scalar-app .py-2{padding-block:8px}.scalar-app .py-2\.5{padding-block:10px}.scalar-app .py-3{padding-block:12px}.scalar-app .py-5{padding-block:20px}.scalar-app .py-8{padding-block:32px}.scalar-app .pt-0{padding-top:0}.scalar-app .pt-2{padding-top:8px}.scalar-app .pt-3{padding-top:12px}.scalar-app .pt-4{padding-top:16px}.scalar-app .pt-6,.swagger-editor .start[data-v-673f10b6]{padding-top:24px}.scalar-app .pt-8{padding-top:32px}.scalar-app .pt-px{padding-top:1px}.scalar-app .pr-0\.75{padding-right:3px}.scalar-app .pr-1{padding-right:4px}.scalar-app .pr-1\.5{padding-right:6px}.scalar-app .pr-2{padding-right:8px}.scalar-app .pr-2\.5{padding-right:10px}.scalar-app .pr-2\.25{padding-right:9px}.scalar-app .pr-3{padding-right:12px}.scalar-app .pr-6{padding-right:24px}.scalar-app .pr-8{padding-right:32px}.scalar-app .pr-9{padding-right:36px}.scalar-app .pr-10{padding-right:40px}.scalar-app .pr-12{padding-right:48px}.scalar-app .pr-\[26px\]{padding-right:26px}.scalar-app .pb-1\.5{padding-bottom:6px}.scalar-app .pb-2{padding-bottom:8px}.scalar-app .pb-5{padding-bottom:20px}.scalar-app .pb-6{padding-bottom:24px}.scalar-app .pb-8{padding-bottom:32px}.scalar-app .pb-14{padding-bottom:56px}.scalar-app .pb-\[75px\]{padding-bottom:75px}.scalar-app .\!pl-3{padding-left:12px!important}.scalar-app .pl-1{padding-left:4px}.scalar-app .pl-1\.5{padding-left:6px}.scalar-app .pl-1\.25{padding-left:5px}.scalar-app .pl-2{padding-left:8px}.scalar-app .pl-3{padding-left:12px}.scalar-app .pl-5{padding-left:20px}.scalar-app .pl-6{padding-left:24px}.scalar-app .pl-8\.5{padding-left:34px}.scalar-app .pl-9{padding-left:36px}.scalar-app .pl-12{padding-left:48px}.scalar-app .pl-px{padding-left:1px}.scalar-app .font-sans{font-family:var(--scalar-font)}.scalar-app .text-base{font-size:var(--scalar-font-size-3);line-height:var(--tw-leading,1.14286)}.scalar-app .text-base\/5{font-size:var(--scalar-font-size-3);line-height:20px}.scalar-app .text-xl{font-size:var(--scalar-font-size-1);line-height:var(--tw-leading,1.14286)}.scalar-app .text-3xs{font-size:var(--scalar-font-size-7)}.scalar-app .text-\[6px\]{font-size:6px}.scalar-app .text-\[9px\]{font-size:9px}.scalar-app .text-\[11px\]{font-size:11px}.scalar-app .text-\[21px\]{font-size:21px}.scalar-app .text-sm{font-size:var(--scalar-font-size-4)}.scalar-app .text-xs{font-size:var(--scalar-font-size-5)}.scalar-app .text-xxs{font-size:var(--scalar-font-size-6)}.scalar-app .\!leading-\[6px\]{--tw-leading:6px!important;line-height:6px!important}.scalar-app .leading-4{--tw-leading:16px;line-height:16px}.scalar-app .leading-5,.scalar-app .leading-\[20px\]{--tw-leading:20px;line-height:20px}.scalar-app .leading-5\.5,.scalar-app .leading-\[22px\]{--tw-leading:22px;line-height:22px}.scalar-app .leading-6{--tw-leading:24px;line-height:24px}.scalar-app .leading-\[1\.44\]{--tw-leading:1.44;line-height:1.44}.scalar-app .leading-\[7px\]{--tw-leading:7px;line-height:7px}.scalar-app .leading-\[21px\]{--tw-leading:21px;line-height:21px}.scalar-app .leading-\[normal\]{--tw-leading:normal;line-height:normal}.scalar-app .leading-none{--tw-leading:1;line-height:1}.scalar-app .leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.scalar-app .leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.scalar-app .font-bold{--tw-font-weight:var(--scalar-bold);font-weight:var(--scalar-bold)}.scalar-app .font-medium{--tw-font-weight:var(--scalar-semibold);font-weight:var(--scalar-semibold)}.scalar-app .font-normal{--tw-font-weight:var(--scalar-regular);font-weight:var(--scalar-regular)}.scalar-app .text-balance{text-wrap:balance}.scalar-app .text-nowrap{text-wrap:nowrap}.scalar-app .text-pretty{text-wrap:pretty}.scalar-app .break-all{word-break:break-all}.scalar-app .text-ellipsis{text-overflow:ellipsis}.scalar-app .whitespace-pre{white-space:pre}.scalar-app .whitespace-pre-wrap{white-space:pre-wrap}.scalar-app .\!text-c-1{color:var(--scalar-color-1)!important}.scalar-app .text-b-1{color:var(--scalar-background-1)}.scalar-app .text-b-2{color:var(--scalar-background-2)}.scalar-app .text-border{color:var(--scalar-border-color)}.scalar-app .text-c-accent{color:var(--scalar-color-accent)}.scalar-app .text-c-btn{color:var(--scalar-button-1-color)}.scalar-app .text-c-danger{color:var(--scalar-color-danger)}.scalar-app .text-sidebar-c-2{color:var(--scalar-sidebar-color-2,var(--scalar-color-2))}.scalar-app .text-sidebar-c-active{color:var(--scalar-sidebar-color-active,var(--scalar-sidebar-color-1))}.scalar-app .text-transparent{color:#0000}.scalar-app .text-white{color:#fff}.scalar-app .text-yellow{color:var(--scalar-color-yellow)}.scalar-app .line-through{text-decoration-line:line-through}.scalar-app .no-underline{text-decoration-line:none}.scalar-app .decoration-c-3{text-decoration-color:var(--scalar-color-3)}.scalar-app .underline-offset-2{text-underline-offset:2px}.scalar-app .opacity-0{opacity:0}.scalar-app .opacity-50{opacity:.5}.scalar-app .bg-blend-normal{background-blend-mode:normal}.scalar-app .mix-blend-luminosity{mix-blend-mode:luminosity}.scalar-app .shadow{--tw-shadow:var(--scalar-shadow-1)}.scalar-app .shadow-\[-8px_0_4px_var\(--scalar-background-1\)\]{--tw-shadow:-8px 0 4px var(--tw-shadow-color,var(--scalar-background-1))}.scalar-app .shadow-\[0_-8px_0_8px_var\(--scalar-background-1\)\,0_0_8px_8px_var\(--scalar-background-1\)\]{--tw-shadow:0 -8px 0 8px var(--tw-shadow-color,var(--scalar-background-1)),0 0 8px 8px var(--tw-shadow-color,var(--scalar-background-1))}.scalar-app .shadow-border{--tw-shadow:inset 0 0 0 var(--tw-shadow-color,var(--scalar-border-width)) var(--scalar-border-color)}.scalar-app .shadow-lg{--tw-shadow:var(--scalar-shadow-2)}.scalar-app .shadow-none{--tw-shadow:0 0 #0000}.scalar-app .outline{outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .outline-offset-2{outline-offset:2px}.scalar-app .outline-b-3{outline-color:var(--scalar-background-3)}.scalar-app .outline-c-danger{outline-color:var(--scalar-color-danger)}.scalar-app .blur{--tw-blur:blur(8px)}.scalar-app .blur,.scalar-app .brightness-90,.scalar-app .brightness-\[\.9\],.scalar-app .brightness-lifted,.scalar-app .filter{filter:var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, )}.scalar-app .brightness-90{--tw-brightness:brightness(90%)}.scalar-app .brightness-\[\.9\]{--tw-brightness:brightness(.9)}.scalar-app .brightness-lifted{--tw-brightness:brightness(var(--scalar-lifted-brightness))}.scalar-app .transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,backdrop-filter,display,content-visibility,overlay,pointer-events}.scalar-app .transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to}.scalar-app .transition-opacity{transition-property:opacity}.scalar-app .transition-transform{transition-property:transform,translate,scale,rotate}.scalar-app .transition-none{transition-property:none}.scalar-app .duration-100{--tw-duration:.1s;transition-duration:.1s}.scalar-app .duration-150{--tw-duration:.15s;transition-duration:.15s}.scalar-app .duration-200{--tw-duration:.2s;transition-duration:.2s}.scalar-app .duration-300{--tw-duration:.3s;transition-duration:.3s}.scalar-app .ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.scalar-app .focus\:outline-none:focus,.scalar-app .outline-none{--tw-outline-style:none;outline-style:none}.scalar-app .select-none{-webkit-user-select:none;user-select:none}.scalar-app .\[--scalar-address-bar-height\:32px\]{--scalar-address-bar-height:32px}.scalar-app .app-drag-region{-webkit-app-region:drag}.scalar-app .app-no-drag-region{-webkit-app-region:no-drag}:is(.scalar-app .\*\:flex>*){display:flex}:is(.scalar-app .\*\:h-8>*){height:32px}:is(.scalar-app .\*\:cursor-pointer>*){cursor:pointer}:is(.scalar-app .\*\:items-center>*){align-items:center}:is(.scalar-app .\*\:rounded-none>*){border-radius:0}:is(.scalar-app .\*\:border-t>*){border-top-style:var(--tw-border-style);border-top-width:var(--scalar-border-width)}:is(.scalar-app .\*\:border-b-0>*){border-bottom-style:var(--tw-border-style);border-bottom-width:0}:is(.scalar-app .\*\:px-1\.5>*){padding-inline:6px}:is(.scalar-app .\*\:pl-4>*){padding-left:16px}.scalar-app .group-first\/row\:border-t-0:is(:where(.group\/row):first-child *){border-top-style:var(--tw-border-style);border-top-width:0}.scalar-app .group-last\:border-b-transparent:is(:where(.group):last-child *){border-bottom-color:#0000}.scalar-app .group-last\/label\:rounded-br-lg:is(:where(.group\/label):last-child *){border-bottom-right-radius:var(--scalar-radius-lg)}.scalar-app .group-focus-within\:flex:is(:where(.group):focus-within *){display:flex}.scalar-app .group-focus-visible\:opacity-100:is(:where(.group):focus-visible *){opacity:1}.scalar-app .group-focus-visible\:outline:is(:where(.group):focus-visible *){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .group-has-focus-visible\:hidden:is(:where(.group):has(:focus-visible) *){display:none}.scalar-app .group-has-\[\.cm-focused\]\:z-1:is(:where(.group):has(.cm-focused) *){z-index:1}.scalar-app .group-has-\[\.cm-focused\]\:flex:is(:where(.group):has(.cm-focused) *){display:flex}.scalar-app .group-has-\[\.cm-focused\]\:pr-6:is(:where(.group):has(.cm-focused) *){padding-right:24px}.scalar-app .group-has-\[\.cm-focused\]\:pr-10:is(:where(.group):has(.cm-focused) *){padding-right:40px}.scalar-app .group-has-\[\:focus-visible\]\:hidden:is(:where(.group):has(:focus-visible) *){display:none}.scalar-app .group-has-\[\:focus-visible\]\:opacity-100:is(:where(.group):has(:focus-visible) *){opacity:1}.scalar-app .group-has-\[\:focus-visible\]\/cell\:border-c-accent:is(:where(.group\/cell):has(:focus-visible) *){border-color:var(--scalar-color-accent)}.scalar-app .group-has-\[\:focus-visible\]\/cell\:opacity-100:is(:where(.group\/cell):has(:focus-visible) *){opacity:1}.scalar-app .group-has-\[\:focus-visible\]\/input\:block:is(:where(.group\/input):has(:focus-visible) *){display:block}.scalar-app .group-has-\[input\]\/label\:mr-0:is(:where(.group\/label):has(:is(input)) *){margin-right:0}.scalar-app .group-aria-expanded\/button\:rotate-180:is(:where(.group\/button)[aria-expanded=true] *),.scalar-app .group-aria-expanded\/combobox-button\:rotate-180:is(:where(.group\/combobox-button)[aria-expanded=true] *){rotate:180deg}.scalar-app .group-\[\.alert\]\:bg-b-alert:is(:where(.group).alert *){background-color:var(--scalar-background-alert)}.scalar-app .group-\[\.alert\]\:bg-transparent:is(:where(.group).alert *){background-color:#0000}.scalar-app .group-\[\.alert\]\:shadow-none:is(:where(.group).alert *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .group-\[\.alert\]\:outline-orange:is(:where(.group).alert *){outline-color:var(--scalar-color-orange)}.scalar-app .group-\[\.error\]\:bg-b-danger:is(:where(.group).error *){background-color:var(--scalar-background-danger)}.scalar-app .group-\[\.error\]\:bg-transparent:is(:where(.group).error *){background-color:#0000}.scalar-app .group-\[\.error\]\:text-red:is(:where(.group).error *){color:var(--scalar-color-red)}.scalar-app .group-\[\.error\]\:shadow-none:is(:where(.group).error *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .group-\[\.error\]\:outline-red:is(:where(.group).error *){outline-color:var(--scalar-color-red)}.scalar-app .peer-checked\:text-c-1:is(:where(.peer):checked~*){color:var(--scalar-color-1)}.scalar-app .peer-has-\[\.cm-focused\]\:opacity-0:is(:where(.peer):has(.cm-focused)~*){opacity:0}.scalar-app .peer-has-\[\.color-selector\]\:hidden:is(:where(.peer):has(.color-selector)~*){display:none}.scalar-app .before\:top-0:before{content:var(--tw-content);top:0}.scalar-app .before\:left-3:before{content:var(--tw-content);left:12px}.scalar-app .before\:left-\[calc\(\.75rem_\+_\.5px\)\]:before{content:var(--tw-content);left:calc(.75rem + .5px)}.scalar-app .before\:z-1:before{content:var(--tw-content);z-index:1}.scalar-app .before\:h-\[calc\(100\%_\+_\.5px\)\]:before{content:var(--tw-content);height:calc(100% + .5px)}.scalar-app .before\:w-\[\.5px\]:before{content:var(--tw-content);width:.5px}.scalar-app .before\:bg-border:before{background-color:var(--scalar-border-color);content:var(--tw-content)}.scalar-app .after\:content-\[\'\:\'\]:after{--tw-content:":";content:var(--tw-content)}:is(.scalar-app .\*\:first\:line-clamp-1>*):first-child{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.scalar-app .\*\:first\:rounded-l>*):first-child{border-bottom-left-radius:var(--scalar-radius);border-top-left-radius:var(--scalar-radius)}:is(.scalar-app .\*\:first\:border-t-0>*):first-child,:is(.scalar-app .first\:\*\:border-t-0:first-child>*){border-top-style:var(--tw-border-style);border-top-width:0}:is(.scalar-app .\*\:first\:text-ellipsis>*):first-child{text-overflow:ellipsis}:is(.scalar-app .\*\:last\:rounded-r>*):last-child{border-bottom-right-radius:var(--scalar-radius);border-top-right-radius:var(--scalar-radius)}.scalar-app .last\:before\:h-full:last-child:before{content:var(--tw-content);height:100%}.scalar-app .last-of-type\:first-of-type\:border-b-0:last-of-type:first-of-type{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.scalar-app .focus-within\:z-20:focus-within{z-index:20}.scalar-app .focus-within\:border-\(--scalar-background-3\):focus-within{border-color:var(--scalar-background-3)}.scalar-app .focus-within\:bg-b-1:focus-within{background-color:var(--scalar-background-1)}.scalar-app .focus-within\:text-c-1:focus-within{color:var(--scalar-color-1)}.scalar-app .focus\:border-b-1:focus{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--scalar-background-1)}.cm-foldPlaceholder:hover,.nav-item[data-v-2e741aab]:hover,.nav-item[data-v-507381a3]:hover,.scalar-app .active\:text-c-1:active,.scalar-app .focus\:text-c-1:focus{color:var(--scalar-color-1)}.scalar-app .focus-visible\:z-10:focus-visible{z-index:10}.scalar-app .has-focus-visible\:bg-b-1:has(:focus-visible){background-color:var(--scalar-background-1)}.scalar-app .has-focus-visible\:outline:has(:focus-visible){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .has-\[\.empty-sidebar-item\]\:border-t:has(.empty-sidebar-item){border-top-style:var(--tw-border-style);border-top-width:var(--scalar-border-width)}.scalar-app .has-\[\:focus-visible\]\:absolute:has(:focus-visible){position:absolute}.scalar-app .has-\[\:focus-visible\]\:z-1:has(:focus-visible){z-index:1}.scalar-app .has-\[\:focus-visible\]\:rounded-\[4px\]:has(:focus-visible){border-radius:4px}.scalar-app .has-\[\:focus-visible\]\:bg-b-1:has(:focus-visible){background-color:var(--scalar-background-1)}.scalar-app .has-\[\:focus-visible\]\:opacity-100:has(:focus-visible){opacity:1}.scalar-app .has-\[\:focus-visible\]\:outline:has(:focus-visible){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .dark\:bg-b-2:where(.dark-mode,.dark-mode *){background-color:var(--scalar-background-2)}.scalar-app .ui-open\:rotate-90[data-headlessui-state~=open],:where([data-headlessui-state~=open]) :is(.scalar-app .ui-open\:rotate-90){rotate:90deg}.scalar-app .ui-open\:rotate-180[data-headlessui-state~=open],:where([data-headlessui-state~=open]) :is(.scalar-app .ui-open\:rotate-180){rotate:180deg}.scalar-app .last\:ui-open\:border-b-0:last-child[data-headlessui-state~=open],:where([data-headlessui-state~=open]) .scalar-app .last\:ui-open\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.scalar-app .ui-not-open\:hidden[data-headlessui-state]:not([data-headlessui-state~=open]),:where([data-headlessui-state]:not([data-headlessui-state~=open])) :is(.scalar-app .ui-not-open\:hidden):not([data-headlessui-state]){display:none}.scalar-app .ui-not-open\:rotate-0[data-headlessui-state]:not([data-headlessui-state~=open]),:where([data-headlessui-state]:not([data-headlessui-state~=open])) :is(.scalar-app .ui-not-open\:rotate-0):not([data-headlessui-state]){rotate:none}.scalar-app .ui-checked\:bg-b-3[data-headlessui-state~=checked],:where([data-headlessui-state~=checked]) :is(.scalar-app .ui-checked\:bg-b-3){background-color:var(--scalar-background-3)}.scalar-app .ui-active\:bg-b-2[data-headlessui-state~=active],:is(.scalar-app .ui-active\:\*\:bg-b-2[data-headlessui-state~=active]>*),:is(:where([data-headlessui-state~=active]) :is(.scalar-app .ui-active\:\*\:bg-b-2)>*),:where([data-headlessui-state~=active]) :is(.scalar-app .ui-active\:bg-b-2){background-color:var(--scalar-background-2)}.app-platform-mac :is(.scalar-app .mac\:pt-10){padding-top:40px}.app-platform-mac :is(.scalar-app .mac\:pl-\[72px\]){padding-left:72px}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}.nav-item[data-v-507381a3]{align-items:center;background:var(--scalar-background-3);border:var(--scalar-border-width) solid var(--scalar-background-2);border-radius:var(--scalar-radius-lg);color:var(--scalar-color-3);cursor:pointer;display:flex;flex:1;justify-content:center;min-width:0;overflow:hidden;padding:4.5px;position:relative}.nav-item-icon-copy[data-v-2e741aab],.nav-item-icon-copy[data-v-507381a3]{-webkit-mask-image:linear-gradient(to left,#0000 0,var(--scalar-background-2) 20px);mask-image:linear-gradient(to left,#0000 0,var(--scalar-background-2) 20px);max-width:100%;overflow:hidden;white-space:nowrap}.nav-item:hover .nav-item-icon-copy[data-v-2e741aab],.nav-item:hover .nav-item-icon-copy[data-v-507381a3]{-webkit-mask-image:linear-gradient(to left,#0000 20px,var(--scalar-background-2) 40px);mask-image:linear-gradient(to left,#0000 20px,var(--scalar-background-2) 40px)}.nav-item-copy[data-v-2e741aab],.nav-item-copy[data-v-507381a3]{max-width:calc(100% - 20px)}.nav-item__active[data-v-2e741aab],.nav-item__active[data-v-507381a3]{background-color:var(--scalar-background-1);border-color:var(--scalar-border-color);color:var(--scalar-color-1)}.dark-mode .nav-item__active[data-v-507381a3],.nav-item__active .nav-item-close[data-v-507381a3]:hover{background-color:var(--scalar-background-2)}.nav-item-close[data-v-2e741aab],.nav-item-close[data-v-507381a3]{background:none;border-radius:var(--scalar-radius);color:var(--scalar-color-3);margin-left:-20px;max-width:20px;opacity:0;padding:2px;position:absolute;right:3px;stroke-width:1.5px}.nav-item:hover .nav-item-close[data-v-507381a3],.scalar .scalar-app-exit[data-v-6118685a]:hover:before{opacity:1}.nav-item-close[data-v-2e741aab]:hover,.nav-item-close[data-v-507381a3]:hover{background-color:var(--scalar-background-4)}.download-app-button[data-v-9b609275],.download-app-button[data-v-cb45fa05]{background:linear-gradient(#ffffffbf,#00000009);box-shadow:0 0 0 .5px var(--scalar-border-color)}.dark-mode .download-app-button[data-v-9b609275],.dark-mode .download-app-button[data-v-cb45fa05]{background:linear-gradient(#ffffff1a,#00000026)}.download-app-button[data-v-9b609275]:hover,.download-app-button[data-v-cb45fa05]:hover,.http-bg-gradient[data-v-076b14a1]:hover{background:linear-gradient(#00000009,#ffffffbf)}.dark-mode .download-app-button[data-v-9b609275]:hover,.dark-mode .download-app-button[data-v-cb45fa05]:hover{background:linear-gradient(#00000026,#ffffff1a)}.http-bg-gradient[data-v-076b14a1]{background:linear-gradient(#ffffffbf,#00000009)}.dark-mode .http-bg-gradient[data-v-076b14a1]{background:linear-gradient(#ffffff09,#00000026)}.dark-mode .http-bg-gradient[data-v-076b14a1]:hover{background:linear-gradient(#00000026,#ffffff09)}.scroll-timeline-x[data-v-e0578855],.scroll-timeline-x[data-v-f4568236]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;-ms-overflow-style:none;overflow:auto;scrollbar-width:none}.commandmenu[data-v-2fe57517],.commandmenu[data-v-dd90fe74]{animation:.3s ease-in-out .1s forwards v;background-color:var(--scalar-background-1);border-radius:var(--scalar-radius-lg);box-shadow:var(--scalar-shadow-2);left:50%;margin:12px;max-height:60dvh;max-width:580px;opacity:0;position:fixed;top:150px;transform:translate3d(-50%,10px,0);width:100%}.commandmenu-overlay[data-v-2fe57517],.commandmenu-overlay[data-v-dd90fe74]{animation:.3s ease-in-out forwards u;background:#0003;cursor:pointer;inset:0;position:fixed}.scalar .scalar-app-layout[data-v-6118685a]{background:var(--scalar-background-1);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:8px;height:calc(100% - 120px);margin:auto;max-width:1390px;overflow:hidden;position:relative;width:100%}.scalar .scalar-app-exit[data-v-6118685a]{background:#00000038;cursor:pointer;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:-1}.scalar .scalar-app-exit[data-v-6118685a]:before{color:#fff;font-family:sans-serif;font-size:30px;font-weight:100;line-height:50px;opacity:.6;position:absolute;right:12px;text-align:center;top:0}.scalar-container[data-v-6118685a]{align-items:center;bottom:0;display:flex;height:100%;justify-content:center;left:0;overflow:hidden;position:fixed;top:0;visibility:visible;width:100%}.scalar .url-form-input[data-v-6118685a]{min-height:auto!important}.scalar .scalar-container[data-v-6118685a]{line-height:normal}.scalar-client-fade-enter-active[data-v-6118685a],.scalar-client-fade-leave-active[data-v-6118685a]{transition:opacity .35s}.filter-hover-item[data-v-f97cc68c],.rabbitjump[data-v-23d35bb5],.rabbitjump[data-v-feb6f541],.scalar-client-fade-enter-from[data-v-6118685a],.scalar-client-fade-leave-to[data-v-6118685a],.v-enter-from[data-v-1f35725e],.v-enter-from[data-v-7ec8af01]{opacity:0}.open-api-client-button[data-v-c7bdd328]{align-items:center;border-radius:var(--scalar-radius);box-shadow:0 0 0 .5px var(--scalar-border-color);color:var(--scalar-sidebar-color-1);cursor:pointer;display:flex;font-size:var(--scalar-small);gap:6px;height:31px;justify-content:center;line-height:1.385;padding:9px 12px;text-align:center;-webkit-text-decoration:none;text-decoration:none;white-space:nowrap;width:100%}.open-api-client-button[data-v-c7bdd328]:hover{background:var(--scalar-sidebar-item-hover-background,var(--scalar-background-2))}[data-v-103d9d56] .cm-editor,[data-v-776dfdde] .cm-editor{background:none;height:100%;outline:0;padding:0}[data-v-103d9d56] .cm-content,[data-v-776dfdde] .cm-content{font-family:var(--scalar-font-code);font-size:var(--scalar-small);max-height:20px;padding:8px 0}[data-v-103d9d56] .cm-tooltip,[data-v-776dfdde] .cm-tooltip{background:none!important;border:none!important;border-radius:var(--scalar-radius);box-shadow:var(--scalar-shadow-2);filter:brightness(var(--scalar-lifted-brightness));outline:0!important;overflow:hidden!important}[data-v-103d9d56] .cm-tooltip-autocomplete ul li,[data-v-776dfdde] .cm-tooltip-autocomplete ul li{padding:3px 6px!important}[data-v-103d9d56] .cm-completionIcon-type:after,[data-v-776dfdde] .cm-completionIcon-type:after{color:var(--scalar-color-3)!important}[data-v-103d9d56] .cm-tooltip-autocomplete ul li[aria-selected],[data-v-776dfdde] .cm-tooltip-autocomplete ul li[aria-selected]{background:var(--scalar-background-2)!important;color:var(--scalar-color-1)!important}[data-v-103d9d56] .cm-tooltip-autocomplete ul,[data-v-776dfdde] .cm-tooltip-autocomplete ul{padding:6px!important;position:relative}[data-v-103d9d56] .cm-tooltip-autocomplete ul li:hover,[data-v-776dfdde] .cm-tooltip-autocomplete ul li:hover{background:var(--scalar-background-3)!important;border-radius:3px;color:var(--scalar-color-1)!important}[data-v-103d9d56] .cm-activeLine,[data-v-103d9d56] .cm-activeLineGutter,[data-v-776dfdde] .cm-activeLine,[data-v-776dfdde] .cm-activeLineGutter{background-color:#0000}[data-v-103d9d56] .cm-matchingBracket,[data-v-103d9d56] .cm-selectionMatch,[data-v-776dfdde] .cm-matchingBracket,[data-v-776dfdde] .cm-selectionMatch{background:var(--scalar-background-4)!important;border-radius:var(--scalar-radius)}[data-v-103d9d56] .cm-css-color-picker-wrapper,[data-v-776dfdde] .cm-css-color-picker-wrapper{border-radius:3px;display:inline-flex;outline:1px solid var(--scalar-background-3);overflow:hidden}[data-v-103d9d56] .cm-gutters,[data-v-776dfdde] .cm-gutters{background-color:#0000;border-radius:0 0 0 3px;border-right:none;color:var(--scalar-color-3);font-size:var(--scalar-small);line-height:22px}[data-v-103d9d56] .cm-gutters:before,[data-v-776dfdde] .cm-gutters:before{background-color:var(--scalar-background-1);border-radius:var(--scalar-radius) 0 0 var(--scalar-radius);content:"";height:calc(100% - 4px);left:2px;position:absolute;top:2px;width:calc(100% - 2px)}[data-v-103d9d56] .cm-gutterElement,[data-v-776dfdde] .cm-gutterElement{align-items:center;display:flex;font-family:var(--scalar-font-code)!important;justify-content:flex-end;padding-left:0!important;padding-right:6px!important;position:relative}.line-wrapping[data-v-103d9d56]:focus-within .cm-content{display:inline-table;min-height:fit-content;padding:3px 6px;white-space:break-spaces;word-break:break-all}.cm-pill{--tw-bg-base:var(--scalar-color-1);background:var(--tw-bg-base)!important;color:var(--tw-bg-base)}.monthHeader_QFki,.updatesPage_z4Av,header.heroBanner_qw_p,main.docs{background:var(--hero-background)}.address-bar-history-button[data-v-a93fa60f]:focus-within{background:var(--scalar-background-2)}.description[data-v-1b7a32a4] .markdown,.description[data-v-92012388] .markdown{color:var(--scalar-color--1);display:block;font-weight:var(--scalar-semibold);padding:0}.description[data-v-1b7a32a4] .markdown>:first-child,.description[data-v-92012388] .markdown>:first-child,.docItemContainer_Djhp article>:first-child,.docItemContainer_Djhp header+*,.start-ul li[data-v-673f10b6]:first-of-type{margin-top:0}[data-v-cb2a35da] .cm-editor,[data-v-f5565642] .cm-editor{height:100%;outline:0;width:100%}[data-v-cb2a35da] .cm-content,[data-v-f5565642] .cm-content{align-items:center;display:flex;font-size:var(--scalar-small);padding:0}.scroll-timeline-x[data-v-cb2a35da],.scroll-timeline-x[data-v-f5565642]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;-ms-overflow-style:none}.scroll-timeline-x-hidden[data-v-cb2a35da] .cm-scroller,.scroll-timeline-x-hidden[data-v-f5565642] .cm-scroller{scrollbar-width:none;-ms-overflow-style:none;overflow:auto;padding-right:20px}.scroll-timeline-x-hidden[data-v-cb2a35da] .cm-scroller::-webkit-scrollbar,.scroll-timeline-x-hidden[data-v-cb2a35da]::-webkit-scrollbar{display:none;height:0;width:0}.scroll-timeline-x-address[data-v-cb2a35da],.scroll-timeline-x-address[data-v-f5565642]{line-height:27px;scrollbar-width:none}.scroll-timeline-x-address[data-v-cb2a35da]:after,.scroll-timeline-x-address[data-v-f5565642]:after{content:"";cursor:text;height:100%;position:absolute;right:0;width:24px}.scroll-timeline-x-address[data-v-cb2a35da]:empty:before,.scroll-timeline-x-address[data-v-f5565642]:empty:before{color:var(--scalar-color-3);content:"Enter URL or cURL request";pointer-events:none}.fade-left[data-v-cb2a35da],.fade-left[data-v-f5565642],.fade-right[data-v-cb2a35da],.fade-right[data-v-f5565642]{animation-direction:reverse;animation-duration:1ms;animation-name:o;animation-timeline:--scroll-timeline;content:"";height:100%;pointer-events:none;position:sticky;z-index:1}.fade-left[data-v-cb2a35da],.fade-left[data-v-f5565642]{background:linear-gradient(-90deg,var(--scalar-address-bar-bg) 0,var(--scalar-address-bar-bg) 30%,var(--scalar-address-bar-bg) 100%);animation-direction:normal;left:-1px;min-width:6px}.fade-right[data-v-cb2a35da],.fade-right[data-v-f5565642]{background:linear-gradient(90deg,var(--scalar-address-bar-bg) 0,var(--scalar-address-bar-bg) 30%,var(--scalar-address-bar-bg) 100%);min-width:24px;right:-1px}.address-bar-bg-states[data-v-cb2a35da],.address-bar-bg-states[data-v-f5565642]{--scalar-address-bar-bg:var(--scalar-background-1);background:var(--scalar-address-bar-bg)}.address-bar-bg-states[data-v-cb2a35da]:has(.cm-focused){--scalar-address-bar-bg:var(--scalar-background-1);border-color:var(--scalar-border-color);outline:1px solid var(--scalar-color-accent)}.address-bar-bg-states:has(.cm-focused) .fade-left[data-v-cb2a35da],.address-bar-bg-states:has(.cm-focused) .fade-right[data-v-cb2a35da]{--scalar-address-bar-bg:var(--scalar-background-1)}.sidebar-height[data-v-7aa06e5a]{min-height:100%}.resizer[data-v-7aa06e5a]{border-right:2px solid #0000;bottom:0;cursor:col-resize;position:absolute;right:0;top:0;transition:border-right-color .3s;width:5px}.dragging .resizer[data-v-7aa06e5a],.resizer:hover,.resizer[data-v-7aa06e5a]:hover,.scalar-dragging .resizer{border-right-color:var(--scalar-background-3)}.dragging[data-v-7aa06e5a],.scalar-dragging{cursor:col-resize}.dragging[data-v-7aa06e5a]:before{content:"";display:block;height:100%;position:absolute;width:100%}[data-v-04661eb4] .cm-content,[data-v-28c8509c] .cm-content,[data-v-3157c3c7] .cm-content,[data-v-36811e28] .cm-content,[data-v-83bfcc8a] .cm-content,[data-v-c1a50a6e] .cm-content,[data-v-ddfccc08] .cm-content{align-items:center;background-color:#0000;display:flex;font-family:var(--scalar-font);font-size:var(--scalar-small);padding:5px 8px;width:100%}[data-v-c1a50a6e] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-04661eb4] .cm-content .cm-pill:not(:last-of-type),[data-v-28c8509c] .cm-content .cm-pill:not(:last-of-type),[data-v-3157c3c7] .cm-content .cm-pill:not(:last-of-type),[data-v-36811e28] .cm-content .cm-pill:not(:last-of-type),[data-v-83bfcc8a] .cm-content .cm-pill:not(:last-of-type),[data-v-c1a50a6e] .cm-content .cm-pill:not(:last-of-type),[data-v-ddfccc08] .cm-content .cm-pill:not(:last-of-type){margin-right:.5px}[data-v-04661eb4] .cm-content .cm-pill:not(:first-of-type),[data-v-28c8509c] .cm-content .cm-pill:not(:first-of-type),[data-v-3157c3c7] .cm-content .cm-pill:not(:first-of-type),[data-v-36811e28] .cm-content .cm-pill:not(:first-of-type),[data-v-83bfcc8a] .cm-content .cm-pill:not(:first-of-type),[data-v-c1a50a6e] .cm-content .cm-pill:not(:first-of-type),[data-v-ddfccc08] .cm-content .cm-pill:not(:first-of-type){margin-left:.5px}[data-v-3157c3c7] .cm-line,[data-v-c1a50a6e] .cm-line{overflow:hidden;padding:0;text-overflow:ellipsis;word-break:break-word}.required[data-v-3157c3c7]:after,.required[data-v-c1a50a6e]:after{content:"Required"}input[data-v-c1a50a6e]::placeholder{color:var(--scalar-color-3)}.scalar-password-input[data-v-3157c3c7],.scalar-password-input[data-v-c1a50a6e]{text-security:disc;-webkit-text-security:disc;-moz-text-security:disc}@keyframes m{0%{opacity:0;transform:scale(.85) translate(calc(50dvw + 80px),10px)}to{opacity:1;transform:scale(.85) translate(calc(50dvw + 80px))}}@keyframes n{0%{opacity:1;transform:scale(.85) translate(calc(50dvw + 80px))}to{opacity:1;transform:scale(1) translate(0)}}.section-flare{position:fixed;right:-50dvw;top:0}#scalar-client{display:flex;flex-direction:column;height:100dvh;width:100dvw;position:relative}.address-bar-history-button[data-v-c15c6573]:focus-within{background:var(--scalar-background-2)}[data-v-776dfdde] .cm-placeholder{color:var(--scalar-color-3)}.line-wrapping[data-v-776dfdde]:focus-within .cm-content{display:inline-table;min-height:fit-content;padding:3px 6px;white-space:break-spaces;word-break:break-all}.cm-pill{border-radius:30px;color:var(--scalar-color-1)!important;display:inline-block;font-size:var(--scalar-small);padding:0 9px}.dark-mode .cm-pill{background:var(--tw-bg-base)!important}.cm-pill:first-of-type{margin-left:0}.cm-editor .cm-widgetBuffer,.cm-foldGutter .cm-gutterElement:first-of-type{display:none}.cm-foldGutter .cm-gutterElement{font-size:var(--scalar-heading-4);padding:2px!important}.cm-foldGutter .cm-gutterElement:hover .cm-foldMarker{background:var(--scalar-background-2);border-radius:var(--scalar-radius);color:var(--scalar-color-1)}.scroll-timeline-x-hidden[data-v-f5565642] .cm-scroller::-webkit-scrollbar,.scroll-timeline-x-hidden[data-v-f5565642]::-webkit-scrollbar{display:none;height:0;width:0}@keyframes o{0%{opacity:0}1%{opacity:1}}.address-bar-bg-states[data-v-f5565642]:has(.cm-focused){--scalar-address-bar-bg:var(--scalar-background-1);border-color:var(--scalar-border-color);outline-style:solid;outline-width:1px}.address-bar-bg-states:has(.cm-focused) .fade-left[data-v-f5565642],.address-bar-bg-states:has(.cm-focused) .fade-right[data-v-f5565642]{--scalar-address-bar-bg:var(--scalar-background-1)}.fade-request-section-content[data-v-f97cc68c]{background:linear-gradient(to left,var(--scalar-background-1) 64%,#0000)}.filter-hover[data-v-f97cc68c]{height:100%;overflow:hidden;padding-left:24px;padding-right:39px;position:absolute;right:0;transition:width 0s ease-in-out .2s}.filter-hover[data-v-f97cc68c]:has(:focus-visible),.filter-hover[data-v-f97cc68c]:hover{width:100%;z-index:10}.filter-hover[data-v-f97cc68c]:before{background-color:var(--scalar-background-1);content:"";height:fit-content;left:0;opacity:0;pointer-events:none;position:absolute;top:0;transition:.3s ease-in-out;width:100%}.filter-hover-item[data-v-f97cc68c]:not(:last-of-type){transform:translate3d(0,3px,0)}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]{transition:opacity .2s ease-in-out,transform .2s ease-in-out}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:last-of-type{transition-delay:50ms}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(2){transition-delay:.1s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(3){transition-delay:.15s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(4){transition-delay:.2s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(5){transition-delay:.25s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(6){transition-delay:.3s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(7){transition-delay:.35s}.filter-hover:has(:focus-visible) .filter-hover-item[data-v-f97cc68c],.filter-hover:hover .filter-hover-item[data-v-f97cc68c]{opacity:1;transform:translateZ(0)}.filter-hover[data-v-f97cc68c]:has(:focus-visible):before,.filter-hover[data-v-f97cc68c]:hover:before{backdrop-filter:blur(10px);opacity:.9}.filter-button[data-v-f97cc68c]{top:50%;transform:translateY(-50%)}.context-bar-group:has(:focus-visible) .context-bar-group-hover\:text-c-1[data-v-f97cc68c],.context-bar-group:hover .context-bar-group-hover\:text-c-1[data-v-f97cc68c]{--tw-text-opacity:1;color:rgb(var(--scalar-color-1)/var(--tw-text-opacity))}.context-bar-group:has(:focus-visible) .context-bar-group-hover\:hidden[data-v-f97cc68c],.context-bar-group:hover .context-bar-group-hover\:hidden[data-v-f97cc68c]{display:none}.schema>span[data-v-4df72868],.schema>span[data-v-f2ab7aa3]{display:flex;white-space:nowrap}[data-v-36811e28] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-04661eb4] .cm-line,[data-v-28c8509c] .cm-line,[data-v-36811e28] .cm-line,[data-v-83bfcc8a] .cm-line,[data-v-ddfccc08] .cm-line{overflow:hidden;padding:0;text-overflow:ellipsis}.filemask[data-v-04661eb4],.filemask[data-v-36811e28]{-webkit-mask-image:linear-gradient(to right,#0000 0,var(--scalar-background-2) 20px);mask-image:linear-gradient(to right,#0000 0,var(--scalar-background-2) 20px)}.modal-actions[data-v-43df1726],.modal-actions[data-v-694018d6]{display:flex;gap:1rem;justify-content:flex-end}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}[data-v-3157c3c7] .cm-content:has(.cm-pill){padding:5px 8px}input[data-v-3157c3c7]::placeholder{color:var(--scalar-color-3)}.request-section-content[data-v-22abab53],.request-section-content[data-v-287f5ecf]{--scalar-border-width:.5px}.request-section-content-filter[data-v-22abab53],.request-section-content-filter[data-v-287f5ecf]{box-shadow:0 -10px 0 10px var(--scalar-background-1)}.request-item:focus-within .request-meta-buttons[data-v-22abab53]{opacity:1}.group-hover-input[data-v-22abab53],.group-hover-input[data-v-242ff9a6],.group-hover-input[data-v-287f5ecf],.group-hover-input[data-v-fced736a]{border-color:#0000;border-width:var(--scalar-border-width)}.group:hover .group-hover-input[data-v-22abab53],.group:hover .group-hover-input[data-v-242ff9a6],.group:hover .group-hover-input[data-v-287f5ecf],.group:hover .group-hover-input[data-v-fced736a]{background:var(--scalar-background-1);border-color:var(--scalar-border-color)}.group-hover-input[data-v-22abab53]:focus,.group-hover-input[data-v-242ff9a6]:focus,.group-hover-input[data-v-287f5ecf]:focus,.group-hover-input[data-v-fced736a]:focus{background:none!important;border-color:var(--scalar-border-color)!important}[data-v-1399120c] .cm-editor,[data-v-9c64cd5e] .cm-editor{background-color:#0000;font-size:var(--scalar-small);outline:0}[data-v-1399120c] .cm-gutters,[data-v-9c64cd5e] .cm-gutters{background-color:var(--scalar-background-1);border-radius:var(--scalar-radius) 0 0 var(--scalar-radius)}.body-raw[data-v-1399120c] .cm-scroller,.body-raw[data-v-9c64cd5e] .cm-scroller{min-width:100%;overflow:auto}.ascii-art-animate .ascii-art-line[data-v-69ebd973]{animation:4s step-end 1s both p,.5s step-end infinite q;border-right:1ch solid #0000}@keyframes p{0%{width:0}to{width:100%}}@keyframes q{0%{border-right-color:initial}50%{border-right-color:#0000}}.scalar-version-number[data-v-34b57d9d],.scalar-version-number[data-v-6d2bdb61]{align-items:center;border-radius:9px 9px 16px 12px;box-shadow:inset 2px 0 0 2px var(--scalar-background-2);display:flex;flex-direction:column;font-family:var(--scalar-font-code);font-size:8px;height:76px;justify-content:center;line-height:11px;margin-left:-36px;margin-top:-113px;position:absolute;text-align:center;text-decoration-color:var(--scalar-color-3);text-transform:none;transform:skew(0,13deg);width:76px}.scalar-version-number a[data-v-34b57d9d],.scalar-version-number a[data-v-6d2bdb61]{background:var(--scalar-background-2);border:.5px solid var(--scalar-border-color);border-radius:3px;font-weight:700;padding:2px 4px;-webkit-text-decoration:none;text-decoration:none}.gitbook-show[data-v-34b57d9d]{display:none}.v-enter-active[data-v-1f35725e],.v-enter-active[data-v-7ec8af01]{transition:opacity .5s}.animate-response-heading .response-heading[data-v-6e4eec82],.animate-response-heading .response-heading[data-v-9c1fd1c7]{animation:.2s ease-in-out forwards x;opacity:1}.animate-response-heading .animate-response-children[data-v-6e4eec82],.animate-response-heading .animate-response-children[data-v-9c1fd1c7]{animation:.2s ease-in-out 50ms forwards y;opacity:0}.request-method[data-v-cbc307e3]{font-family:var(--scalar-font-code);margin-right:6px;text-transform:uppercase}.request-card-footer[data-v-cbc307e3]{display:flex;flex-shrink:0;justify-content:flex-end;padding:6px;position:relative}.request-editor-section[data-v-cbc307e3]{display:flex;flex:1}.request-card-simple[data-v-cbc307e3]{align-items:center;display:flex;font-size:var(--scalar-small);justify-content:space-between;padding:8px 8px 8px 12px}.code-snippet[data-v-cbc307e3]{display:flex;flex-direction:column;width:100%}.resizer[data-v-e2c54c18]{border-right:2px solid #0000;bottom:0;cursor:col-resize;position:absolute;right:0;top:0;transition:border-right-color .3s;width:5px;z-index:100}.scalar-dragging:after{content:"";display:block;inset:0;position:absolute}.scroll-timeline-x[data-v-f4568236]::-webkit-scrollbar{display:none}.splash-screen[data-v-af32615f]{animation:.5s ease-in-out forwards r;opacity:0}.logo-icon[data-v-af32615f]{animation:.6s ease-in-out .2s forwards s,2s ease-in-out .8s infinite t;opacity:0}@keyframes r{0%{opacity:0}to{opacity:.9}}@keyframes s{0%{opacity:0;transform:scale(.9)}to{opacity:.8;transform:scale(1)}}@keyframes t{0%,to{opacity:.8}50%{opacity:.6}}@keyframes u{0%{opacity:0}to{opacity:1}}@keyframes v{0%{opacity:0;transform:translate3d(-50%,10px,0)}to{opacity:1;transform:translate3d(-50%,0,0)}}.empty-sidebar-item-content[data-v-23d35bb5],.empty-sidebar-item-content[data-v-feb6f541],.gitbook-show[data-v-6d2bdb61],.gitbook-show[data-v-c8df97c6]{display:none}.empty-sidebar-item .empty-sidebar-item-content[data-v-23d35bb5],.empty-sidebar-item .empty-sidebar-item-content[data-v-feb6f541],.tocCollapsibleContent_vkbj a{display:block}.empty-sidebar-item:hover .rabbitjump[data-v-23d35bb5],.empty-sidebar-item:hover .rabbitjump[data-v-feb6f541]{animation:.5s step-end infinite A;opacity:1}.empty-sidebar-item:hover .rabbitsit[data-v-23d35bb5],.empty-sidebar-item:hover .rabbitsit[data-v-feb6f541]{animation:.5s step-end infinite B;opacity:0}.nav-item:hover .nav-item-close[data-v-2e741aab],header.heroBanner_qw_p img.starButton_Vbnu:hover{opacity:1}.empty-sidebar-item:hover .rabbit-ascii[data-v-23d35bb5],.empty-sidebar-item:hover .rabbit-ascii[data-v-feb6f541]{animation:8s linear infinite z}.nav-single-tab[data-v-2e741aab]{color:var(--scalar-color-1);height:100%;width:100%}.nav-item[data-v-2e741aab],.nav-single-tab[data-v-2e741aab]{align-items:center;display:flex;justify-content:center;overflow:hidden}.nav-item[data-v-2e741aab]{background:var(--scalar-background-3);border:var(--scalar-border-width) solid var(--scalar-background-2);border-radius:var(--scalar-radius-lg);color:var(--scalar-color-3);cursor:pointer;flex:1;min-width:0;padding:4.5px 1rem;position:relative}.dark-mode .nav-item[data-v-2e741aab]{background:var(--scalar-background-2)}#scalar-client,.dark-mode .nav-item__active[data-v-2e741aab],.nav-item__active .nav-item-close[data-v-2e741aab]:hover{background-color:var(--scalar-background-2)}.dark-mode #scalar-client{background-color:var(--scalar-background-1)}.scalar-collection-auth[data-v-60f72663],.scalar-collection-auth[data-v-cc87292e]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);overflow:hidden}[data-v-ddfccc08] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-28c8509c] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-5997a667] .cm-scroller,[data-v-d486ea71] .cm-scroller{max-width:100%;overflow:auto hidden}.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-font-size:var(--scalar-small);--callout-neutral-primary:var(--scalar-color-3);--callout-neutral-secondary:var(--scalar-background-2);--callout-neutral-font-color:var(--scalar-color-1);--callout-success-primary:var(--scalar-color-green);--callout-success-secondary:var(--scalar-color-green);--callout-success-font-color:var(--scalar-color-1);--callout-danger-primary:var(--scalar-color-red);--callout-danger-secondary:var(--scalar-color-red);--callout-danger-font-color:var(--scalar-color-1);--callout-warning-primary:var(--scalar-color-yellow);--callout-warning-secondary:var(--scalar-color-yellow);--callout-warning-font-color:var(--scalar-color-1);--callout-info-primary:var(--scalar-color-blue);--callout-info-secondary:var(--scalar-color-blue);--callout-info-font-color:var(--scalar-color-1);--callout-line-height:22px}.t-editor__callout[data-v-fa58cbab]{border-radius:var(--scalar-radius);margin-top:var(--scalar-block-spacing);--callout-primary:var(--scalar-border-color);--callout-secondary:var(--scalar-background-2);--callout-svg:var(--callout-primary);background:var(--callout-secondary);border:var(--scalar-border-width) solid var(--callout-primary);padding:10px 14px}.t-editor__callout .callout-content__text[data-v-fa58cbab]{flex:1;font-size:var(--callout-font-size);line-height:var(--callout-line-height)}.t-editor__callout .callout-content__icon[data-v-fa58cbab]{align-items:center;border-radius:var(--scalar-radius);color:var(--callout-svg);display:flex;height:fit-content;justify-content:center;position:relative;width:18px}.t-editor__callout .callout-content__icon[data-v-fa58cbab]:before{content:"​";line-height:var(--callout-line-height)}.t-editor__callout.callout__neutral[data-v-fa58cbab]{--callout-primary:var(--callout-neutral-primary);--callout-secondary:var(--callout-neutral-secondary);--callout-font-color:var(--callout-neutral-font-color);--callout-svg:var(--callout-neutral-font-color)}.t-editor__callout.callout__info[data-v-fa58cbab]{--callout-primary:var(--callout-info-primary);--callout-secondary:var(--callout-info-secondary);--callout-font-color:var(--callout-info-font-color);--callout-svg:var(--callout-info-primary)}.t-editor__callout.callout__warning[data-v-fa58cbab]{--callout-primary:var(--callout-warning-primary);--callout-secondary:var(--callout-warning-secondary);--callout-font-color:var(--callout-warning-font-color);--callout-svg:var(--callout-warning-primary)}.t-editor__callout.callout__success[data-v-fa58cbab]{--callout-primary:var(--callout-success-primary);--callout-secondary:var(--callout-success-secondary);--callout-font-color:var(--callout-success-font-color);--callout-svg:var(--callout-success-primary)}.t-editor__callout.callout__danger[data-v-fa58cbab]{--callout-primary:var(--callout-danger-primary);--callout-secondary:var(--callout-danger-secondary);--callout-font-color:var(--callout-danger-font-color);--callout-svg:var(--callout-danger-primary)}[data-v-04661eb4] .cm-content:has(.cm-pill){padding:5px 8px}.auth-combobox-position[data-v-0bb98074],.auth-combobox-position[data-v-3f1067a4]{margin-left:120px}.scroll-timeline-x[data-v-0bb98074],.scroll-timeline-x[data-v-3f1067a4]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;scrollbar-width:none;-ms-overflow-style:none;overflow:auto}.fade-left[data-v-0bb98074],.fade-left[data-v-3f1067a4],.fade-right[data-v-0bb98074],.fade-right[data-v-3f1067a4]{animation-direction:reverse;animation-duration:1ms;animation-name:w;animation-timeline:--scroll-timeline;content:"";height:100%;min-height:24px;pointer-events:none;position:sticky}.fade-left[data-v-0bb98074],.fade-left[data-v-3f1067a4]{background:linear-gradient(-90deg,var(--scalar-background-1) 0,var(--scalar-background-1) 60%,var(--scalar-background-1) 100%);animation-direction:normal;left:-1px;min-width:3px}.fade-right[data-v-0bb98074],.fade-right[data-v-3f1067a4]{background:linear-gradient(90deg,var(--scalar-background-1) 0,var(--scalar-background-1) 60%,var(--scalar-background-1) 100%);margin-left:-20px;min-width:24px;right:-1px;top:0}@keyframes w{0%{opacity:0}15%{opacity:1}}.request-item:focus-within .request-meta-buttons[data-v-287f5ecf]{opacity:1}.response-body-virtual[data-headlessui-state=open],.response-body-virtual[data-headlessui-state=open] .diclosure-panel{display:flex;flex-direction:column;flex-grow:1}.keycap-n[data-v-b1211b87]{background:-webkit-linear-gradient(5deg,#0000 30%,var(--scalar-color-3) 50%);-webkit-text-fill-color:#0000;-webkit-background-clip:text;background-clip:text}.keycap-hotkey[data-v-b1211b87]{line-height:26px;position:absolute;top:32px}@keyframes x{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}@keyframes y{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.ellipsis-position[data-v-01a1ab71]{transform:translate3d(calc(-100% - 4.5px),0,0)}.indent-border-line-offset[data-v-b2f06757]:before{left:var(--v2bb71a6d)}.indent-padding-left[data-v-b2f06757]{padding-left:calc(var(--b961af9e) + 6px)}.sidebar-folderitem[data-v-b2f06757] .ellipsis-position{right:6px;transform:none}.search-button-fade[data-v-23d35bb5]{background:linear-gradient(var(--scalar-background-1) 32px,var(--scalar-background-1) 38px,#0000)}@keyframes z{0%,to{transform:translateZ(0)}25%,75%{transform:translate3d(250px,0,0)}25.01%,75.01%{transform:translate3d(-250px,0,0)}}@keyframes A{0%,to{opacity:1}50%{opacity:0}}@keyframes B{0%,to{opacity:0}50%{opacity:1;transform:translate3d(0,-8px,0)}}.request-text-color-text[data-v-02af05d1],.request-text-color-text[data-v-57ae0d10]{background:linear-gradient(var(--scalar-background-1),var(--scalar-background-3));box-shadow:0 0 0 1px var(--scalar-border-color);color:var(--scalar-color-1)}[data-v-83bfcc8a] .cm-content:has(.cm-pill){padding:5px 8px}.dragover-above[data-v-a89d6a6e]:after,.dragover-below[data-v-a89d6a6e]:after{background:var(--scalar-color-blue);content:"";display:block;height:3px;position:absolute;top:-1.5px;width:100%;border-radius:var(--scalar-radius);pointer-events:none}.dragover-below[data-v-a89d6a6e]:after{bottom:-1.5px;top:auto}.dragover-asChild[data-v-a89d6a6e]:after{background:var(--scalar-color-blue);content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%;border-radius:var(--scalar-radius);pointer-events:none}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}.scalar-app .markdown .markdown-alert{background-color:var(--scalar-background-2)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue);border:var(--scalar-border-width) solid var(--scalar-color-blue)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2);border:var(--scalar-border-width) solid var(--scalar-color-2)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange);border:var(--scalar-border-width) solid var(--scalar-color-orange)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red);border:var(--scalar-border-width) solid var(--scalar-color-red)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green);border:var(--scalar-border-width) solid var(--scalar-color-green)}.scalar-app .border-c-accent\/30{border-color:var(--scalar-color-accent)}.scalar-app .bg-c-3\/5{background-color:var(--scalar-color-3)}.scalar-app .bg-c-accent\/10,.scalar-app .bg-c-accent\/5{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.scalar-app .to-b-1\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000)}}.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}.scalar-app .markdown .markdown-alert{background-color:var(--scalar-background-2)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue);border:var(--scalar-border-width) solid var(--scalar-color-blue)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2);border:var(--scalar-border-width) solid var(--scalar-color-2)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange);border:var(--scalar-border-width) solid var(--scalar-color-orange)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red);border:var(--scalar-border-width) solid var(--scalar-color-red)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green);border:var(--scalar-border-width) solid var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000);text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000)}.scalar-app .markdown .markdown-alert{background-color:color-mix(in srgb,var(--scalar-background-2),#0000)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:color-mix(in srgb,var(--scalar-color-blue),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-blue),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:color-mix(in srgb,var(--scalar-color-2),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-2),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:color-mix(in srgb,var(--scalar-color-orange),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-orange),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:color-mix(in srgb,var(--scalar-color-red),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-red),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:color-mix(in srgb,var(--scalar-color-green),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-green),#0000 50%)}}.scalar-app .border-c-accent\/30{border-color:color-mix(in oklab,var(--scalar-color-accent) 30%,#0000)}.scalar-app .bg-c-3\/5{background-color:color-mix(in oklab,var(--scalar-color-3) 5%,#0000)}.scalar-app .bg-c-accent\/5{background-color:color-mix(in oklab,var(--scalar-color-accent) 5%,#0000)}.scalar-app .bg-c-accent\/10{background-color:color-mix(in oklab,var(--scalar-color-accent) 10%,#0000)}}.dark-mode .nav-item[data-v-2e741aab],.dark-mode .nav-item[data-v-507381a3]{background:color-mix(in srgb,var(--scalar-background-2),#0000)}.cm-pill{background:color-mix(in srgb,var(--tw-bg-base),#0000 94%)!important}.fade-left[data-v-cb2a35da],.fade-left[data-v-f5565642]{background:linear-gradient(-90deg,color-mix(in srgb,var(--scalar-address-bar-bg),#0000 100%) 0,color-mix(in srgb,var(--scalar-address-bar-bg),#0000 20%) 30%,var(--scalar-address-bar-bg) 100%)}.fade-right[data-v-cb2a35da],.fade-right[data-v-f5565642]{background:linear-gradient(90deg,color-mix(in srgb,var(--scalar-address-bar-bg),#0000 100%) 0,color-mix(in srgb,var(--scalar-address-bar-bg),#0000 20%) 30%,var(--scalar-address-bar-bg) 100%)}.address-bar-bg-states[data-v-cb2a35da],.address-bar-bg-states[data-v-f5565642]{--scalar-address-bar-bg:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.dark-mode .cm-pill{background:color-mix(in srgb,var(--tw-bg-base),#0000 90%)!important}.group:hover .group-hover-input[data-v-22abab53],.group:hover .group-hover-input[data-v-242ff9a6],.group:hover .group-hover-input[data-v-287f5ecf],.group:hover .group-hover-input[data-v-fced736a]{background:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.dark-mode #scalar-client{background-color:color-mix(in srgb,var(--scalar-background-1) 65%,#000)}.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-neutral-secondary:color-mix(in srgb,var(--scalar-background-2),#0000 50%);--callout-success-secondary:color-mix(in srgb,var(--scalar-color-green),#0000 97%);--callout-danger-secondary:color-mix(in srgb,var(--scalar-color-red),#0000 97%);--callout-warning-secondary:color-mix(in srgb,var(--scalar-color-yellow),#0000 97%);--callout-info-secondary:color-mix(in srgb,var(--scalar-color-blue),#0000 97%)}.t-editor__callout[data-v-fa58cbab]{border:var(--scalar-border-width) solid color-mix(in srgb,var(--callout-primary),#0000 50%)}.fade-left[data-v-0bb98074],.fade-left[data-v-3f1067a4]{background:linear-gradient(-90deg,color-mix(in srgb,var(--scalar-background-1),#0000 100%) 0,color-mix(in srgb,var(--scalar-background-1),#0000 20%) 60%,var(--scalar-background-1) 100%)}.fade-right[data-v-0bb98074],.fade-right[data-v-3f1067a4]{background:linear-gradient(90deg,color-mix(in srgb,var(--scalar-background-1),#0000 100%) 0,color-mix(in srgb,var(--scalar-background-1),#0000 20%) 60%,var(--scalar-background-1) 100%)}.search-button-fade[data-v-23d35bb5]{background:linear-gradient(var(--scalar-background-1) 32px,color-mix(in srgb,var(--scalar-background-1),#0000) 38px,#0000)}.dragover-above[data-v-a89d6a6e]:after,.dragover-asChild[data-v-a89d6a6e]:after,.dragover-below[data-v-a89d6a6e]:after{background:color-mix(in srgb,var(--scalar-color-blue),#0000 85%)}}.empty-variable-name[data-v-298ba76d]:empty:before{color:var(--scalar-color-3);content:"Untitled"}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}.t-doc__sidebar[data-v-94d6fca6]{z-index:10}.references-layout[data-v-94d6fca6]{flex:1;max-width:100%;min-height:100dvh;min-width:100%;--full-height:100dvh;background:var(--scalar-background-1);display:grid;grid-template-areas:"c c" "d g" "h h";grid-template-columns:auto 1fr;grid-template-rows:var(--scalar-header-height,0) repeat(2,auto)}.references-editor[data-v-94d6fca6]{background:var(--scalar-background-1);display:flex;grid-area:i;min-width:0}.references-rendered[data-v-94d6fca6]{background:var(--scalar-background-1);grid-area:g;min-width:0;position:relative}.references-classic .references-rendered[data-v-94d6fca6],.scalar-api-reference.references-classic[data-v-94d6fca6]{height:auto!important;max-height:none!important}@layer scalar-config{.scalar-api-reference[data-v-94d6fca6]{--refs-header-height:calc(var(--scalar-custom-header-height, 0px) + var(--scalar-header-height, 0px));--refs-viewport-offset:calc(var(--refs-header-height, 0px) + var(--refs-content-offset, 0px));--refs-viewport-height:calc(var(--full-height, 100dvh) - var(--refs-viewport-offset, 0px));--refs-sidebar-width:var(--scalar-sidebar-width,0px);--refs-sidebar-height:calc(var(--full-height, 100dvh) - var(--refs-header-height, 0px));--refs-content-max-width:var(--scalar-content-max-width,1540px)}.scalar-api-reference.references-classic[data-v-94d6fca6]{--refs-content-max-width:var(--scalar-content-max-width,1420px);min-height:100dvh;--refs-sidebar-width:0}.references-sidebar[data-v-94d6fca6]{--refs-sidebar-width:var(--scalar-sidebar-width,288px)}}.references-footer[data-v-94d6fca6]{grid-area:h}.darklight-reference[data-v-94d6fca6]{margin-top:auto;width:100%}.start[data-v-673f10b6]{display:flex;flex-flow:wrap;justify-content:space-between;padding:24px;position:relative;z-index:0}.start-h1[data-v-673f10b6]{font-size:var(--scalar-heading-2);margin-bottom:0;position:relative}.start-h1[data-v-673f10b6],.start-h3[data-v-673f10b6]{color:var(--scalar-color-1);font-weight:var(--scalar-bold);line-height:1.45;margin-top:0;width:100%}.start-h3[data-v-673f10b6]{display:block;font-size:var(--scalar-paragraph);margin-bottom:6px}.start-p[data-v-673f10b6]{color:var(--scalar-color-2);width:100%}.start-p[data-v-673f10b6],.start-ul[data-v-673f10b6]{font-size:var(--scalar-paragraph);line-height:1.5;margin-top:12px}.start-ul[data-v-673f10b6]{display:flex;flex-flow:wrap;gap:24px;list-style:initial;padding-left:0}.start-ul li[data-v-673f10b6]{color:var(--scalar-color-2);list-style:none;margin:0;padding:0;width:calc(50% - 24px)}.start-section[data-v-673f10b6]{display:flex;flex-flow:wrap;margin-bottom:12px;width:100%}.start-h2[data-v-673f10b6]{border:1px solid var(--scalar-border-color);border-top-left-radius:var(--scalar-radius-lg);border-top-right-radius:var(--scalar-radius-lg);color:var(--scalar-color-3);width:100%}.start-h2[data-v-673f10b6],.start-item[data-v-673f10b6]{background:var(--scalar-background-2);font-size:var(--scalar-mini);font-weight:var(--scalar-semibold);padding:9px}.start-item[data-v-673f10b6]{align-items:center;border-bottom:1px solid var(--scalar-border-color);border-right:1px solid var(--scalar-border-color);color:var(--scalar-color-1);cursor:pointer;display:flex;flex:1;text-transform:capitalize;-webkit-user-select:none;user-select:none}.start-section-integrations .start-item[data-v-673f10b6]:first-of-type{border-bottom-left-radius:var(--scalar-radius-lg);border-left:1px solid var(--scalar-border-color)}.start-section-integrations .start-item[data-v-673f10b6]:last-of-type{border-bottom-right-radius:var(--scalar-radius-lg)}.start-section-colors .start-item[data-v-673f10b6]{min-width:33.33%}.start-section-colors .start-item[data-v-673f10b6]:nth-child(3n+2){border-left:1px solid var(--scalar-border-color)}.start-item svg[data-v-673f10b6]{height:14px;margin-right:6px;width:14px}.start-item-active[data-v-673f10b6]{color:var(--scalar-color-1);position:relative;z-index:10}.start-item-active[data-v-673f10b6]:before{border:1px solid var(--scalar-color-1);content:"";inset:-1px;pointer-events:none;position:absolute}.start-cta[data-v-673f10b6]{display:flex;gap:12px;margin-top:24px;width:100%}.start-row[data-v-673f10b6]{margin-top:12px;overflow:hidden;width:100%}.start-hero-copy[data-v-673f10b6]{background:var(--scalar-background-2);border-radius:var(--scalar-radius-lg);padding:12px}.start-p-small[data-v-673f10b6]{color:var(--scalar-color-2);font-size:var(--scalar-mini);font-weight:var(--scalar-semibold);line-height:1.4;margin-bottom:12px}.start-cta[data-v-673f10b6]{margin-bottom:12px;white-space:nowrap;width:fit-content}.start-copy[data-v-673f10b6]{align-items:center;display:flex;flex-direction:column;justify-content:center;padding:76px 48px 48px;text-align:center}.start-logo[data-v-673f10b6]{aspect-ratio:1;border-radius:50%;box-shadow:var(--scalar-shadow-2);color:var(--scalar-color-1);margin-bottom:24px;position:relative;width:72px}.start-logo[data-v-673f10b6]:before{aspect-ratio:1;background-image:linear-gradient(to right,var(--scalar-border-color) 1px,#0000 1px),linear-gradient(to bottom,var(--scalar-border-color) 1px,#0000 1px);background-size:24px 24px;border-radius:50%;box-shadow:inset 0 0 50px var(--scalar-background-1),inset 0 0 50px var(--scalar-background-1);content:"";left:-100%;position:absolute;top:-100%;width:300%;z-index:-1}.start-logo svg[data-v-673f10b6]{background:var(--scalar-background-1);border-radius:50%;height:auto;padding:3px;position:relative;width:100%}.updatesPage_z4Av{min-height:80vh;padding:2rem 1rem 4rem}.pageHeader_g4VR{margin-bottom:2.5rem;text-align:center}.pageHeader_g4VR h1{color:var(--text-color);font-family:Racing Sans One,cursive;font-size:2.5rem;margin-bottom:.25rem}.pageHeader_g4VR p{color:var(--mid-grey);font-family:Raleway,sans-serif;font-size:1.1rem;margin:0}.rssLink_aJml{align-items:center;border:1px solid var(--ifm-color-primary,#54bff7);border-radius:4px;color:var(--ifm-color-primary,#54bff7);display:inline-flex;font-family:Raleway,sans-serif;font-size:.95rem;font-weight:600;gap:.4rem;margin-top:.85rem;padding:.3rem .7rem;transition:background .2s,color .2s}.commitRow_RIZe,.releaseCard_aa3v .entryTitle_RUqs,.tagCard_SluW .entryTitle_RUqs{align-items:center;display:flex;color:var(--text-color)}.releaseCard_aa3v,.tagCard_SluW{border-radius:8px;box-shadow:var(--feature-img-shadow)}.badge_qP1g,.count_Ad5j,.monthHeader_QFki,.sha_JsPS,.tagCard_SluW .entryTitle_RUqs,.tagPill_IKwa{font-family:Roboto Mono,monospace}.rssLink_aJml:hover{background:var(--ifm-color-primary,#54bff7);color:#fff}.timeline_WTQQ{margin:0 auto;max-width:1000px;padding-left:2.5rem;position:relative;width:80%}.timeline_WTQQ:before{background:var(--mid-grey);bottom:0;content:"";left:0;opacity:.4;position:absolute;top:0;width:2px}.monthHeader_QFki{color:var(--mid-grey);font-size:1rem;margin:0;padding:.75rem 0 .5rem;position:sticky;top:60px;z-index:1}.entry_mTt2{margin-bottom:1rem;position:relative}.entry_mTt2:before{border-radius:50%;content:"";position:absolute;z-index:1}.releaseEntry_ZG2V:before{background:var(--accent-pink);height:14px;left:-2.5rem;margin-left:-6px;top:1rem;width:14px}.tagEntry_ORGt:before{background:var(--accent-blue);height:10px;left:-2.5rem;margin-left:-4px;top:1rem;width:10px}.commitEntry_JtMn:before{background:var(--mid-grey);height:6px;left:-2.5rem;margin-left:-2px;top:.9rem;width:6px}.commitRow_RIZe:hover,.releaseCard_aa3v,.tagCard_SluW{background:var(--background)}.releaseCard_aa3v{border-left:4px solid var(--accent-pink);padding:1.25rem 1.5rem}.releaseCard_aa3v .entryTitle_RUqs{flex-wrap:wrap;font-family:Roboto Mono,monospace;font-size:1.3rem;gap:.5rem;margin:0 0 .5rem}.tagCard_SluW{border-left:4px solid var(--accent-blue);padding:1rem 1.25rem}.badgeRelease_agcf,.button.color-btn.btn-pink{background:var(--accent-pink)}.tagCard_SluW .entryTitle_RUqs{font-size:1.1rem;gap:.5rem;margin:0 0 .25rem}.commitLink_QhHv,.meta_GplF{align-items:center;gap:.75rem}.commitRow_RIZe{border-bottom:1px solid #80808026;gap:.75rem;padding:.5rem 0;transition:background .15s}.commitLink_QhHv{display:flex;overflow:hidden}.commitLink_QhHv,.commitLink_QhHv:hover,.tile_CRAN,.tile_CRAN:hover{color:var(--text-color);-webkit-text-decoration:none;text-decoration:none}.fetchErrorCard_M8w3 a,.keepReading_G_Ux,.meta_GplF a,main.docs p.see-repo-note a{-webkit-text-decoration:underline;text-decoration:underline}.badge_qP1g{border-radius:4px;color:#fff;display:inline-block;font-size:.7rem;line-height:1.4;padding:.1rem .5rem;white-space:nowrap}.badgeTag_gC_o{background:var(--accent-blue)}.tagPill_IKwa{border:1px solid var(--mid-grey);border-radius:4px;color:var(--mid-grey);font-size:.8rem;padding:.05rem .4rem}.avatarSmall_luV7,.avatar_Vzby,.contributorAvatar_eGpt img{border-radius:50%;vertical-align:middle}.errorMessage_lztz,.fetchErrorCard_M8w3{box-shadow:var(--feature-img-shadow);padding:1.25rem 1.5rem;text-align:center}.bodyPreview_iKaD{color:var(--mid-grey);display:-webkit-box;font-size:.9rem;-webkit-line-clamp:3;margin:.25rem 0 .5rem;-webkit-box-orient:vertical;line-height:1.5;overflow:hidden}.meta_GplF,.sha_JsPS{color:var(--mid-grey);font-size:.85rem}.meta_GplF{display:flex;flex-wrap:wrap}.meta_GplF a{color:var(--mid-grey)}.linksList_CKaa img,.meta_GplF a .providerIcon_NdOm{margin-right:.25rem;vertical-align:middle;width:1.35rem}.avatar_Vzby{height:24px;width:24px}.authorInfo_pi2M{align-items:center;display:flex;gap:.35rem}.sha_JsPS{flex-shrink:0}.commitMsg_XeKn{font-size:.95rem;max-width:500px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.authorLink_YWtN{color:var(--text-color);opacity:.75;transition:.2s}.authorLink_YWtN:hover .avatarSmall_luV7{transform:scale(1.08)}.avatarSmall_luV7{height:18px;margin-right:.2rem;transition:.2s;width:18px}.errorMessage_lztz,.fetchErrorCard_M8w3,.skeleton_NVtD{background:var(--background);border-radius:8px}.commitMeta_teNK{align-items:center;color:var(--mid-grey);display:flex;flex-shrink:0;font-size:.8rem;gap:.3rem;margin-left:auto;white-space:nowrap}.contributors_QyJV{align-items:center;display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.5rem}.contributorAvatar_eGpt{display:block;flex-shrink:0;height:26px;width:26px}.contributorAvatar_eGpt img{height:26px;transition:transform .2s;width:26px}.contributorAvatar_eGpt:hover img{transform:scale(1.15)}.skeleton_NVtD{animation:1.5s ease-in-out infinite C;height:80px;margin-bottom:1rem}.authorSocialIcon_XYv3,.authorSocialLink_owbf,.authorSocials_rSDt{height:var(--docusaurus-blog-social-icon-size)}@keyframes C{0%,to{opacity:.4}50%{opacity:1}}.loadMore_YatW{display:flex;justify-content:center;margin:2rem auto}.fetchErrorCard_M8w3{color:var(--mid-grey);font-size:.95rem;margin:1.5rem auto;max-width:1000px;width:80%}.fetchErrorCard_M8w3 a{color:var(--accent-blue)}.errorMessage_lztz{color:var(--mid-grey);font-size:1.1rem;margin:0 auto;width:fit-content}.authorSocialIcon_XYv3,.authorSocialLink_owbf{width:var(--docusaurus-blog-social-icon-size)}.errorMessage_lztz p{margin:.25rem 0}.sidebar_re4s{overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 2rem)}.sidebarItemTitle_pO2u{font-size:var(--ifm-h3-font-size);font-weight:var(--ifm-font-weight-bold)}.container_mt6G,.sidebarItemList_Yudw{font-size:.9rem}.sidebarItem__DBe{margin-top:.7rem}.sidebarItemLink_mo7H{color:var(--ifm-font-color-base);display:block}.sidebarItemLinkActive_I1ZP{color:var(--ifm-color-primary)!important}.yearGroupHeading_rMGB{margin-bottom:.4rem;margin-top:1.6rem}.yearGroupHeading_QT03{margin:1rem .75rem .5rem}[data-theme=dark] .blueskySvg_AzZw,[data-theme=dark] .githubSvg_Uu4N,[data-theme=dark] .instagramSvg_YC40,[data-theme=dark] .linkedinSvg_FCgI,[data-theme=dark] .threadsSvg_PTXY,[data-theme=dark] .xSvg_y3PF{fill:var(--light)}[data-theme=light] .blueskySvg_AzZw,[data-theme=light] .githubSvg_Uu4N,[data-theme=light] .instagramSvg_YC40,[data-theme=light] .linkedinSvg_FCgI,[data-theme=light] .threadsSvg_PTXY,[data-theme=light] .xSvg_y3PF{fill:var(--dark)}.authorSocials_rSDt{align-items:center;display:flex;flex-wrap:wrap;line-clamp:1;-webkit-line-clamp:1;overflow:hidden;-webkit-box-orient:vertical}.authorSocialLink_owbf,.authorSocials_rSDt{line-height:0}.authorSocialLink_owbf{margin-right:.4rem}.authorImage_XqGP{--ifm-avatar-photo-size:3.6rem}.author-as-h1_n9oJ .authorImage_XqGP{--ifm-avatar-photo-size:7rem}.author-as-h2_gXvM .authorImage_XqGP{--ifm-avatar-photo-size:5.4rem}.authorDetails_lV9A{align-items:flex-start;display:flex;flex-direction:column;justify-content:space-around}.authorName_yefp{display:flex;flex-direction:row;font-size:1.1rem;line-height:1.1rem}.author-as-h1_n9oJ .authorName_yefp{display:inline;font-size:2.4rem;line-height:2.4rem}.author-as-h2_gXvM .authorName_yefp{display:inline;font-size:1.4rem;line-height:1.4rem}.authorTitle_nd0D{display:-webkit-box;font-size:.8rem;line-clamp:1;-webkit-line-clamp:1;line-height:1rem;overflow:hidden;-webkit-box-orient:vertical}.author-as-h1_n9oJ .authorTitle_nd0D{font-size:1.2rem;line-height:1.6rem}.author-as-h2_gXvM .authorTitle_nd0D{font-size:1rem;line-height:1.3rem}.authorBlogPostCount_iiJ5{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.8rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.authorCol_Hf19{max-width:inherit!important}.imageOnlyAuthorRow_pa_O{display:flex;flex-flow:row wrap}.imageOnlyAuthorCol_G86a{margin-left:.3rem;margin-right:.3rem}html[data-theme=light]{--heading-shadow:-2px 3px 2px #a2a1a1;--sub-heading-shadow:-1px 1px 1px #a2a1a1}header.heroBanner_qw_p{min-height:calc(100vh - 4rem);overflow:hidden;padding:4rem 1rem 2rem;position:relative;text-align:center}header.heroBanner_qw_p h1.heroTitle_CteR{font-family:Racing Sans One,mono;font-size:8rem;text-shadow:var(--heading-shadow)}header.heroBanner_qw_p h3.heroSubTitle_p8ER{text-shadow:var(--sub-heading-shadow)}header.heroBanner_qw_p img.starButton_Vbnu{opacity:.85;position:absolute;right:1rem;top:1rem;width:12rem}.buttons_LJn2{align-items:center;display:flex;justify-content:center;margin:1.5rem}.dashyDescription_iLL2{font-size:1.2rem;margin:1rem auto;max-width:680px;text-align:left}.dashyDescription_iLL2>p,.dashyDescription_iLL2>span{margin-left:auto;margin-right:auto;max-width:680px}.keepReading_G_Ux{cursor:pointer;font-size:1.2rem;margin-left:.5rem}.scrollDown_cjGa{bottom:0;cursor:pointer;display:flex;left:42%;margin:2rem auto;position:absolute;width:fit-content}.scrollDownText_HIIX{color:var(--text-color);font-size:1.2rem;font-weight:700;margin:0 1rem}.scrollDownIcon_UZ2V{width:2rem}.scrollDownIcon_UZ2V path{fill:var(--text-color)}.sponsorContainer_zSLg{display:flex;flex-direction:column;gap:1rem;margin:1.5rem auto;max-width:680px}.sponsor_JDQ1{background:var(--background);color:var(--text-color);display:flex;flex-direction:column-reverse;gap:.5rem;justify-content:left;padding:.5rem;width:100%}.sponsor_JDQ1 span{font-size:.9rem;text-align:center}.sponsor_JDQ1 span a{color:var(--text-color);font-size:.9rem}.button{border:none;border-radius:4px;color:var(--black);cursor:pointer;display:inline-block;display:flex;font-size:1.3rem;font-weight:700;margin:.5rem;min-width:12rem;padding:.75rem 1rem;position:relative;transition:none}.button svg{margin-right:.5rem;width:1.8rem}.button.btn-small{font-size:1rem;max-width:12rem;min-width:auto;padding:.5rem .75rem;width:100%}.button.btn-small svg{margin-right:.35rem;width:1.2rem}.button.color-btn:hover{color:rgba(0,0,0,.702);top:2px}.button.color-btn:active{box-shadow:none!important;color:rgba(0,0,0,.502);top:4px}.button.color-btn.btn-pink{box-shadow:0 4px var(--accent-pink-dark)}.button.color-btn.btn-pink:hover{box-shadow:0 2px var(--accent-pink-dark)}.button.color-btn.btn-purple{background:var(--accent-purple);box-shadow:0 4px var(--accent-purple-dark)}.button.color-btn.btn-purple:hover{box-shadow:0 2px var(--accent-purple-dark)}.button.color-btn.btn-blue{background:var(--accent-blue);box-shadow:0 4px var(--accent-blue-dark)}.button.color-btn.btn-blue:hover{box-shadow:0 2px var(--accent-blue-dark)}.button.color-btn.btn-green{background:var(--accent-green);box-shadow:0 4px var(--accent-green-dark)}.button.color-btn.btn-green:hover{box-shadow:0 2px var(--accent-green-dark)}.button.color-btn.btn-yellow{background:var(--accent-yellow);box-shadow:0 4px var(--accent-yellow-dark)}.button.color-btn.btn-yellow:hover{box-shadow:0 2px var(--accent-yellow-dark)}.enjoyingWrap_FXPn .enjoyingWrapPart_oGX0,.home-page-features-wrapper .feature .feature-half.assets,.statItem_at6_,.tile_CRAN:hover,main.docs{box-shadow:var(--feature-img-shadow)}main.docs{margin:1rem auto;max-width:1250px;padding:1rem}main.docs h1.docs-title{font-size:5rem;margin:0;text-align:center}main.docs p.see-repo-note{background:rgba(255,255,170,.839);border-radius:3px;display:none;padding:.25rem 1rem;width:fit-content}main.docs p.see-repo-note a,main.docs p.see-repo-note b,main.docs p.see-repo-note span{color:#000;font-size:1rem;font-style:italic}main.docs p.see-repo-note a{color:#000;font-size:1rem;font-weight:700}main.docs div.row1{display:flex;flex-direction:row}main.docs div.docs-links-section{display:flex;flex-wrap:wrap}main.docs div.docs-contents{color:var(--text-color);display:flex;flex-direction:column;justify-content:space-around}main.docs div.docs-contents h3.section-title{margin:1rem .5rem .5rem}main.docs div.docs-contents a{display:flex;flex-direction:column;margin:.5rem .75rem;min-height:5rem;padding:.25rem .5rem;width:calc(33% - 1.5rem)}main.docs div.docs-contents a p.name{font-size:1.4rem;margin:0;text-align:left}main.docs div.docs-contents a span.description{font-size:1rem;text-align:left;white-space:pre-wrap}.color-pink{--feature-color:var(--accent-pink)}.color-blue{--feature-color:var(--accent-blue)}.color-green{--feature-color:var(--accent-green)}.color-yellow{--feature-color:var(--accent-yellow)}.color-white{--feature-color:#fff}html[data-theme=light] .color-pink{--feature-color:var(--accent-pink-dark)}html[data-theme=light] .color-blue{--feature-color:var(--accent-purple-dark)}html[data-theme=light] .color-green{--feature-color:var(--accent-green-dark)}html[data-theme=light] .color-yellow{--feature-color:var(--accent-yellow-dark)}html[data-theme=light] .color-white{--feature-color:#000}.home-page-features-wrapper{display:flex;flex-direction:column;font-size:1.2rem}.home-page-features-wrapper .feature{align-items:center;display:flex;justify-content:center;margin:0;padding:.5rem}.home-page-features-wrapper .feature.align-left{background:var(--background);box-shadow:0 1px 4px #000;flex-direction:row}.home-page-features-wrapper .feature.align-right{background:var(--hero-background);box-shadow:0 -1px 4px rgba(0,0,0,.702);flex-direction:row-reverse}.home-page-features-wrapper .feature .feature-half{margin:1rem;padding:.25rem;width:40%}.home-page-features-wrapper .feature .feature-half.assets{align-items:center;background:var(--feature-color);border-radius:8px;display:flex;height:fit-content;justify-content:center;margin:.5rem}.home-page-features-wrapper .feature .feature-half.assets img.demo,.home-page-features-wrapper .feature .feature-half.assets span.not-demo{background:var(--kinda-transparent);border-radius:8px;box-shadow:var(--feature-img-shadow);height:100%;max-height:36rem;min-height:12rem;object-fit:contain;width:100%}.home-page-features-wrapper .feature .feature-title{align-items:flex-end;color:var(--feature-color);display:flex;margin:1rem 0}.home-page-features-wrapper .feature .feature-title svg{margin-right:.5rem;width:2rem}.home-page-features-wrapper .feature div.read-the-docs a.button-link-wrapper{margin:0 1rem 1rem}.home-page-features-wrapper .feature div.read-the-docs small{margin:0 .5rem;opacity:.75}.authorsSection_t3c7{background:var(--hero-background);padding:3rem 1rem}.inner_rVjr{margin:.5rem auto;width:80%}.heading_j3J0{color:var(--text-color);font-size:2rem}.enjoyingWrap_FXPn{display:flex;gap:2rem;margin-bottom:2rem}.enjoyingWrap_FXPn .enjoyingWrapPart_oGX0{background:var(--background);border-radius:8px;padding:.5rem 1rem;width:50%}.enjoyingWrap_FXPn .enjoyingWrapPart_oGX0 p{margin:0 0 .5rem}.enjoyingWrap_FXPn .enjoyingWrapPart_oGX0 .authorInnerWrap_y4gi{align-items:center;display:flex;gap:.75rem}.subHeading_D3Yw{color:var(--text-color);font-size:1.2rem;margin:.25rem 0}.builtBy_GoGF,.name_mmMI{color:var(--mid-grey);font-size:.9rem}.grid_qTnk{display:grid;gap:.75rem;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));list-style:none;margin:0 auto 2rem;max-width:1800px;padding:0}.tile_CRAN{align-items:center;background:var(--background);border-radius:8px;display:flex;flex-direction:column;gap:.4rem;padding:.6rem;position:relative;transition:transform .2s,box-shadow .2s}.ctaSection_oQB2,.statItem_at6_{align-items:center;display:flex}.tile_CRAN:hover{transform:translateY(-2px)}.avatar__HA9{border-radius:50%;height:48px;width:48px}.name_mmMI{max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.star_JoTu{position:absolute;right:.5rem;top:.5rem}.fallback_O_bA{margin:0 auto;max-width:100%}.fallback_O_bA img{height:auto;max-width:100%}.ctaSection_oQB2{flex-direction:row;gap:.5rem;justify-content:center;margin-top:.25rem}.builtBy_GoGF{margin-bottom:1rem}.statsSection_Rc3S{background:var(--hero-background);padding:.25rem 1rem 3rem}.inner_YtnN{display:flex;flex-wrap:wrap;gap:1.5rem;justify-content:space-around;margin:0 auto;max-width:1000px;width:80%}.statItem_at6_{background:var(--background);border-radius:8px;flex:1 1 180px;flex-direction:column;max-width:240px;padding:1.5rem 2rem;text-align:center}.icon_vhSc{height:3rem;margin-bottom:.25rem;width:3rem}.count_Ad5j,.icon_vhSc{color:var(--stat-color)}.count_Ad5j{font-size:2.8rem;font-weight:400;line-height:1.1}.label_wElN{color:var(--mid-grey);font-size:1rem;margin-top:.25rem}.buttonGroup_M5ko button,.codeBlockContainer_Ckt0{background:var(--prism-background-color);color:var(--prism-color)}iframe#dashy-survey{border:none;height:92vh;width:100%}.codeBlockContainer_Ckt0{border-radius:var(--ifm-code-border-radius);box-shadow:var(--ifm-global-shadow-lw);margin-bottom:var(--ifm-leading)}.codeBlock_bY9V{--ifm-pre-background:var(--prism-background-color);margin:0;padding:0}.codeBlockLines_e6Vv{float:left;font:inherit;min-width:100%;padding:var(--ifm-pre-padding)}.codeBlockLinesWithNumbering_o6Pm{display:table;padding:var(--ifm-pre-padding) 0}:where(:root){--docusaurus-highlighted-code-line-bg:#484d5b}:where([data-theme=dark]){--docusaurus-highlighted-code-line-bg:#646464}.theme-code-block-highlighted-line{background-color:var(--docusaurus-highlighted-code-line-bg);display:block;margin:0 calc(var(--ifm-pre-padding)*-1);padding:0 var(--ifm-pre-padding)}.codeLine_lJS_{counter-increment:line-count;display:table-row}.codeLineNumber_Tfdd{background:var(--ifm-pre-background);display:table-cell;left:0;overflow-wrap:normal;padding:0 var(--ifm-pre-padding);position:sticky;text-align:right;width:1%}.codeLineNumber_Tfdd:before{content:counter(line-count);opacity:.4}.codeLineContent_feaV{padding-right:var(--ifm-pre-padding)}.theme-code-block:hover .copyButtonCopied_Vdqa{opacity:1!important}.copyButtonIcons_IEyt{height:1.125rem;position:relative;width:1.125rem}.copyButtonIcon_TrPX,.copyButtonSuccessIcon_cVMy{fill:currentColor;height:inherit;left:0;opacity:inherit;position:absolute;top:0;transition:all var(--ifm-transition-fast) ease;width:inherit}.copyButtonSuccessIcon_cVMy{color:#00d600;left:50%;opacity:0;top:50%;transform:translate(-50%,-50%) scale(.33)}.copyButtonCopied_Vdqa .copyButtonIcon_TrPX{opacity:0;transform:scale(.33)}.copyButtonCopied_Vdqa .copyButtonSuccessIcon_cVMy{opacity:1;transform:translate(-50%,-50%) scale(1);transition-delay:75ms}.wordWrapButtonIcon_b1P5{height:1.2rem;width:1.2rem}.buttonGroup_M5ko{column-gap:.2rem;display:flex;position:absolute;right:calc(var(--ifm-pre-padding)/2);top:calc(var(--ifm-pre-padding)/2)}.buttonGroup_M5ko button{align-items:center;border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-global-radius);display:flex;line-height:0;opacity:0;padding:.4rem;transition:opacity var(--ifm-transition-fast) ease-in-out}.buttonGroup_M5ko button:focus-visible,.buttonGroup_M5ko button:hover{opacity:1!important}.codeBlockContent_QJqH{border-radius:inherit;direction:ltr;position:relative}.codeBlockTitle_OeMC{border-bottom:1px solid var(--ifm-color-emphasis-300);border-top-left-radius:inherit;border-top-right-radius:inherit;font-size:var(--ifm-code-font-size);font-weight:500;padding:.75rem var(--ifm-pre-padding)}.tag_zVej{border:1px solid var(--docusaurus-tag-list-border);transition:border var(--ifm-transition-fast)}.tag_zVej:hover{--docusaurus-tag-list-border:var(--ifm-link-color);-webkit-text-decoration:none;text-decoration:none}.tagRegular_sFm0{border-radius:var(--ifm-global-radius);font-size:90%;padding:.2rem .5rem .3rem}.tagWithCount_h2kH{align-items:center;border-left:0;display:flex;padding:0 .5rem 0 1rem;position:relative}.tagWithCount_h2kH:after,.tagWithCount_h2kH:before{border:1px solid var(--docusaurus-tag-list-border);content:"";position:absolute;top:50%;transition:inherit}.tagWithCount_h2kH:before{border-bottom:0;border-right:0;height:1.18rem;right:100%;transform:translate(50%,-50%) rotate(-45deg);width:1.18rem}.tagWithCount_h2kH:after{border-radius:50%;height:.5rem;left:0;transform:translateY(-50%);width:.5rem}.tagWithCount_h2kH span{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.7rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.details_lb9f{--docusaurus-details-summary-arrow-size:0.38rem;--docusaurus-details-transition:transform 200ms ease;--docusaurus-details-decoration-color:grey}.details_lb9f>summary{cursor:pointer;list-style:none;padding-left:1rem;position:relative}.details_lb9f>summary::-webkit-details-marker{display:none}.details_lb9f>summary:before{border-color:#0000 #0000 #0000 var(--docusaurus-details-decoration-color);border-style:solid;border-width:var(--docusaurus-details-summary-arrow-size);content:"";left:0;position:absolute;top:.45rem;transform:rotate(0);transform-origin:calc(var(--docusaurus-details-summary-arrow-size)/2) 50%;transition:var(--docusaurus-details-transition)}.collapsibleContent_i85q{border-top:1px solid var(--docusaurus-details-decoration-color);margin-top:1rem;padding-top:1rem}.tag_QGVx{display:inline-block;margin:0 .4rem .5rem 0}.iconEdit_Z9Sw{margin-right:.3em;vertical-align:sub}.details_b_Ee{--docusaurus-details-decoration-color:var(--ifm-alert-border-color);--docusaurus-details-transition:transform var(--ifm-transition-fast) ease;border:1px solid var(--ifm-alert-border-color);margin:0 0 var(--ifm-spacing-vertical)}.lastUpdated_JAkA{font-size:smaller;font-style:italic;margin-top:.2rem}.tocCollapsibleButton_TO0P{align-items:center;display:flex;font-size:inherit;justify-content:space-between;padding:.4rem .8rem;width:100%}.tocCollapsibleButton_TO0P:after{background:var(--ifm-menu-link-sublist-icon) 50% 50%/2rem 2rem no-repeat;content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast);width:1.25rem}.tocCollapsibleButtonExpanded_MG3E:after,.tocCollapsibleExpanded_sAul{transform:none}.tocCollapsible_ETCw{background-color:var(--ifm-menu-color-background-active);border-radius:var(--ifm-global-radius);margin:1rem 0}.tocCollapsibleContent_vkbj>ul{border-left:none;border-top:1px solid var(--ifm-color-emphasis-300);font-size:15px;padding:.2rem 0}.tocCollapsibleContent_vkbj ul li{margin:.4rem .8rem}.containsTaskList_mC6p{list-style:none}.admonition_xJq3{margin-bottom:1em}.admonitionHeading_Gvgb{font:var(--ifm-heading-font-weight) var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);text-transform:uppercase}.admonitionHeading_Gvgb:not(:last-child){margin-bottom:.3rem}.admonitionHeading_Gvgb code{text-transform:none}.admonitionIcon_Rf37{display:inline-block;margin-right:.4em;vertical-align:middle}.admonitionIcon_Rf37 svg{display:inline-block;fill:var(--ifm-alert-foreground-color);height:1.6em;width:1.6em}.tableOfContents_bqdL{overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 1rem)}.breadcrumbHomeIcon_YNFT{height:1.1rem;position:relative;top:1px;vertical-align:top;width:1.1rem}.breadcrumbsContainer_Z_bl{--ifm-breadcrumb-size-multiplier:0.8;margin-bottom:.8rem}@media (min-width:400px){.scalar-app .container{max-width:400px}}@media (min-width:600px){.introduction-card-row[data-v-5764c94a]{flex-flow:row wrap}.introduction-card-row[data-v-5764c94a]>*{min-width:min-content}.scalar-app .container{max-width:600px}.scalar-app .sm\:not-sr-only{clip-path:none;height:auto;margin:0;overflow:visible;padding:0;position:static;white-space:normal;width:auto}.scalar-app .sm\:order-none{order:0}.scalar-app .sm\:mr-1\.5{margin-right:6px}.scalar-app .sm\:mb-1\.5{margin-bottom:6px}.scalar-app .sm\:ml-1\.5{margin-left:6px}.scalar-app .sm\:flex{display:flex}.scalar-app .sm\:hidden{display:none}.scalar-app .sm\:max-w-max{max-width:max-content}.scalar-app .sm\:min-w-max{min-width:max-content}.scalar-app .sm\:flex-col{flex-direction:column}.scalar-app .sm\:flex-row{flex-direction:row}.scalar-app .sm\:justify-between{justify-content:space-between}.scalar-app .sm\:gap-px{gap:1px}.scalar-app .sm\:rounded{border-radius:var(--scalar-radius)}.scalar-app .sm\:rounded-lg{border-radius:var(--scalar-radius-lg)}.scalar-app .sm\:px-2{padding-inline:8px}.scalar-app .sm\:px-3{padding-inline:12px}.scalar-app .sm\:py-1\.5{padding-block:6px}:is(.scalar-app .sm\:\*\:rounded-lg>*){border-radius:var(--scalar-radius-lg)}}@media (min-width:800px){.scalar-app .md\:w-\[calc\(100vw-16px\)\]{width:calc(100vw - 16px)}.full-size-styles[data-v-c36b47da]{width:50dvw!important}.scalar-app .container{max-width:800px}.scalar-app .md\:right-10{right:40px}.scalar-app .md\:bottom-10{bottom:40px}.scalar-app .md\:mx-auto{margin-inline:auto}.scalar-app .md\:-ml-1\.25{margin-left:-5px}.scalar-app .md\:ml-1\.5{margin-left:6px}.scalar-app .md\:block{display:block}.scalar-app .md\:flex{display:flex}.scalar-app .md\:grid{display:grid}.scalar-app .md\:w-full{width:100%}.scalar-app .md\:max-w-180,.scalar-app .md\:max-w-\[720px\]{max-width:720px}.scalar-app .md\:min-w-fit{min-width:fit-content}.scalar-app .md\:flex-none{flex:none}.scalar-app .md\:translate-x-0{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.scalar-app .md\:translate-y-0{--tw-translate-y:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.scalar-app .md\:grid-cols-\[1fr_720px_1fr\]{grid-template-columns:1fr 720px 1fr}.scalar-app .md\:flex-row{flex-direction:row}.scalar-app .md\:border-r{border-right-style:var(--tw-border-style);border-right-width:var(--scalar-border-width)}.scalar-app .md\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.scalar-app .md\:p-1\.5{padding:6px}.scalar-app .md\:px-0{padding-inline:0}.scalar-app .md\:px-1\.5{padding-inline:6px}.scalar-app .md\:px-2{padding-inline:8px}.scalar-app .md\:px-2\.5{padding-inline:10px}.scalar-app .md\:px-4{padding-inline:16px}.scalar-app .md\:px-\[18px\]{padding-inline:18px}.scalar-app .md\:py-2\.5{padding-block:10px}.scalar-app .md\:pb-2\.5{padding-bottom:10px}.scalar-app .md\:pb-\[37px\]{padding-bottom:37px}.scalar-app .md\:pl-0{padding-left:0}:is(.scalar-app .md\:\*\:border-t-0>*){border-top-style:var(--tw-border-style);border-top-width:0}.sidebar-mask[data-v-7aa06e5a]{-webkit-mask-image:linear-gradient(0,#0000 0,#0000 0,var(--scalar-background-2) 30px);mask-image:linear-gradient(0,#0000 0,#0000 0,var(--scalar-background-2) 30px)}.has-import-url,.has-no-import-url{contain:paint;max-width:100dvw;overflow-x:hidden}.has-no-import-url .scalar-client>main{animation:.3s ease-in-out forwards n;background:var(--scalar-background-1);opacity:1}.has-import-url .scalar-client>main{animation:.3s ease-in-out forwards m;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:12px;opacity:0;overflow:hidden;transform:scale(.85) translate(calc(50dvw + 80px));z-index:10000}.has-import-url .scalar-client .sidenav{display:none}.has-import-url .scalar-app,.has-no-import-url .scalar-app{background:var(--scalar-background-1)!important}}@media not all and (min-width:800px){.scalar-app .max-md\:absolute\!{position:absolute!important}.scalar-app .max-md\:w-full\!{width:100%!important}}@media (min-width:997px){.collapseSidebarButton_PEFL,.expandButton_TmdG{background-color:var(--docusaurus-collapse-button-bg)}:root{--docusaurus-announcement-bar-height:30px}.announcementBarClose_gvF7,.announcementBarPlaceholder_vyr4{flex-basis:50px}.collapseSidebarButton_PEFL{border:1px solid var(--ifm-toc-border-color);border-radius:0;bottom:0;display:block!important;height:40px;position:sticky}.collapseSidebarButtonIcon_kv0_{margin-top:4px;transform:rotate(180deg)}.expandButtonIcon_i1dp,[dir=rtl] .collapseSidebarButtonIcon_kv0_{transform:rotate(0)}.collapseSidebarButton_PEFL:focus,.collapseSidebarButton_PEFL:hover,.expandButton_TmdG:focus,.expandButton_TmdG:hover{background-color:var(--docusaurus-collapse-button-bg-hover)}.menuHtmlItem_M9Kj{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu_SIkG{flex-grow:1;padding:.5rem}@supports (scrollbar-gutter:stable){.menu_SIkG{padding:.5rem 0 .5rem .5rem;scrollbar-gutter:stable}}.menuWithAnnouncementBar_GW3s{margin-bottom:var(--docusaurus-announcement-bar-height)}.sidebar_njMd{display:flex;flex-direction:column;height:100%;padding-top:var(--ifm-navbar-height);width:var(--doc-sidebar-width)}.sidebarWithHideableNavbar_wUlq{padding-top:0}.sidebarHidden_VK0M{opacity:0;visibility:hidden}.sidebarLogo_isFc{align-items:center;color:inherit!important;display:flex!important;margin:0 var(--ifm-navbar-padding-horizontal);max-height:var(--ifm-navbar-height);min-height:var(--ifm-navbar-height);-webkit-text-decoration:none!important;text-decoration:none!important}.sidebarLogo_isFc img{height:2rem;margin-right:.5rem}.expandButton_TmdG{align-items:center;display:flex;height:100%;justify-content:center;position:absolute;right:0;top:0;transition:background-color var(--ifm-transition-fast) ease;width:100%}[dir=rtl] .expandButtonIcon_i1dp{transform:rotate(180deg)}.docSidebarContainer_YfHR{border-right:1px solid var(--ifm-toc-border-color);clip-path:inset(0);display:block;margin-top:calc(var(--ifm-navbar-height)*-1);transition:width var(--ifm-transition-fast) ease;width:var(--doc-sidebar-width);will-change:width}.docSidebarContainerHidden_DPk8{cursor:pointer;width:var(--doc-sidebar-hidden-width)}.sidebarViewport_aRkj{height:100%;max-height:100vh;position:sticky;top:0}.docMainContainer_TBSr{flex-grow:1;max-width:calc(100% - var(--doc-sidebar-width))}.docMainContainerEnhanced_lQrH{max-width:calc(100% - var(--doc-sidebar-hidden-width))}.docItemWrapperEnhanced_JWYK{max-width:calc(var(--ifm-container-width) + var(--doc-sidebar-width))!important}.navbarSearchContainer_Bca1{padding:0 var(--ifm-navbar-item-padding-horizontal)}.lastUpdated_JAkA{text-align:right}.tocMobile_ITEo{display:none}.docItemCol_VOVn{max-width:75%!important}}@media (min-width:997px)and (max-width:1200px){nav.navbar .navbar__item.navbar__link{font-size:1rem}nav.navbar .navbar__item.navbar__link svg{display:none}}@media (min-width:1000px){.endpoint-content[data-v-55addca4]{grid-auto-flow:column}.scalar-app .lg\:w-\[calc\(100vw-32px\)\]{width:calc(100vw - 32px)}.scalar-app .lg\:w-full{width:100%}.scalar-app .container{max-width:1000px}.scalar-app .lg\:order-0,.scalar-app .lg\:order-none{order:0}.scalar-app .lg\:-mr-1{margin-right:-4px}.scalar-app .lg\:mb-0{margin-bottom:0}.scalar-app .lg\:flex{display:flex}.scalar-app .lg\:min-h-header{min-height:48px}.scalar-app .lg\:w-auto{width:auto}.scalar-app .lg\:max-w-\[580px\]{max-width:580px}.scalar-app .lg\:min-w-\[580px\]{min-width:580px}.scalar-app .lg\:flex-1{flex:1}.scalar-app .lg\:p-0{padding:0}.scalar-app .lg\:p-1{padding:4px}.scalar-app .lg\:px-1{padding-inline:4px}.scalar-app .lg\:px-2\.5{padding-inline:10px}.scalar-app .lg\:pt-1{padding-top:4px}.scalar-app .lg\:pr-24{padding-right:96px}}@media (min-width:1200px){.section-header-wrapper[data-v-465a7a78]{grid-template-columns:repeat(2,1fr)}.scalar-app .xl\:mb-1\.5{margin-bottom:6px}.scalar-app .xl\:gap-12{gap:48px}.scalar-app .xl\:border-r{border-right-style:var(--tw-border-style);border-right-width:var(--scalar-border-width)}.scalar-app .xl\:border-none{--tw-border-style:none;border-style:none}.scalar-app .xl\:first\:ml-auto:first-child{margin-left:auto}.scalar-app .container{max-width:1200px}.scalar-app .xl\:\!flex{display:flex!important}.scalar-app .xl\:flex{display:flex}.scalar-app .xl\:hidden{display:none}.scalar-app .xl\:h-fit{height:fit-content}.scalar-app .xl\:h-full{height:100%}.scalar-app .xl\:min-h-header{min-height:48px}.scalar-app .xl\:max-w-\[720px\]{max-width:720px}.scalar-app .xl\:min-w-0{min-width:0}.scalar-app .xl\:min-w-\[720px\]{min-width:720px}.scalar-app .xl\:flex-row{flex-direction:row}.scalar-app .xl\:overflow-auto{overflow:auto}.scalar-app .xl\:overflow-hidden{overflow:hidden}.scalar-app .xl\:rounded-none{border-radius:0}.scalar-app .xl\:pr-0\.5{padding-right:2px}.scalar-app .xl\:pl-2{padding-left:8px}:is(.scalar-app .\*\:xl\:border-t-0>*){border-top-style:var(--tw-border-style);border-top-width:0}:is(.scalar-app .\*\:xl\:border-l>*){border-left-style:var(--tw-border-style);border-left-width:var(--scalar-border-width)}:is(.scalar-app .\*\:first\:xl\:border-l-0>*):first-child{border-left-style:var(--tw-border-style);border-left-width:0}}@media screen and (min-width:1200px){.sponsorContainer_zSLg{flex-direction:row;justify-content:center;max-width:1000px}.sponsor_JDQ1{flex:1 1 0;min-height:100%}}@media (min-width:1440px){.container{max-width:var(--ifm-container-width-xl)}}@media (min-width:96rem){.scalar-app .container{max-width:96rem}}@media (max-width:1000px){.agent-scalar-container[data-v-b45a83b1]{width:100%}.agent-scalar[data-v-b45a83b1]{border-radius:var(--scalar-radius-lg) var(--scalar-radius-lg) 0 0;bottom:0;height:calc(100dvh - 50px);top:auto;width:100%;z-index:12}.scalar-app-exit[data-v-b45a83b1]{z-index:11}.references-layout[data-v-94d6fca6]{--refs-sidebar-height:calc(var(--full-height, 100dvh) - var(--scalar-custom-header-height, 0px));grid-template-areas:"c" "d" "g" "h";grid-template-columns:100%;grid-template-rows:var(--scalar-header-height,0) 0 auto auto}.references-editable[data-v-94d6fca6]{grid-template-areas:"c" "d" "i"}.references-rendered[data-v-94d6fca6]{position:static}.scalar-api-references-standalone-mobile[data-v-94d6fca6]:not(.references-classic){--scalar-header-height:50px}}@media screen and (max-width:1000px){.start[data-v-673f10b6]{overflow:auto;padding:0}}@media (max-width:996px){.col{--ifm-col-width:100%;flex-basis:var(--ifm-col-width);margin-left:0}.footer{--ifm-footer-padding-horizontal:0}.colorModeToggle_DEke,.footer__link-separator,.navbar__item,.sidebar_re4s,.tableOfContents_bqdL{display:none}.footer__col{margin-bottom:calc(var(--ifm-spacing-vertical)*3)}.footer__link-item{display:block;width:max-content}.hero{padding-left:0;padding-right:0}.navbar>.container,.navbar>.container-fluid{padding:0}.navbar__toggle{display:inherit}.navbar__search-input{width:9rem}.pills--block,.tabs--block{flex-direction:column}.navbarSearchContainer_Bca1{position:absolute;right:var(--ifm-navbar-padding-horizontal)}.docItemContainer_F8PC{padding:0 .3rem}}@media not (max-width:996px){.searchBar_RVTs.searchBarLeft_MXDe .dropdownMenu_qbY6{left:0!important;right:auto!important}}@media only screen and (max-width:996px){.searchQueryColumn_q7nx{max-width:60%!important}.searchContextColumn_oWAF{max-width:40%!important}}@media (max-width:966px){.timeline_WTQQ{padding-left:2rem;width:100%}.commitEntry_JtMn:before,.releaseEntry_ZG2V:before,.tagEntry_ORGt:before{left:-2rem}.commitRow_RIZe{flex-wrap:wrap}.commitMsg_XeKn{max-width:100%}.commitMeta_teNK{margin-left:0}.bodyPreview_iKaD{-webkit-line-clamp:2}header.heroBanner_qw_p h1.heroTitle_CteR{font-size:6rem}header.heroBanner_qw_p h3.heroSubTitle_p8ER{font-size:1.5rem}.scrollDown_cjGa{display:none}.button{width:80%}.ctaSection_oQB2,.enjoyingWrap_FXPn,.home-page-features-wrapper .feature.align-left,.home-page-features-wrapper .feature.align-right{flex-direction:column}.home-page-features-wrapper .feature.align-left .feature-half,.home-page-features-wrapper .feature.align-right .feature-half{width:95%}.enjoyingWrap_FXPn .enjoyingWrapPart_oGX0,.inner_rVjr{width:100%}.grid_qTnk{grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}.inner_YtnN{display:grid;gap:1rem;grid-template-columns:1fr 1fr;width:100%}.statItem_at6_{max-width:none;padding:1.25rem 1rem}.count_Ad5j{font-size:2.2rem}}@media screen and (max-width:966px){header.heroBanner_qw_p{padding:2rem}.buttons_LJn2{flex-direction:column}}@media screen and (max-width:800px){.sidebar-active-hide-layout[data-v-02af05d1],.sidebar-active-hide-layout[data-v-57ae0d10]{display:none}.sidebar-active-width[data-v-02af05d1],.sidebar-active-width[data-v-57ae0d10]{width:100%}}@media (max-width:768px){.glitchCode_Pt6F{font-size:6rem}}@media (max-width:720px){:root{--scalar-heading-1:24px;--scalar-page-description:20px}}@media (max-width:720px)and (max-height:480px){.scalar-app .zoomed\:\!whitespace-normal{white-space:normal!important}.scalar-modal-layout .scalar-modal[data-v-c36b47da]{margin-top:5svh;max-height:90svh}.scalar-app .zoomed\:static{position:static}.scalar-app .zoomed\:p-1{padding:4px}.scalar .scalar-app-layout[data-v-6118685a]{height:100%;max-height:90svh}}@media (max-width:600px){.banner_woPo{font-size:.85rem;padding:.4rem .75rem}.closeBtn_fC0A{font-size:1.1rem;right:.5rem}.operation-example-card[data-v-55addca4]{max-height:unset;position:static}.examples[data-v-3c3c5405]>*{max-height:unset}.introduction-card-row[data-v-5764c94a]>*{max-width:100%}}@media screen and (max-width:600px){.references-classic .client-libraries[data-v-6a49c111]{flex-direction:column}.start-item[data-v-673f10b6],.start-section-colors .start-item[data-v-673f10b6]{border-radius:0;border-right:none;border-top:1px solid var(--scalar-border-color);width:100%}.start-item[data-v-673f10b6]:empty{display:none}.start-h2[data-v-673f10b6]{border-bottom:none}.start li[data-v-673f10b6]{width:100%}.start-copy[data-v-673f10b6]{padding:48px 0 24px}}@media (max-width:576px){.markdown h1:first-child{--ifm-h1-font-size:2rem}.markdown>h2{--ifm-h2-font-size:1.5rem}.markdown>h3{--ifm-h3-font-size:1.25rem}.navbar__search-input:not(:focus){width:2rem}.searchBar_RVTs .dropdownMenu_qbY6{max-width:calc(100vw - var(--ifm-navbar-padding-horizontal)*2);width:var(--search-local-modal-width-sm,340px)}.searchBarContainer_NW3z:not(.focused_OWtg) .searchClearButton_qk4g,.searchHintContainer_Pkmr{display:none}.title_f1Hy{font-size:2rem}}@media screen and (max-width:576px){.searchQueryColumn_q7nx{max-width:100%!important}.searchContextColumn_oWAF{max-width:100%!important;padding-left:var(--ifm-spacing-horizontal)!important}}@media (max-width:500px){main.docs div.docs-contents a{width:100%}}@media (max-width:460px){:root{--scalar-font-size-1:22px;--scalar-font-size-2:14px;--scalar-font-size-3:12px}}@media screen and (max-width:450px){.client-libraries[data-v-6a49c111]:nth-of-type(4),.client-libraries[data-v-6a49c111]:nth-of-type(5){display:none}}@media (color-gamut:p3){.light-mode{--scalar-color-accent:color(display-p3 0 .6 1);--scalar-color-green:color(display-p3 .023529 .564706 .380392);--scalar-color-red:color(display-p3 .937255 0 .023529);--scalar-color-yellow:color(display-p3 .929412 .745098 .12549);--scalar-color-blue:color(display-p3 0 .509804 .815686);--scalar-color-orange:color(display-p3 1 .4 .02);--scalar-color-purple:color(display-p3 .321569 .011765 .819608)}.dark-mode{--scalar-color-accent:color(display-p3 .07 .67 1);--scalar-color-green:color(display-p3 0 .713725 .282353);--scalar-color-red:color(display-p3 .862745 .105882 .098039);--scalar-color-yellow:color(display-p3 1 .788235 .05098);--scalar-color-blue:color(display-p3 .305882 .701961 .92549);--scalar-color-orange:color(display-p3 1 .552941 .301961);--scalar-color-purple:color(display-p3 .694118 .568627 .976471)}}@media (hover:hover){.backToTopButton_sjWU:hover{background-color:var(--ifm-color-emphasis-300)}.scalar-app .hover\:brightness-90:hover{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);--tw-brightness:brightness(90%)}.scalar-app .hover\:brightness-75:hover,.scalar-app .hover\:brightness-90:hover{filter:var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, )}.scalar-app .group-hover\/heading\:opacity-100:is(:where(.group\/heading):hover *),.scalar-app .group-hover\/parameter-item\:opacity-100:is(:where(.group\/parameter-item):hover *),.scalar-app .group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.scalar-app .hover\:opacity-100:hover,.scalar-app .peer-hover\/button\:opacity-100:is(:where(.peer\/button):hover~*){opacity:1}.scalar-app .group-hover\/button\:opacity-0:is(:where(.group\/button):hover *){opacity:0}.scalar-app .peer-hover\/button\:opacity-100:is(:where(.peer\/button):hover~*){opacity:1}.scalar-app .hover\:opacity-100:hover{opacity:1}.scalar-app .group-hover\/button\:bg-sidebar-indent-border-hover:is(:where(.group\/button):hover *){background-color:var(--scalar-sidebar-indent-border-hover,var(--scalar-border-color))}.scalar-app .group-hover\/button\:text-c-1:is(:where(.group\/button):hover *){color:var(--scalar-color-1)}.scalar-app .hover\:bg-h-btn:hover{background-color:var(--scalar-button-1-hover)}.scalar-app .hover\:bg-sidebar-b-1:hover{background-color:var(--scalar-sidebar-background-1,var(--scalar-background-1))}.scalar-app .hover\:bg-sidebar-b-hover:hover{background-color:var(--scalar-sidebar-item-hover-background,var(--scalar-background-2))}.scalar-app .hover\:bg-linear-to-b:hover{--tw-gradient-position:to bottom;background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .hover\:bg-linear-to-t:hover{--tw-gradient-position:to top;background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .hover\:text-sidebar-c-1:hover{color:var(--scalar-sidebar-color-1,var(--scalar-color-1))}.scalar-app .hover\:text-sidebar-c-hover:hover{color:var(--scalar-sidebar-item-hover-color,var(--scalar-sidebar-color-2))}.scalar-app .dark\:hover\:bg-b-3:where(.dark-mode,.dark-mode *):hover{background-color:var(--scalar-background-3)}.scalar-app .dark\:hover\:bg-linear-to-b:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to bottom;background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .dark\:hover\:bg-linear-to-t:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to top;background-image:linear-gradient(var(--tw-gradient-stops))}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .hover\:bg-linear-to-b:hover{--tw-gradient-position:to bottom in oklab}.scalar-app .hover\:bg-linear-to-t:hover{--tw-gradient-position:to top in oklab}.scalar-app .dark\:hover\:bg-linear-to-b:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to bottom in oklab}.scalar-app .dark\:hover\:bg-linear-to-t:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to top in oklab}}.scalar-app .group-hover\:block:is(:where(.group):hover *){display:block}.scalar-app .group-hover\:flex:is(:where(.group):hover *){display:flex}.scalar-app .group-hover\:hidden:is(:where(.group):hover *){display:none}.scalar-app .group-hover\:inline:is(:where(.group):hover *){display:inline}.scalar-app .group-hover\:pr-5:is(:where(.group):hover *){padding-right:20px}.scalar-app .group-hover\:pr-6:is(:where(.group):hover *){padding-right:24px}.scalar-app .group-hover\:pr-10:is(:where(.group):hover *){padding-right:40px}.scalar-app .group-hover\:text-c-1:is(:where(.group):hover *){color:var(--scalar-color-1)}.scalar-app .group-hover\:opacity-80:is(:where(.group):hover *){opacity:.8}.scalar-app .group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.scalar-app .group-hover\/auth\:absolute:is(:where(.group\/auth):hover *){position:absolute}.scalar-app .group-hover\/auth\:h-auto:is(:where(.group\/auth):hover *){height:auto}.scalar-app .group-hover\/auth\:border-b:is(:where(.group\/auth):hover *){border-bottom-style:var(--tw-border-style);border-bottom-width:var(--scalar-border-width)}.scalar-app .group-hover\/cell\:opacity-100:is(:where(.group\/cell):hover *){opacity:1}.scalar-app .group-hover\/item\:flex:is(:where(.group\/item):hover *){display:flex}.scalar-app .group-hover\/item\:opacity-100:is(:where(.group\/item):hover *),.scalar-app .group-hover\/params\:opacity-100:is(:where(.group\/params):hover *){opacity:1}.scalar-app .group-hover\/row\:flex:is(:where(.group\/row):hover *){display:flex}.scalar-app .group-hover\/scopes-accordion\:text-c-2:is(:where(.group\/scopes-accordion):hover *){color:var(--scalar-color-2)}.scalar-app .group-hover\/upload\:block:is(:where(.group\/upload):hover *){display:block}:is(.scalar-app .group-hover\/auth\:\*\:first\:line-clamp-none:is(:where(.group\/auth):hover *)>*):first-child{-webkit-line-clamp:unset;-webkit-box-orient:horizontal;display:block;overflow:visible}.scalar-app .hover\:cursor-default:hover{cursor:default}.scalar-app .hover\:border-\(--scalar-background-3\):hover{border-color:var(--scalar-background-3)}.scalar-app .hover\:border-inherit:hover{border-color:inherit}.scalar-app .hover\:bg-b-2:hover{background-color:var(--scalar-background-2)}.scalar-app .hover\:bg-b-3:hover{background-color:var(--scalar-background-3)}.scalar-app .hover\:bg-c-accent\/20:hover{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .hover\:bg-c-accent\/20:hover{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .hover\:bg-c-accent\/20:hover{background-color:color-mix(in oklab,var(--scalar-color-accent) 20%,#0000)}}}.scalar-app .hover\:bg-inherit:hover{background-color:inherit}.scalar-app .hover\:bg-sidebar-b-active:hover{background-color:var(--scalar-sidebar-item-active-background,var(--scalar-background-2))}.scalar-app .hover\:whitespace-normal:hover{white-space:normal}.scalar-app .hover\:text-c-1:hover{color:var(--scalar-color-1)}.scalar-app .hover\:text-c-2:hover{color:var(--scalar-color-2)}.scalar-app .hover\:underline:hover{text-decoration-line:underline}.scalar-app .hover\:brightness-75:hover{--tw-brightness:brightness(75%)}.scalar-app .hover\:dark\:bg-b-2:hover:where(.dark-mode,.dark-mode *){background-color:var(--scalar-background-2)}}@media (pointer:fine){.thin-scrollbar{scrollbar-width:thin}.thin-scrollbar::-webkit-scrollbar{height:var(--ifm-scrollbar-size);width:var(--ifm-scrollbar-size)}.thin-scrollbar::-webkit-scrollbar-track{background:var(--ifm-scrollbar-track-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb{background:var(--ifm-scrollbar-thumb-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb:hover{background:var(--ifm-scrollbar-thumb-hover-background-color)}}@media (pointer:coarse){.scalar-app .cm-scroller,.scalar-app .custom-scroll{padding-right:12px}}@media (prefers-color-scheme:dark){#carbonads{--carbon-bg-primary:#1f1f1f;--carbon-bg-secondary:#262626;--carbon-text-color:#e6e6e6}}@media (prefers-reduced-motion:reduce){:root{--ifm-transition-fast:0ms;--ifm-transition-slow:0ms}.skeleton_NVtD{animation:none;opacity:.6}}@media print{.announcementBar_mb4j,.footer,.menu,.navbar,.noPrint_WFHX,.pagination-nav,.table-of-contents,.tocMobile_ITEo{display:none}.tabs{page-break-inside:avoid}.codeBlockLines_e6Vv{white-space:pre-wrap}} \ No newline at end of file diff --git a/assets/css/styles.39682e6f.css b/assets/css/styles.39682e6f.css deleted file mode 100644 index b9cab44c..00000000 --- a/assets/css/styles.39682e6f.css +++ /dev/null @@ -1 +0,0 @@ -@import url(https://fonts.googleapis.com/css2?family=Racing+Sans+One&family=Poppins:wght@300&family=Roboto+Mono:wght@500;300&display=swap);.col,.container{padding:0 var(--ifm-spacing-horizontal);width:100%}.markdown>h2,.markdown>h3,.markdown>h4,.markdown>h5,.markdown>h6{margin-bottom:calc(var(--ifm-heading-vertical-rhythm-bottom)*var(--ifm-leading))}body,ol ol,ol ul,ul ol,ul ul{margin:0}pre,table{overflow:auto}blockquote,pre{margin:0 0 var(--ifm-spacing-vertical)}.breadcrumbs__link,.button{transition-timing-function:var(--ifm-transition-timing-default)}.button,.hash-link{-webkit-user-select:none}.button,code{vertical-align:middle}.button--outline.button--active,.button--outline:active,.button--outline:hover,:root{--ifm-button-color:var(--ifm-font-color-base-inverse)}.menu__link:hover,a{transition:color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.navbar--dark,:root{--ifm-navbar-link-hover-color:var(--ifm-color-primary)}.menu,.navbar-sidebar{overflow-x:hidden}:root,html[data-theme=dark]{--ifm-color-emphasis-500:var(--ifm-color-gray-500)}#carbonads .carbon-poweredby,#carbonads a,#carbonads a:hover{color:var(--carbon-text-color)}.toggleButton_gllP,html{-webkit-tap-highlight-color:transparent}.markdown li,.operation-path[data-v-ec6c8861],.prompt[data-v-e7c7c266],body{word-wrap:break-word}.scalar-app .scale-0,.scalar-app .scale-100,.scalar-app .scale-75{scale:var(--tw-scale-x) var(--tw-scale-y)}.scalar-app .backdrop-blur,.scalar-app .backdrop-filter{backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.close,.home-page-features-wrapper .feature div.read-the-docs{float:right}:root{--ifm-color-scheme:light;--ifm-dark-value:10%;--ifm-darker-value:15%;--ifm-darkest-value:30%;--ifm-light-value:15%;--ifm-lighter-value:30%;--ifm-lightest-value:50%;--ifm-contrast-background-value:90%;--ifm-contrast-foreground-value:70%;--ifm-contrast-background-dark-value:70%;--ifm-contrast-foreground-dark-value:90%;--ifm-color-primary:#3578e5;--ifm-color-secondary:#ebedf0;--ifm-color-success:#00a400;--ifm-color-info:#54c7ec;--ifm-color-warning:#ffba00;--ifm-color-danger:#fa383e;--ifm-color-primary-dark:#306cce;--ifm-color-primary-darker:#2d66c3;--ifm-color-primary-darkest:#2554a0;--ifm-color-primary-light:#538ce9;--ifm-color-primary-lighter:#72a1ed;--ifm-color-primary-lightest:#9abcf2;--ifm-color-primary-contrast-background:#ebf2fc;--ifm-color-primary-contrast-foreground:#102445;--ifm-color-secondary-dark:#d4d5d8;--ifm-color-secondary-darker:#c8c9cc;--ifm-color-secondary-darkest:#a4a6a8;--ifm-color-secondary-light:#eef0f2;--ifm-color-secondary-lighter:#f1f2f5;--ifm-color-secondary-lightest:#f5f6f8;--ifm-color-secondary-contrast-background:#fdfdfe;--ifm-color-secondary-contrast-foreground:#474748;--ifm-color-success-dark:#009400;--ifm-color-success-darker:#008b00;--ifm-color-success-darkest:#007300;--ifm-color-success-light:#26b226;--ifm-color-success-lighter:#4dbf4d;--ifm-color-success-lightest:#80d280;--ifm-color-success-contrast-background:#e6f6e6;--ifm-color-success-contrast-foreground:#003100;--ifm-color-info-dark:#4cb3d4;--ifm-color-info-darker:#47a9c9;--ifm-color-info-darkest:#3b8ba5;--ifm-color-info-light:#6ecfef;--ifm-color-info-lighter:#87d8f2;--ifm-color-info-lightest:#aae3f6;--ifm-color-info-contrast-background:#eef9fd;--ifm-color-info-contrast-foreground:#193c47;--ifm-color-warning-dark:#e6a700;--ifm-color-warning-darker:#d99e00;--ifm-color-warning-darkest:#b38200;--ifm-color-warning-light:#ffc426;--ifm-color-warning-lighter:#ffcf4d;--ifm-color-warning-lightest:#ffdd80;--ifm-color-warning-contrast-background:#fff8e6;--ifm-color-warning-contrast-foreground:#4d3800;--ifm-color-danger-dark:#e13238;--ifm-color-danger-darker:#d53035;--ifm-color-danger-darkest:#af272b;--ifm-color-danger-light:#fb565b;--ifm-color-danger-lighter:#fb7478;--ifm-color-danger-lightest:#fd9c9f;--ifm-color-danger-contrast-background:#ffebec;--ifm-color-danger-contrast-foreground:#4b1113;--ifm-color-white:#fff;--ifm-color-black:#000;--ifm-color-gray-0:var(--ifm-color-white);--ifm-color-gray-100:#f5f6f7;--ifm-color-gray-200:#ebedf0;--ifm-color-gray-300:#dadde1;--ifm-color-gray-400:#ccd0d5;--ifm-color-gray-500:#bec3c9;--ifm-color-gray-600:#8d949e;--ifm-color-gray-700:#606770;--ifm-color-gray-800:#444950;--ifm-color-gray-900:#1c1e21;--ifm-color-gray-1000:var(--ifm-color-black);--ifm-color-emphasis-0:var(--ifm-color-gray-0);--ifm-color-emphasis-100:var(--ifm-color-gray-100);--ifm-color-emphasis-200:var(--ifm-color-gray-200);--ifm-color-emphasis-300:var(--ifm-color-gray-300);--ifm-color-emphasis-400:var(--ifm-color-gray-400);--ifm-color-emphasis-600:var(--ifm-color-gray-600);--ifm-color-emphasis-700:var(--ifm-color-gray-700);--ifm-color-emphasis-800:var(--ifm-color-gray-800);--ifm-color-emphasis-900:var(--ifm-color-gray-900);--ifm-color-emphasis-1000:var(--ifm-color-gray-1000);--ifm-color-content:var(--ifm-color-emphasis-900);--ifm-color-content-inverse:var(--ifm-color-emphasis-0);--ifm-color-content-secondary:#525860;--ifm-background-color:#0000;--ifm-background-surface-color:var(--ifm-color-content-inverse);--ifm-global-border-width:1px;--ifm-global-radius:0.4rem;--ifm-hover-overlay:#0000000d;--ifm-font-color-base:var(--ifm-color-content);--ifm-font-color-base-inverse:var(--ifm-color-content-inverse);--ifm-font-color-secondary:var(--ifm-color-content-secondary);--ifm-font-family-base:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--ifm-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--ifm-font-size-base:100%;--ifm-font-weight-light:300;--ifm-font-weight-normal:400;--ifm-font-weight-semibold:500;--ifm-font-weight-bold:700;--ifm-font-weight-base:var(--ifm-font-weight-normal);--ifm-line-height-base:1.65;--ifm-global-spacing:1rem;--ifm-spacing-vertical:var(--ifm-global-spacing);--ifm-spacing-horizontal:var(--ifm-global-spacing);--ifm-transition-fast:200ms;--ifm-transition-slow:400ms;--ifm-transition-timing-default:cubic-bezier(0.08,0.52,0.52,1);--ifm-global-shadow-lw:0 1px 2px 0 #0000001a;--ifm-global-shadow-md:0 5px 40px #0003;--ifm-global-shadow-tl:0 12px 28px 0 #0003,0 2px 4px 0 #0000001a;--ifm-z-index-dropdown:100;--ifm-z-index-fixed:200;--ifm-z-index-overlay:400;--ifm-container-width:1140px;--ifm-container-width-xl:1320px;--ifm-code-background:#f6f7f8;--ifm-code-border-radius:var(--ifm-global-radius);--ifm-code-font-size:90%;--ifm-code-padding-horizontal:0.1rem;--ifm-code-padding-vertical:0.1rem;--ifm-pre-background:var(--ifm-code-background);--ifm-pre-border-radius:var(--ifm-code-border-radius);--ifm-pre-color:inherit;--ifm-pre-line-height:1.45;--ifm-pre-padding:1rem;--ifm-heading-color:inherit;--ifm-heading-margin-top:0;--ifm-heading-margin-bottom:var(--ifm-spacing-vertical);--ifm-heading-font-family:var(--ifm-font-family-base);--ifm-heading-font-weight:var(--ifm-font-weight-bold);--ifm-heading-line-height:1.25;--ifm-h1-font-size:2rem;--ifm-h2-font-size:1.5rem;--ifm-h3-font-size:1.25rem;--ifm-h4-font-size:1rem;--ifm-h5-font-size:0.875rem;--ifm-h6-font-size:0.85rem;--ifm-image-alignment-padding:1.25rem;--ifm-leading-desktop:1.25;--ifm-leading:calc(var(--ifm-leading-desktop)*1rem);--ifm-list-left-padding:2rem;--ifm-list-margin:1rem;--ifm-list-item-margin:0.25rem;--ifm-list-paragraph-margin:1rem;--ifm-table-cell-padding:0.75rem;--ifm-table-background:#0000;--ifm-table-stripe-background:#00000008;--ifm-table-border-width:1px;--ifm-table-border-color:var(--ifm-color-emphasis-300);--ifm-table-head-background:inherit;--ifm-table-head-color:inherit;--ifm-table-head-font-weight:var(--ifm-font-weight-bold);--ifm-table-cell-color:inherit;--ifm-link-color:var(--ifm-color-primary);--ifm-link-decoration:none;--ifm-link-hover-color:var(--ifm-link-color);--ifm-link-hover-decoration:underline;--ifm-paragraph-margin-bottom:var(--ifm-leading);--ifm-blockquote-font-size:var(--ifm-font-size-base);--ifm-blockquote-border-left-width:2px;--ifm-blockquote-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-blockquote-padding-vertical:0;--ifm-blockquote-shadow:none;--ifm-blockquote-color:var(--ifm-color-emphasis-800);--ifm-blockquote-border-color:var(--ifm-color-emphasis-300);--ifm-hr-background-color:var(--ifm-color-emphasis-500);--ifm-hr-height:1px;--ifm-hr-margin-vertical:1.5rem;--ifm-scrollbar-size:7px;--ifm-scrollbar-track-background-color:#f1f1f1;--ifm-scrollbar-thumb-background-color:silver;--ifm-scrollbar-thumb-hover-background-color:#a7a7a7;--ifm-alert-background-color:inherit;--ifm-alert-border-color:inherit;--ifm-alert-border-radius:var(--ifm-global-radius);--ifm-alert-border-width:0px;--ifm-alert-border-left-width:5px;--ifm-alert-color:var(--ifm-font-color-base);--ifm-alert-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-alert-padding-vertical:var(--ifm-spacing-vertical);--ifm-alert-shadow:var(--ifm-global-shadow-lw);--ifm-avatar-intro-margin:1rem;--ifm-avatar-intro-alignment:inherit;--ifm-avatar-photo-size:3rem;--ifm-badge-background-color:inherit;--ifm-badge-border-color:inherit;--ifm-badge-border-radius:var(--ifm-global-radius);--ifm-badge-border-width:var(--ifm-global-border-width);--ifm-badge-color:var(--ifm-color-white);--ifm-badge-padding-horizontal:calc(var(--ifm-spacing-horizontal)*0.5);--ifm-badge-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-breadcrumb-border-radius:1.5rem;--ifm-breadcrumb-spacing:0.5rem;--ifm-breadcrumb-color-active:var(--ifm-color-primary);--ifm-breadcrumb-item-background-active:var(--ifm-hover-overlay);--ifm-breadcrumb-padding-horizontal:0.8rem;--ifm-breadcrumb-padding-vertical:0.4rem;--ifm-breadcrumb-size-multiplier:1;--ifm-breadcrumb-separator:url('data:image/svg+xml;utf8,');--ifm-breadcrumb-separator-filter:none;--ifm-breadcrumb-separator-size:0.5rem;--ifm-breadcrumb-separator-size-multiplier:1.25;--ifm-button-background-color:inherit;--ifm-button-border-color:var(--ifm-button-background-color);--ifm-button-border-width:var(--ifm-global-border-width);--ifm-button-font-weight:var(--ifm-font-weight-bold);--ifm-button-padding-horizontal:1.5rem;--ifm-button-padding-vertical:0.375rem;--ifm-button-size-multiplier:1;--ifm-button-transition-duration:var(--ifm-transition-fast);--ifm-button-border-radius:calc(var(--ifm-global-radius)*var(--ifm-button-size-multiplier));--ifm-button-group-spacing:2px;--ifm-card-background-color:var(--ifm-background-surface-color);--ifm-card-border-radius:calc(var(--ifm-global-radius)*2);--ifm-card-horizontal-spacing:var(--ifm-global-spacing);--ifm-card-vertical-spacing:var(--ifm-global-spacing);--ifm-toc-border-color:var(--ifm-color-emphasis-300);--ifm-toc-link-color:var(--ifm-color-content-secondary);--ifm-toc-padding-vertical:0.5rem;--ifm-toc-padding-horizontal:0.5rem;--ifm-dropdown-background-color:var(--ifm-background-surface-color);--ifm-dropdown-font-weight:var(--ifm-font-weight-semibold);--ifm-dropdown-link-color:var(--ifm-font-color-base);--ifm-dropdown-hover-background-color:var(--ifm-hover-overlay);--ifm-footer-background-color:var(--ifm-color-emphasis-100);--ifm-footer-color:inherit;--ifm-footer-link-color:var(--ifm-color-emphasis-700);--ifm-footer-link-hover-color:var(--ifm-color-primary);--ifm-footer-link-horizontal-spacing:0.5rem;--ifm-footer-padding-horizontal:calc(var(--ifm-spacing-horizontal)*2);--ifm-footer-padding-vertical:calc(var(--ifm-spacing-vertical)*2);--ifm-footer-title-color:inherit;--ifm-footer-logo-max-width:min(30rem,90vw);--ifm-hero-background-color:var(--ifm-background-surface-color);--ifm-hero-text-color:var(--ifm-color-emphasis-800);--ifm-menu-color:var(--ifm-color-emphasis-700);--ifm-menu-color-active:var(--ifm-color-primary);--ifm-menu-color-background-active:var(--ifm-hover-overlay);--ifm-menu-color-background-hover:var(--ifm-hover-overlay);--ifm-menu-link-padding-horizontal:0.75rem;--ifm-menu-link-padding-vertical:0.375rem;--ifm-menu-link-sublist-icon:url('data:image/svg+xml;utf8,');--ifm-menu-link-sublist-icon-filter:none;--ifm-navbar-background-color:var(--ifm-background-surface-color);--ifm-navbar-height:3.75rem;--ifm-navbar-item-padding-horizontal:0.75rem;--ifm-navbar-item-padding-vertical:0.25rem;--ifm-navbar-link-color:var(--ifm-font-color-base);--ifm-navbar-link-active-color:var(--ifm-link-color);--ifm-navbar-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-navbar-padding-vertical:calc(var(--ifm-spacing-vertical)*0.5);--ifm-navbar-shadow:var(--ifm-global-shadow-lw);--ifm-navbar-search-input-background-color:var(--ifm-color-emphasis-200);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-800);--ifm-navbar-search-input-placeholder-color:var(--ifm-color-emphasis-500);--ifm-navbar-search-input-icon:url('data:image/svg+xml;utf8,');--ifm-navbar-sidebar-width:83vw;--ifm-pagination-border-radius:var(--ifm-global-radius);--ifm-pagination-color-active:var(--ifm-color-primary);--ifm-pagination-font-size:1rem;--ifm-pagination-item-active-background:var(--ifm-hover-overlay);--ifm-pagination-page-spacing:0.2em;--ifm-pagination-padding-horizontal:calc(var(--ifm-spacing-horizontal)*1);--ifm-pagination-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-pagination-nav-border-radius:var(--ifm-global-radius);--ifm-pagination-nav-color-hover:var(--ifm-color-primary);--ifm-pills-color-active:var(--ifm-color-primary);--ifm-pills-color-background-active:var(--ifm-hover-overlay);--ifm-pills-spacing:0.125rem;--ifm-tabs-color:var(--ifm-font-color-secondary);--ifm-tabs-color-active:var(--ifm-color-primary);--ifm-tabs-color-active-border:var(--ifm-tabs-color-active);--ifm-tabs-padding-horizontal:1rem;--ifm-tabs-padding-vertical:1rem}.badge--danger,.badge--info,.badge--primary,.badge--secondary,.badge--success,.badge--warning{--ifm-badge-border-color:var(--ifm-badge-background-color)}.button--link,.button--outline{--ifm-button-background-color:#0000}*,.scalar-app .box-border{box-sizing:border-box}html{background-color:var(--ifm-background-color);color:var(--ifm-font-color-base);color-scheme:var(--ifm-color-scheme);font:var(--ifm-font-size-base)/var(--ifm-line-height-base) var(--ifm-font-family-base);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;-webkit-text-size-adjust:100%;text-size-adjust:100%;--primary:#54bff7;--background:var(--pale-grey);--text-color:var(--black);--hero-background:var(--bright-white);--kinda-transparent:#ffffffd9;--hyperlink:#ff62ce;--feature-img-shadow:4px 4px 6px #a9a9a980,-2px -2px 4px #0006;--footer-color:var(--white);--footer-text-color:var(--black);scroll-behavior:smooth}body{background:var(--background)}iframe{border:0;color-scheme:auto}.container{margin:0 auto;max-width:var(--ifm-container-width)}.container--fluid,.scalar-app .max-w-\[inherit\]{max-width:inherit}.row{display:flex;flex-wrap:wrap;margin:0 calc(var(--ifm-spacing-horizontal)*-1)}.authContent[data-v-8af07610] .markdown,.margin-bottom--none,.margin-vert--none,.markdown>:last-child,.scalar-app .\!mb-0{margin-bottom:0!important}.margin-top--none,.margin-vert--none{margin-top:0!important}.row--no-gutters{margin-left:0;margin-right:0}.margin-horiz--none,.margin-right--none,.scalar-app .\!mr-0{margin-right:0!important}.row--no-gutters>.col{padding-left:0;padding-right:0}.row--align-top,.scalar-app .items-start{align-items:flex-start}.row--align-bottom,.scalar-app .items-end{align-items:flex-end}.left[data-v-bc24f891],.menuExternalLink_NmtK,.row--align-center,.scalar-app .items-center{align-items:center}.row--align-stretch,.scalar-app .items-stretch{align-items:stretch}.row--align-baseline,.scalar-app .items-baseline{align-items:baseline}.col{--ifm-col-width:100%;flex:1 0;margin-left:0;max-width:var(--ifm-col-width)}.padding-bottom--none,.padding-vert--none{padding-bottom:0!important}.padding-top--none,.padding-vert--none,.scalar-app .\!pt-0{padding-top:0!important}.padding-horiz--none,.padding-left--none,[data-v-103d9d56] .cm-gutter+.cm-gutter :not(.cm-foldGutter) .cm-gutterElement,[data-v-776dfdde] .cm-gutter+.cm-gutter :not(.cm-foldGutter) .cm-gutterElement{padding-left:0!important}.padding-horiz--none,.padding-right--none{padding-right:0!important}.col[class*=col--]{flex:0 0 var(--ifm-col-width)}.col--1{--ifm-col-width:8.33333%}.col--offset-1{margin-left:8.33333%}.col--2{--ifm-col-width:16.66667%}.col--offset-2{margin-left:16.66667%}.col--3{--ifm-col-width:25%}.col--offset-3{margin-left:25%}.col--4{--ifm-col-width:33.33333%}.col--offset-4{margin-left:33.33333%}.col--5{--ifm-col-width:41.66667%}.col--offset-5{margin-left:41.66667%}.col--6{--ifm-col-width:50%}.col--offset-6{margin-left:50%}.col--7{--ifm-col-width:58.33333%}.col--offset-7{margin-left:58.33333%}.col--8{--ifm-col-width:66.66667%}.col--offset-8{margin-left:66.66667%}.col--9{--ifm-col-width:75%}.col--offset-9{margin-left:75%}.col--10{--ifm-col-width:83.33333%}.col--offset-10{margin-left:83.33333%}.col--11{--ifm-col-width:91.66667%}.col--offset-11{margin-left:91.66667%}.col--12{--ifm-col-width:100%}.col--offset-12{margin-left:100%}.margin-horiz--none,.margin-left--none{margin-left:0!important}.margin--none,.scalar-app .\!m-0{margin:0!important}.margin-bottom--xs,.margin-vert--xs{margin-bottom:.25rem!important}.margin-top--xs,.margin-vert--xs{margin-top:.25rem!important}.margin-horiz--xs,.margin-left--xs{margin-left:.25rem!important}.margin-horiz--xs,.margin-right--xs{margin-right:.25rem!important}.margin--xs{margin:.25rem!important}.margin-bottom--sm,.margin-vert--sm{margin-bottom:.5rem!important}.margin-top--sm,.margin-vert--sm{margin-top:.5rem!important}.margin-horiz--sm,.margin-left--sm{margin-left:.5rem!important}.margin-horiz--sm,.margin-right--sm{margin-right:.5rem!important}.margin--sm{margin:.5rem!important}.margin-bottom--md,.margin-vert--md{margin-bottom:1rem!important}.margin-top--md,.margin-vert--md{margin-top:1rem!important}.margin-horiz--md,.margin-left--md{margin-left:1rem!important}.margin-horiz--md,.margin-right--md{margin-right:1rem!important}.margin--md{margin:1rem!important}.margin-bottom--lg,.margin-vert--lg{margin-bottom:2rem!important}.margin-top--lg,.margin-vert--lg{margin-top:2rem!important}.margin-horiz--lg,.margin-left--lg{margin-left:2rem!important}.margin-horiz--lg,.margin-right--lg{margin-right:2rem!important}.margin--lg{margin:2rem!important}.margin-bottom--xl,.margin-vert--xl{margin-bottom:5rem!important}.margin-top--xl,.margin-vert--xl{margin-top:5rem!important}.margin-horiz--xl,.margin-left--xl{margin-left:5rem!important}.margin-horiz--xl,.margin-right--xl{margin-right:5rem!important}.margin--xl{margin:5rem!important}.padding--none,.scalar-app .\!p-0{padding:0!important}.padding-bottom--xs,.padding-vert--xs{padding-bottom:.25rem!important}.padding-top--xs,.padding-vert--xs{padding-top:.25rem!important}.padding-horiz--xs,.padding-left--xs{padding-left:.25rem!important}.padding-horiz--xs,.padding-right--xs{padding-right:.25rem!important}.padding--xs{padding:.25rem!important}.padding-bottom--sm,.padding-vert--sm{padding-bottom:.5rem!important}.padding-top--sm,.padding-vert--sm{padding-top:.5rem!important}.padding-horiz--sm,.padding-left--sm{padding-left:.5rem!important}.padding-horiz--sm,.padding-right--sm{padding-right:.5rem!important}.padding--sm{padding:.5rem!important}.padding-bottom--md,.padding-vert--md{padding-bottom:1rem!important}.padding-top--md,.padding-vert--md{padding-top:1rem!important}.padding-horiz--md,.padding-left--md{padding-left:1rem!important}.padding-horiz--md,.padding-right--md{padding-right:1rem!important}.padding--md{padding:1rem!important}.padding-bottom--lg,.padding-vert--lg{padding-bottom:2rem!important}.padding-top--lg,.padding-vert--lg{padding-top:2rem!important}.padding-horiz--lg,.padding-left--lg{padding-left:2rem!important}.padding-horiz--lg,.padding-right--lg{padding-right:2rem!important}.padding--lg{padding:2rem!important}.padding-bottom--xl,.padding-vert--xl{padding-bottom:5rem!important}.padding-top--xl,.padding-vert--xl{padding-top:5rem!important}.padding-horiz--xl,.padding-left--xl{padding-left:5rem!important}.padding-horiz--xl,.padding-right--xl{padding-right:5rem!important}.padding--xl{padding:5rem!important}code{background-color:var(--ifm-code-background);border:.1rem solid #0000001a;border-radius:var(--ifm-code-border-radius);font-family:var(--ifm-font-family-monospace);font-size:var(--ifm-code-font-size);padding:var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal)}a code{color:inherit}pre{background-color:var(--ifm-pre-background);border-radius:var(--ifm-pre-border-radius);color:var(--ifm-pre-color);font:var(--ifm-code-font-size)/var(--ifm-pre-line-height) var(--ifm-font-family-monospace);padding:var(--ifm-pre-padding)}pre code{background-color:initial;border:none;font-size:100%;line-height:inherit;padding:0}kbd{background-color:var(--ifm-color-emphasis-0);border:1px solid var(--ifm-color-emphasis-400);border-radius:.2rem;box-shadow:inset 0 -1px 0 var(--ifm-color-emphasis-400);color:var(--ifm-color-emphasis-800);font:80% var(--ifm-font-family-monospace);padding:.15rem .3rem}h1,h2,h3,h4,h5,h6{color:var(--ifm-heading-color);font-family:var(--ifm-heading-font-family);font-weight:var(--ifm-heading-font-weight);line-height:var(--ifm-heading-line-height);margin:var(--ifm-heading-margin-top) 0 var(--ifm-heading-margin-bottom) 0}article div.markdown h1,h1{font-size:var(--ifm-h1-font-size)}article div.markdown h2,h2{font-size:var(--ifm-h2-font-size)}article div.markdown h3,h3{font-size:var(--ifm-h3-font-size)}h4{font-size:var(--ifm-h4-font-size)}h5{font-size:var(--ifm-h5-font-size)}h6{font-size:var(--ifm-h6-font-size)}.container_lyt7,.container_lyt7>svg,.scalar-app .max-w-\[100\%\],.scalar-app .max-w-full,img{max-width:100%}img[align=right]{padding-left:var(--image-alignment-padding)}img[align=left]{padding-right:var(--image-alignment-padding)}.markdown{--ifm-h1-vertical-rhythm-top:3;--ifm-h2-vertical-rhythm-top:2;--ifm-h3-vertical-rhythm-top:1.5;--ifm-heading-vertical-rhythm-top:1.25;--ifm-h1-vertical-rhythm-bottom:1.25;--ifm-heading-vertical-rhythm-bottom:1}.markdown:after,.markdown:before{content:"";display:table}.markdown:after{clear:both}.markdown h1:first-child{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-h1-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown>h2{--ifm-h2-font-size:2rem;margin-top:calc(var(--ifm-h2-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h3{--ifm-h3-font-size:1.5rem;margin-top:calc(var(--ifm-h3-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h4,.markdown>h5,.markdown>h6{margin-top:calc(var(--ifm-heading-vertical-rhythm-top)*var(--ifm-leading))}.markdown>p,.markdown>pre,.markdown>ul{margin-bottom:var(--ifm-leading)}.markdown li>p{margin-top:var(--ifm-list-paragraph-margin)}.markdown li+li{margin-top:var(--ifm-list-item-margin)}ol,ul{margin:0 0 var(--ifm-list-margin);padding-left:var(--ifm-list-left-padding)}.scalar-app .markdown ol ol ol,.scalar-app .markdown ol ol ol ol ol ol,.scalar-app .markdown ol ol ol ol ol ol ol ol ol,ol ol,ul ol{list-style-type:lower-roman}.scalar-app .markdown ol ol,.scalar-app .markdown ol ol ol ol ol,.scalar-app .markdown ol ol ol ol ol ol ol ol,ol ol ol,ol ul ol,ul ol ol,ul ul ol{list-style-type:lower-alpha}table{border-collapse:collapse;display:block;margin-bottom:var(--ifm-spacing-vertical)}table thead tr{border-bottom:2px solid var(--ifm-table-border-color)}table thead,table tr:nth-child(2n){background-color:var(--ifm-table-stripe-background)}table tr{background-color:var(--ifm-table-background);border-top:var(--ifm-table-border-width) solid var(--ifm-table-border-color)}table td,table th{border:var(--ifm-table-border-width) solid var(--ifm-table-border-color);padding:var(--ifm-table-cell-padding)}table th{background-color:var(--ifm-table-head-background);color:var(--ifm-table-head-color);font-weight:var(--ifm-table-head-font-weight)}table td{color:var(--ifm-table-cell-color)}strong{font-weight:var(--ifm-font-weight-bold)}a{color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}a:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.button:hover,.text--no-decoration,.text--no-decoration:hover,a:not([href]){-webkit-text-decoration:none;text-decoration:none}p{margin:0 0 var(--ifm-paragraph-margin-bottom)}blockquote{border-left:var(--ifm-blockquote-border-left-width) solid var(--ifm-blockquote-border-color);box-shadow:var(--ifm-blockquote-shadow);color:var(--ifm-blockquote-color);font-size:var(--ifm-blockquote-font-size);padding:var(--ifm-blockquote-padding-vertical) var(--ifm-blockquote-padding-horizontal)}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}hr{background-color:var(--ifm-hr-background-color);border:0;height:var(--ifm-hr-height);margin:var(--ifm-hr-margin-vertical) 0}.shadow--lw{box-shadow:var(--ifm-global-shadow-lw)!important}.shadow--md{box-shadow:var(--ifm-global-shadow-md)!important}.shadow--tl{box-shadow:var(--ifm-global-shadow-tl)!important}.text--primary,.wordWrapButtonEnabled_uzNF .wordWrapButtonIcon_b1P5{color:var(--ifm-color-primary)}.text--secondary{color:var(--ifm-color-secondary)}.text--success{color:var(--ifm-color-success)}.text--info{color:var(--ifm-color-info)}.text--warning{color:var(--ifm-color-warning)}.text--danger{color:var(--ifm-color-danger)}.text--center{text-align:center}.text--left{text-align:left}.text--justify{text-align:justify}.text--right{text-align:right}.scalar-app .capitalize,.start-section-color .start-item[data-v-673f10b6],.tag-name[data-v-1124be5d],.text--capitalize{text-transform:capitalize}.scalar-app .lowercase,.text--lowercase{text-transform:lowercase}.scalar-app .uppercase,.text--uppercase{text-transform:uppercase}.text--light{font-weight:var(--ifm-font-weight-light)}.text--normal{font-weight:var(--ifm-font-weight-normal)}.text--semibold{font-weight:var(--ifm-font-weight-semibold)}.text--bold{font-weight:var(--ifm-font-weight-bold)}.scalar-app .hljs-emphasis,.scalar-app .italic,.scalar-app .markdown em,.text--italic{font-style:italic}.property-detail-truncate>.property-detail-value[data-v-1295f965],.scalar-app .truncate,.text--truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text--break{word-wrap:break-word!important;word-break:break-word!important}.clean-btn{background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:0}.alert,.alert .close{color:var(--ifm-alert-foreground-color)}.clean-list{list-style:none;padding-left:0}.alert--primary{--ifm-alert-background-color:var(--ifm-color-primary-contrast-background);--ifm-alert-background-color-highlight:#3578e526;--ifm-alert-foreground-color:var(--ifm-color-primary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-primary-dark)}.alert--secondary{--ifm-alert-background-color:var(--ifm-color-secondary-contrast-background);--ifm-alert-background-color-highlight:#ebedf026;--ifm-alert-foreground-color:var(--ifm-color-secondary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-secondary-dark)}.alert--success{--ifm-alert-background-color:var(--ifm-color-success-contrast-background);--ifm-alert-background-color-highlight:#00a40026;--ifm-alert-foreground-color:var(--ifm-color-success-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-success-dark)}.alert--info{--ifm-alert-background-color:var(--ifm-color-info-contrast-background);--ifm-alert-background-color-highlight:#54c7ec26;--ifm-alert-foreground-color:var(--ifm-color-info-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-info-dark)}.alert--warning{--ifm-alert-background-color:var(--ifm-color-warning-contrast-background);--ifm-alert-background-color-highlight:#ffba0026;--ifm-alert-foreground-color:var(--ifm-color-warning-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-warning-dark)}.alert--danger{--ifm-alert-background-color:var(--ifm-color-danger-contrast-background);--ifm-alert-background-color-highlight:#fa383e26;--ifm-alert-foreground-color:var(--ifm-color-danger-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-danger-dark)}.alert{--ifm-code-background:var(--ifm-alert-background-color-highlight);--ifm-link-color:var(--ifm-alert-foreground-color);--ifm-link-hover-color:var(--ifm-alert-foreground-color);--ifm-link-decoration:underline;--ifm-tabs-color:var(--ifm-alert-foreground-color);--ifm-tabs-color-active:var(--ifm-alert-foreground-color);--ifm-tabs-color-active-border:var(--ifm-alert-border-color);background-color:var(--ifm-alert-background-color);border:var(--ifm-alert-border-width) solid var(--ifm-alert-border-color);border-left-width:var(--ifm-alert-border-left-width);border-radius:var(--ifm-alert-border-radius);box-shadow:var(--ifm-alert-shadow);padding:var(--ifm-alert-padding-vertical) var(--ifm-alert-padding-horizontal)}.alert__heading{align-items:center;display:flex;font:700 var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);margin-bottom:.5rem;text-transform:uppercase}.alert__icon{display:inline-flex;margin-right:.4em}.alert__icon svg{fill:var(--ifm-alert-foreground-color);stroke:var(--ifm-alert-foreground-color);stroke-width:0}.alert .close{margin:calc(var(--ifm-alert-padding-vertical)*-1) calc(var(--ifm-alert-padding-horizontal)*-1) 0 0;opacity:.75}.alert .close:focus,.alert .close:hover{opacity:1}.alert a{text-decoration-color:var(--ifm-alert-border-color)}.alert a:hover{text-decoration-thickness:2px}.avatar{column-gap:var(--ifm-avatar-intro-margin);display:flex}.avatar__photo{border-radius:50%;display:block;height:var(--ifm-avatar-photo-size);overflow:hidden;width:var(--ifm-avatar-photo-size)}.avatar__photo--sm{--ifm-avatar-photo-size:2rem}.avatar__photo--lg{--ifm-avatar-photo-size:4rem}.avatar__photo--xl{--ifm-avatar-photo-size:6rem}.avatar__intro{display:flex;flex:1 1;flex-direction:column;justify-content:center;text-align:var(--ifm-avatar-intro-alignment)}.badge,.breadcrumbs__item,.breadcrumbs__link,.dropdown>.navbar__link:after,.scalar-app .inline-block,.searchBarContainer_NW3z.searchIndexLoading_EJ1f .searchBarLoadingRing_YnHq{display:inline-block}.avatar__name{font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base)}.avatar__subtitle{margin-top:.25rem}.avatar--vertical{--ifm-avatar-intro-alignment:center;--ifm-avatar-intro-margin:0.5rem;align-items:center;flex-direction:column}.badge{background-color:var(--ifm-badge-background-color);border:var(--ifm-badge-border-width) solid var(--ifm-badge-border-color);border-radius:var(--ifm-badge-border-radius);color:var(--ifm-badge-color);font-size:75%;font-weight:var(--ifm-font-weight-bold);line-height:1;padding:var(--ifm-badge-padding-vertical) var(--ifm-badge-padding-horizontal)}.badge--primary{--ifm-badge-background-color:var(--ifm-color-primary)}.badge--secondary{--ifm-badge-background-color:var(--ifm-color-secondary);color:var(--ifm-color-black)}.breadcrumbs__link,.button.button--secondary.button--outline:not(.button--active):not(:hover){color:var(--ifm-font-color-base)}.badge--success{--ifm-badge-background-color:var(--ifm-color-success)}.badge--info{--ifm-badge-background-color:var(--ifm-color-info)}.badge--warning{--ifm-badge-background-color:var(--ifm-color-warning)}.badge--danger{--ifm-badge-background-color:var(--ifm-color-danger)}.breadcrumbs{margin-bottom:0;padding-left:0}.breadcrumbs__item:not(:last-child):after{background:var(--ifm-breadcrumb-separator) center;content:" ";display:inline-block;filter:var(--ifm-breadcrumb-separator-filter);height:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier));margin:0 var(--ifm-breadcrumb-spacing);opacity:.5;width:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier))}.breadcrumbs__item--active .breadcrumbs__link{background:var(--ifm-breadcrumb-item-background-active);color:var(--ifm-breadcrumb-color-active)}.breadcrumbs__link{border-radius:var(--ifm-breadcrumb-border-radius);font-size:calc(1rem*var(--ifm-breadcrumb-size-multiplier));padding:calc(var(--ifm-breadcrumb-padding-vertical)*var(--ifm-breadcrumb-size-multiplier)) calc(var(--ifm-breadcrumb-padding-horizontal)*var(--ifm-breadcrumb-size-multiplier));transition-duration:var(--ifm-transition-fast);transition-property:background,color}.breadcrumbs__link:any-link:hover,.breadcrumbs__link:link:hover,.breadcrumbs__link:visited:hover,area[href].breadcrumbs__link:hover{background:var(--ifm-breadcrumb-item-background-active);-webkit-text-decoration:none;text-decoration:none}.breadcrumbs--sm{--ifm-breadcrumb-size-multiplier:0.8}.breadcrumbs--lg{--ifm-breadcrumb-size-multiplier:1.2}.button{background-color:var(--ifm-button-background-color);border:var(--ifm-button-border-width) solid var(--ifm-button-border-color);border-radius:var(--ifm-button-border-radius);font-size:calc(.875rem*var(--ifm-button-size-multiplier));font-weight:var(--ifm-button-font-weight);line-height:1.5;padding:calc(var(--ifm-button-padding-vertical)*var(--ifm-button-size-multiplier)) calc(var(--ifm-button-padding-horizontal)*var(--ifm-button-size-multiplier));text-align:center;transition-duration:var(--ifm-button-transition-duration);user-select:none;white-space:nowrap}.button,.button:hover{color:var(--ifm-button-color)}.button--outline{--ifm-button-color:var(--ifm-button-border-color)}.button--outline:hover{--ifm-button-background-color:var(--ifm-button-border-color)}.button--link{--ifm-button-border-color:#0000;color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}.button--link.button--active,.button--link:active,.button--link:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}#carbonads a,.banner_woPo,.button-link-wrapper:hover,.dropdown__link--active,.dropdown__link:hover,.link2_y3x6:hover,.link_ecgS:hover,.menu__link:hover,.navLink_KmGT,.navLink_KmGT:hover,.navbar__brand:hover,.navbar__link--active,.navbar__link:hover,.pagination-nav__link:hover,.pagination__link:hover,.rssLink_aJml,.rssLink_aJml:hover,.sidebarItemLink_mo7H:hover{-webkit-text-decoration:none;text-decoration:none}.button.disabled,.button:disabled,.button[disabled]{opacity:.65;pointer-events:none}.button--sm{--ifm-button-size-multiplier:0.8}.button--lg{--ifm-button-size-multiplier:1.35}.button--block{display:block;width:100%}.button.button--secondary{color:var(--ifm-color-gray-900)}:where(.button--primary){--ifm-button-background-color:var(--ifm-color-primary);--ifm-button-border-color:var(--ifm-color-primary)}:where(.button--primary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-primary-dark);--ifm-button-border-color:var(--ifm-color-primary-dark)}.button--primary.button--active,.button--primary:active{--ifm-button-background-color:var(--ifm-color-primary-darker);--ifm-button-border-color:var(--ifm-color-primary-darker)}:where(.button--secondary){--ifm-button-background-color:var(--ifm-color-secondary);--ifm-button-border-color:var(--ifm-color-secondary)}:where(.button--secondary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-secondary-dark);--ifm-button-border-color:var(--ifm-color-secondary-dark)}.button--secondary.button--active,.button--secondary:active{--ifm-button-background-color:var(--ifm-color-secondary-darker);--ifm-button-border-color:var(--ifm-color-secondary-darker)}:where(.button--success){--ifm-button-background-color:var(--ifm-color-success);--ifm-button-border-color:var(--ifm-color-success)}:where(.button--success):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-success-dark);--ifm-button-border-color:var(--ifm-color-success-dark)}.button--success.button--active,.button--success:active{--ifm-button-background-color:var(--ifm-color-success-darker);--ifm-button-border-color:var(--ifm-color-success-darker)}:where(.button--info){--ifm-button-background-color:var(--ifm-color-info);--ifm-button-border-color:var(--ifm-color-info)}:where(.button--info):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-info-dark);--ifm-button-border-color:var(--ifm-color-info-dark)}.button--info.button--active,.button--info:active{--ifm-button-background-color:var(--ifm-color-info-darker);--ifm-button-border-color:var(--ifm-color-info-darker)}:where(.button--warning){--ifm-button-background-color:var(--ifm-color-warning);--ifm-button-border-color:var(--ifm-color-warning)}:where(.button--warning):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-warning-dark);--ifm-button-border-color:var(--ifm-color-warning-dark)}.button--warning.button--active,.button--warning:active{--ifm-button-background-color:var(--ifm-color-warning-darker);--ifm-button-border-color:var(--ifm-color-warning-darker)}:where(.button--danger){--ifm-button-background-color:var(--ifm-color-danger);--ifm-button-border-color:var(--ifm-color-danger)}:where(.button--danger):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-danger-dark);--ifm-button-border-color:var(--ifm-color-danger-dark)}.button--danger.button--active,.button--danger:active{--ifm-button-background-color:var(--ifm-color-danger-darker);--ifm-button-border-color:var(--ifm-color-danger-darker)}.button-group{display:inline-flex;gap:var(--ifm-button-group-spacing)}.button-group>.button:not(:first-child),.scalar-app .rounded-l-none{border-bottom-left-radius:0;border-top-left-radius:0}.button-group>.button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.button-group--block{display:flex;justify-content:stretch}.button-group--block>.button,.scalar-app .flex-grow,.scalar-app .grow{flex-grow:1}.card{background-color:var(--ifm-card-background-color);border-radius:var(--ifm-card-border-radius);box-shadow:var(--ifm-global-shadow-lw);display:flex;flex-direction:column;overflow:hidden}.card--full-height{height:100%}.card__image{padding-top:var(--ifm-card-vertical-spacing)}.card__image:first-child{padding-top:0}.card__body,.card__footer,.card__header{padding:var(--ifm-card-vertical-spacing) var(--ifm-card-horizontal-spacing)}.card__body:not(:last-child),.card__footer:not(:last-child),.card__header:not(:last-child),.parameter-item:last-of-type .parameter-schema[data-v-be55ce3a],.property-enum-value:last-of-type .property-enum-value-label[data-v-f4b54bdd]{padding-bottom:0}.card__body>:last-child,.card__footer>:last-child,.card__header>:last-child{margin-bottom:0}.card__footer,.scalar-app .mt-auto{margin-top:auto}.table-of-contents{font-size:.8rem;margin-bottom:0;padding:var(--ifm-toc-padding-vertical) 0}.table-of-contents,.table-of-contents ul{list-style:none;padding-left:var(--ifm-toc-padding-horizontal)}.table-of-contents li{margin:var(--ifm-toc-padding-vertical) var(--ifm-toc-padding-horizontal)}.table-of-contents__left-border{border-left:1px solid var(--ifm-toc-border-color)}.table-of-contents__link{color:var(--ifm-toc-link-color);display:block}.table-of-contents__link--active,.table-of-contents__link--active code,.table-of-contents__link:hover,.table-of-contents__link:hover code{color:var(--ifm-color-primary);-webkit-text-decoration:none;text-decoration:none}.close{color:var(--ifm-color-black);font-size:1.5rem;font-weight:var(--ifm-font-weight-bold);line-height:1;opacity:.5;padding:1rem;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.close:hover{opacity:.7}.close:focus,.theme-code-block-highlighted-line .codeLineNumber_Tfdd:before{opacity:.8}.dropdown{display:inline-flex;font-weight:var(--ifm-dropdown-font-weight);position:relative;vertical-align:top}.dropdown--hoverable:hover .dropdown__menu,.dropdown--show .dropdown__menu{opacity:1;pointer-events:all;transform:translateY(-1px);visibility:visible}.dropdown--right .dropdown__menu{left:inherit;right:0}.dropdown--nocaret .navbar__link:after{content:none!important}.dropdown__menu{background-color:var(--ifm-dropdown-background-color);border-radius:var(--ifm-global-radius);box-shadow:var(--ifm-global-shadow-md);left:0;list-style:none;max-height:80vh;min-width:10rem;opacity:0;overflow-y:auto;padding:.5rem;pointer-events:none;position:absolute;top:calc(100% - var(--ifm-navbar-item-padding-vertical) + .3rem);transform:translateY(-.625rem);transition-duration:var(--ifm-transition-fast);transition-property:opacity,transform,visibility;transition-timing-function:var(--ifm-transition-timing-default);visibility:hidden;z-index:var(--ifm-z-index-dropdown)}.sidebar_re4s,.tableOfContents_bqdL{max-height:calc(100vh - var(--ifm-navbar-height) - 2rem)}.menu__caret,.menu__link,.menu__list-item-collapsible{border-radius:.25rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.dropdown__link{border-radius:.25rem;color:var(--ifm-dropdown-link-color);display:block;font-size:.875rem;margin-top:.2rem;padding:.25rem .5rem;white-space:nowrap}.dropdown__link--active,.dropdown__link:hover{background-color:var(--ifm-dropdown-hover-background-color);color:var(--ifm-dropdown-link-color)}.dropdown__link--active,.dropdown__link--active:hover{--ifm-dropdown-link-color:var(--ifm-link-color)}.dropdown>.navbar__link:after{border-color:currentcolor #0000;border-style:solid;border-width:.4em .4em 0;content:"";margin-left:.3em;position:relative;top:2px;transform:translateY(-50%)}.footer{background-color:var(--ifm-footer-background-color);color:var(--ifm-footer-color);padding:var(--ifm-footer-padding-vertical) var(--ifm-footer-padding-horizontal)}.footer--dark{--ifm-footer-background-color:#303846;--ifm-footer-color:var(--ifm-footer-link-color);--ifm-footer-link-color:var(--ifm-color-secondary);--ifm-footer-title-color:var(--ifm-color-white)}.footer__links{margin-bottom:1rem}.footer__link-item{color:var(--ifm-footer-link-color);line-height:2}.footer__link-item:hover{color:var(--ifm-footer-link-hover-color)}.footer__link-separator{margin:0 var(--ifm-footer-link-horizontal-spacing)}.footer__logo{margin-top:1rem;max-width:var(--ifm-footer-logo-max-width)}.footer__title{color:var(--ifm-footer-title-color);font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base);margin-bottom:var(--ifm-heading-margin-bottom)}.menu,.navbar__link{font-weight:var(--ifm-font-weight-semibold)}.footer__item,.property-description[data-v-d5367294]:first-of-type:last-of-type,.property-heading:empty+.property-description[data-v-d5367294]:last-of-type{margin-top:0}.admonitionContent_BuS1>:last-child,.collapsibleContent_i85q p:last-child,.details_lb9f>summary>p:last-child,.footer__items,.operation-details-card-item[data-v-55addca4] .parameter-list-items,.scalar-app .last\:mb-0:last-child,.scalar-app .markdown td>*,.scalar-app .markdown th>*,.scalar-app .markdown>:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):last-child,.scalar-app .mb-0,.searchResultItem_U687>h2,.section-accordion-title[data-v-9419dd23] .section-header{margin-bottom:0}.composition-panel .property.property.property.property--level-0[data-v-84242873],.requestPreview.succeeded[data-v-0eb5f95d],[type=checkbox]{padding:0}.hero{align-items:center;background-color:var(--ifm-hero-background-color);color:var(--ifm-hero-text-color);display:flex;padding:4rem 2rem}.hero--primary{--ifm-hero-background-color:var(--ifm-color-primary);--ifm-hero-text-color:var(--ifm-font-color-base-inverse)}.hero--dark{--ifm-hero-background-color:#303846;--ifm-hero-text-color:var(--ifm-color-white)}.hero__title,.title_f1Hy,h2{font-size:3rem}.hero__subtitle,.scalar-app .markdown h1{font-size:1.5rem}.menu__list{list-style:none;margin:0;padding-left:0}.menu__caret,.menu__link{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu__list .menu__list{flex:0 0 100%;margin-top:.25rem;padding-left:var(--ifm-menu-link-padding-horizontal)}.menu__list-item:not(:first-child){margin-top:.25rem}.menu__list-item--collapsed .menu__list{height:0;overflow:hidden}.details_lb9f[data-collapsed=false].isBrowser_bmU9>summary:before,.details_lb9f[open]:not(.isBrowser_bmU9)>summary:before,.menu__list-item--collapsed .menu__caret:before,.menu__list-item--collapsed .menu__link--sublist:after,.toggle-icon-sun-ray[data-v-60be8692]:nth-of-type(2){transform:rotate(90deg)}.menu__list-item-collapsible{display:flex;flex-wrap:wrap;position:relative}.menu__caret:hover,.menu__link:hover,.menu__list-item-collapsible--active,.menu__list-item-collapsible:hover{background:var(--ifm-menu-color-background-hover)}.menu__list-item-collapsible .menu__link--active,.menu__list-item-collapsible .menu__link:hover{background:none!important}.menu__caret,.menu__link{align-items:center;display:flex}.navbar-sidebar,.navbar-sidebar__backdrop{opacity:0;transition-duration:var(--ifm-transition-fast);transition-timing-function:ease-in-out;top:0;bottom:0;left:0}.menu__link{color:var(--ifm-menu-color);flex:1;line-height:1.25}.menu__link:hover{color:var(--ifm-menu-color)}.menu__caret:before,.menu__link--sublist-caret:after{content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast) linear;width:1.25rem}.menu__link--sublist-caret:after{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem;margin-left:auto;min-width:1.25rem}.menu__link--active,.menu__link--active:hover{color:var(--ifm-menu-color-active)}.navbar__brand,.navbar__link{color:var(--ifm-navbar-link-color)}.menu__link--active:not(.menu__link--sublist){background-color:var(--ifm-menu-color-background-active)}.menu__caret:before{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem}.scalar-app .active\:brightness-90:active,.scalar-app .blur,.scalar-app .brightness-lifted,.scalar-app .filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.authContent[data-v-8af07610],.requestContent[data-v-0eb5f95d]{grid-template-rows:0fr;transition:grid-template-rows .2s ease-out}.navbar--dark,html[data-theme=dark]{--ifm-menu-link-sublist-icon-filter:invert(100%) sepia(94%) saturate(17%) hue-rotate(223deg) brightness(104%) contrast(98%)}.navbar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-navbar-shadow);height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar,.navbar>.container,.navbar>.container-fluid{display:flex}.navbar--fixed-top{position:sticky;top:0;z-index:var(--ifm-z-index-fixed)}.navbar__inner{display:flex;flex-wrap:wrap;justify-content:space-between;width:100%}.navbar__brand{align-items:center;display:flex;margin-right:1rem;min-width:0}.navbar__brand:hover{color:var(--ifm-navbar-link-hover-color)}.announcementBarContent_xLdY,.navbar__title{flex:1 1 auto}.navbar__toggle{display:none;margin-right:.5rem}.navbar__logo{flex:0 0 auto;height:2rem;margin-right:.5rem}.navbar__logo img,.scalar-app .h-full,body,html{height:100%}.navbar__items,.request-card-footer-addon[data-v-cbc307e3]{align-items:center;display:flex;flex:1;min-width:0}.navbar__items--center{flex:0 0 auto}.navbar__items--center .navbar__brand{margin:0}.introduction-card-row[data-v-5764c94a]>*,.navbar__items--center+.navbar__items--right{flex:1}.navbar__items--right{flex:0 0 auto;justify-content:flex-end}.navbar__item{display:inline-block;padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}#nprogress,.navbar__item.dropdown .navbar__link:not([href]),.scalar-app .pointer-events-none,.start-item[data-v-673f10b6]:empty{pointer-events:none}.navbar__link--active,.navbar__link:hover{color:var(--ifm-navbar-link-hover-color)}.navbar--dark,.navbar--primary{--ifm-menu-color:var(--ifm-color-gray-300);--ifm-navbar-link-color:var(--ifm-color-gray-100);--ifm-navbar-search-input-background-color:#ffffff1a;--ifm-navbar-search-input-placeholder-color:#ffffff80;color:var(--ifm-color-white)}.navbar--dark{--ifm-navbar-background-color:#242526;--ifm-menu-color-background-active:#ffffff0d;--ifm-navbar-search-input-color:var(--ifm-color-white)}.navbar--primary{--ifm-navbar-background-color:var(--ifm-color-primary);--ifm-navbar-link-hover-color:var(--ifm-color-white);--ifm-menu-color-active:var(--ifm-color-white);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-500)}.navbar__search-input{appearance:none;background:var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat .75rem center/1rem 1rem;border:none;border-radius:2rem;color:var(--ifm-navbar-search-input-color);cursor:text;display:inline-block;font-size:1rem;height:2rem;padding:0 .5rem 0 2.25rem;width:12.5rem}.navbar__search-input::placeholder{color:var(--ifm-navbar-search-input-placeholder-color)}.navbar-sidebar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-global-shadow-md);position:fixed;transform:translate3d(-100%,0,0);transition-property:opacity,visibility,transform;visibility:hidden;width:var(--ifm-navbar-sidebar-width)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar__items,.toggle-icon-dark .toggle-icon-moon-mask[data-v-60be8692]{transform:translateZ(0)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar--show .navbar-sidebar__backdrop{opacity:1;visibility:visible}.navbar-sidebar__backdrop{background-color:#0009;position:fixed;right:0;transition-property:opacity,visibility;visibility:hidden}.navbar-sidebar__brand{align-items:center;box-shadow:var(--ifm-navbar-shadow);display:flex;flex:1;height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.footer,article div.markdown p img,nav.navbar{box-shadow:var(--feature-img-shadow)}.navbar-sidebar__items{display:flex;height:calc(100% - var(--ifm-navbar-height));transition:transform var(--ifm-transition-fast) ease-in-out}.navbar-sidebar__items--show-secondary{transform:translate3d(calc((var(--ifm-navbar-sidebar-width))*-1),0,0)}.navbar-sidebar__item{flex-shrink:0;padding:.5rem;width:calc(var(--ifm-navbar-sidebar-width))}.navbar-sidebar__back{background:var(--ifm-menu-color-background-active);font-size:15px;font-weight:var(--ifm-button-font-weight);margin:0 0 .2rem -.5rem;padding:.6rem 1.5rem;position:relative;text-align:left;top:-.5rem;width:calc(100% + 1rem)}.navbar-sidebar__close{display:flex;margin-left:auto}.pagination{column-gap:var(--ifm-pagination-page-spacing);display:flex;font-size:var(--ifm-pagination-font-size);padding-left:0}.pagination--sm{--ifm-pagination-font-size:0.8rem;--ifm-pagination-padding-horizontal:0.8rem;--ifm-pagination-padding-vertical:0.2rem}.pagination--lg{--ifm-pagination-font-size:1.2rem;--ifm-pagination-padding-horizontal:1.2rem;--ifm-pagination-padding-vertical:0.3rem}.pagination__item,.property-detail[data-v-1295f965],.scalar-app .inline-flex{display:inline-flex}.pagination__item>span{padding:var(--ifm-pagination-padding-vertical)}.pagination__item--active .pagination__link{color:var(--ifm-pagination-color-active)}.pagination__item--active .pagination__link,.pagination__item:not(.pagination__item--active):hover .pagination__link{background:var(--ifm-pagination-item-active-background)}.pagination__item--disabled,.pagination__item[disabled]{opacity:.25;pointer-events:none}.pagination__link{border-radius:var(--ifm-pagination-border-radius);color:var(--ifm-font-color-base);display:inline-block;padding:var(--ifm-pagination-padding-vertical) var(--ifm-pagination-padding-horizontal);transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav{display:grid;grid-gap:var(--ifm-spacing-horizontal);gap:var(--ifm-spacing-horizontal);grid-template-columns:repeat(2,1fr)}.pagination-nav__link{border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-pagination-nav-border-radius);display:block;height:100%;line-height:var(--ifm-heading-line-height);padding:var(--ifm-global-spacing);transition:border-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav__link:hover{border-color:var(--ifm-pagination-nav-color-hover)}.content_knG7 a,.disclaimerLink[data-v-56f7e8dd],.hitFooter_E9YW a,.link2_y3x6.link2_y3x6,.link2_y3x6.link2_y3x6:hover,.link_ecgS.link2_y3x6,.link_ecgS.link2_y3x6:hover,.suggestion_fB_2.cursor_eG29 mark,.toggleButton[data-v-0eb5f95d]:hover,li a:hover,p a:hover{-webkit-text-decoration:underline;text-decoration:underline}.pagination-nav__link--next{grid-column:2/3;text-align:right}.pagination-nav__label{font-size:var(--ifm-h4-font-size);font-weight:var(--ifm-heading-font-weight);word-break:break-word}.pagination-nav__link--prev .pagination-nav__label:before{content:"« "}.pagination-nav__link--next .pagination-nav__label:after{content:" »"}.pagination-nav__sublabel{color:var(--ifm-color-content-secondary);font-size:var(--ifm-h5-font-size);font-weight:var(--ifm-font-weight-semibold);margin-bottom:.25rem}.pills__item,.tabs{font-weight:var(--ifm-font-weight-bold)}.pills{display:flex;gap:var(--ifm-pills-spacing);padding-left:0}.pills__item{border-radius:.5rem;cursor:pointer;display:inline-block;padding:.25rem 1rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pills__item--active{color:var(--ifm-pills-color-active)}.pills__item--active,.pills__item:not(.pills__item--active):hover{background:var(--ifm-pills-color-background-active)}.pills--block,.scalar-app .justify-stretch,.tabs--block{justify-content:stretch}.pills--block .pills__item{flex-grow:1;text-align:center}.tabs{color:var(--ifm-tabs-color);display:flex;margin-bottom:0;overflow-x:auto;padding-left:0}.tabs__item{border-bottom:3px solid #0000;border-radius:var(--ifm-global-radius);cursor:pointer;display:inline-flex;padding:var(--ifm-tabs-padding-vertical) var(--ifm-tabs-padding-horizontal);transition:background-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.tabs__item--active{border-bottom-color:var(--ifm-tabs-color-active-border);border-bottom-left-radius:0;border-bottom-right-radius:0;color:var(--ifm-tabs-color-active)}.tabs__item:hover{background-color:var(--ifm-hover-overlay)}.tabs--block .tabs__item{flex-grow:1;justify-content:center}html[data-theme=dark]{--ifm-color-scheme:dark;--ifm-color-emphasis-0:var(--ifm-color-gray-1000);--ifm-color-emphasis-100:var(--ifm-color-gray-900);--ifm-color-emphasis-200:var(--ifm-color-gray-800);--ifm-color-emphasis-300:var(--ifm-color-gray-700);--ifm-color-emphasis-400:var(--ifm-color-gray-600);--ifm-color-emphasis-600:var(--ifm-color-gray-400);--ifm-color-emphasis-700:var(--ifm-color-gray-300);--ifm-color-emphasis-800:var(--ifm-color-gray-200);--ifm-color-emphasis-900:var(--ifm-color-gray-100);--ifm-color-emphasis-1000:var(--ifm-color-gray-0);--ifm-background-color:#1b1b1d;--ifm-background-surface-color:#242526;--ifm-hover-overlay:#ffffff0d;--ifm-color-content:#e3e3e3;--ifm-color-content-secondary:#fff;--ifm-breadcrumb-separator-filter:invert(64%) sepia(11%) saturate(0%) hue-rotate(149deg) brightness(99%) contrast(95%);--ifm-code-background:#ffffff1a;--ifm-scrollbar-track-background-color:#444;--ifm-scrollbar-thumb-background-color:#686868;--ifm-scrollbar-thumb-hover-background-color:#7a7a7a;--ifm-table-stripe-background:#ffffff12;--ifm-toc-border-color:var(--ifm-color-emphasis-200);--ifm-color-primary-contrast-background:#102445;--ifm-color-primary-contrast-foreground:#ebf2fc;--ifm-color-secondary-contrast-background:#474748;--ifm-color-secondary-contrast-foreground:#fdfdfe;--ifm-color-success-contrast-background:#003100;--ifm-color-success-contrast-foreground:#e6f6e6;--ifm-color-info-contrast-background:#193c47;--ifm-color-info-contrast-foreground:#eef9fd;--ifm-color-warning-contrast-background:#4d3800;--ifm-color-warning-contrast-foreground:#fff8e6;--ifm-color-danger-contrast-background:#4b1113;--ifm-color-danger-contrast-foreground:#ffebec;--primary:#db78fc;--background:var(--dark-grey);--text-color:var(--white);--hero-background:var(--black);--kinda-transparent:#000000d9;--hyperlink:var(--primary);--feature-img-shadow:4px 4px 6px #00000080,-2px -2px 4px #0006;--footer-color:var(--black);--footer-text-color:var(--white);--heading-shadow:-5px 4px 0px #000;--sub-heading-shadow:-3px 3px 1px #000}:root{--docusaurus-progress-bar-color:var(--ifm-color-primary);--bright-white:#fff;--white:#f7f7f7;--pale-grey:#e9e9e8;--mid-grey:#a9a9a9;--dark-grey:#18191a;--black:#121212;--pitch-black:#000;--info:#35c9fa;--success:#8f8;--warning:#ece715;--danger:#f80363;--accent-green:#41ef90;--accent-green-dark:#1e9554;--accent-blue:#5c85f7;--accent-blue-dark:#3d48dd;--accent-purple:#9083ed;--accent-purple-dark:#6b3cd6;--accent-pink:#db78fc;--accent-pink-dark:#b83ddd;--accent-yellow:#dcff5a;--accent-yellow-dark:#ceb73f;--ifm-color-primary:var(--accent-pink,#ff62ce);--ifm-color-primary-dark:#21af90;--ifm-color-primary-darker:#1fa588;--ifm-color-primary-darkest:#1a8870;--ifm-color-primary-light:#46cbae;--ifm-color-primary-lighter:#66d4bd;--ifm-color-primary-lightest:#92e0d0;--ifm-code-font-size:95%;--ifm-navbar-background-color:var(--hero-background);--ifm-navbar-link-color:var(--text-color);--ifm-breadcrumb-color-active:var(--text-color);--docusaurus-announcement-bar-height:auto;--docusaurus-collapse-button-bg:#0000;--docusaurus-collapse-button-bg-hover:#0000001a;--doc-sidebar-width:300px;--doc-sidebar-hidden-width:30px;--scalar-loaded-api-reference:true;--docusaurus-blog-social-icon-size:1rem;--docusaurus-tag-list-border:var(--ifm-color-emphasis-300)}#nprogress .bar{background:var(--docusaurus-progress-bar-color);height:2px;left:0;position:fixed;top:0;width:100%;z-index:1031}#nprogress .peg{box-shadow:0 0 10px var(--docusaurus-progress-bar-color),0 0 5px var(--docusaurus-progress-bar-color);height:100%;opacity:1;position:absolute;right:0;transform:rotate(3deg) translateY(-4px);width:100px}h1{font-family:Racing Sans One,mono;font-size:4rem}button,h2,h3,h4,h5{font-family:Roboto Mono,monospace}h3{font-size:2rem}button,h4,h5{font-size:1.2rem}a,body,div,p,section,span,ul li{font-family:Poppins,sans-serif;font-size:1.2rem}.link2_y3x6,.link_ecgS,code span{font-family:Roboto Mono,monospace}.hero{--ifm-hero-background-color:var(--hero-background);--ifm-hero-text-color:var(--text-color)}.footer{--ifm-footer-background-color:var(--footer-color);--ifm-footer-color:var(--footer-text-color);--ifm-footer-link-color:var(--footer-text-color);--ifm-footer-title-color:var(--footer-text-color);--ifm-footer-link-hover-color:var(--primary);--ifm-link-color:var(--footer-text-color);--ifm-link-hover-color:var(--primary)}.footer .footer__link-item,.scalar-app .markdown h4,.scalar-app .markdown h5,.scalar-app .markdown h6,.sidebar-ad #carbonads .carbon-text,article div.markdown a,article div.markdown code span,article div.markdown li,article div.markdown ol,article div.markdown p,article div.markdown ul,ul.table-of-contents li a{font-size:1rem}.footer .footer__copyright,.footer .footer__copyright a{font-family:Roboto Mono,monospace;font-size:.9rem;font-weight:700;opacity:.9}.docusaurus-highlight-code-line{background-color:#0000001a;display:block;margin:0 calc(var(--ifm-pre-padding)*-1);padding:0 var(--ifm-pre-padding)}nav.navbar a.navbar__brand .navbar__logo,nav.navbar a.navbar__brand b{transition:.3s}nav.navbar a.navbar__brand:hover .navbar__logo{transform:scale(1.1) rotate(-3deg)}nav.navbar a.navbar__brand:hover b{color:#00eed6}nav.navbar a.navbar__brand b{font-size:1.5rem;font-weight:700}.authorLink_YWtN:hover,nav.navbar .navbar__link--active,nav.navbar .navbar__link:hover{color:var(--primary)}.fetchErrorCard_M8w3 a:hover,.meta_GplF a:hover,.pagination-nav__label,html[data-theme=light] main div.container.padding-top--md.padding-bottom--lg .pagination-nav__label{color:var(--text-color)}li a,li a:hover,p a,p a:hover,ul.table-of-contents li a:hover,ul.table-of-contents li a:hover code{color:var(--hyperlink)}h1,h2,h3,h4,h5{cursor:default}article div.markdown{--ifm-h1-font-size:3.0rem;--ifm-h2-font-size:2.5rem;--ifm-h3-font-size:1.4rem}article div.markdown p img{border-radius:4px}.sponsor_JDQ1,.sponsor_JDQ1 img,article div.markdown table>tr>td>a>img{border-radius:6px}ul.table-of-contents li a.table-of-contents__link--active,ul.table-of-contents li a.table-of-contents__link--active code{color:var(--hyperlink);font-weight:700}.form-group[data-v-43df1726],.form-group[data-v-694018d6],ul.menu__list{margin-bottom:1rem}ul.menu__list li.menu__list-item a.active,ul.menu__list li.menu__list-item a.menu__link--active{color:var(--primary);font-weight:700}code span{font-size:.9rem!important}html[data-theme=light] main div.container.padding-top--md.padding-bottom--lg{padding:0 1rem!important}html[data-theme=light] main div.container.padding-top--md.padding-bottom--lg div>div.col[class*=docItemCol]{background:var(--bright-white);border-right:1px solid var(--ifm-toc-border-color);padding:1rem}html[data-theme=light] main div.container.padding-top--md.padding-bottom--lg div>div.col[class*=docItemCol] ul li a{font-weight:700}.scalar-app .markdown td:first-of-type,.scalar-app .markdown th:first-of-type,html[data-theme=light] main div.container.padding-top--md.padding-bottom--lg .table-of-contents__left-border{border-left:none}#carbonads *{line-height:normal;margin:initial;padding:initial}#carbonads{--carbon-font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",Helvetica,Arial,sans-serif;--carbon-font-size:14px;--carbon-padding:1.5ch;--carbon-max-char:20ch;--carbon-bg-primary:#fafafa;--carbon-bg-secondary:#ebebeb;--carbon-text-color:#333;font-family:var(--carbon-font-family);font-size:var(--carbon-font-size);margin:0 auto;width:fit-content;z-index:10}#carbonads>span{background-color:var(--carbon-bg-primary);box-shadow:0 0 1px rgba(0,0,0,.085),0 0 2px rgba(0,0,0,.085),0 0 4px rgba(0,0,0,.085),0 0 8px rgba(0,0,0,.085);display:flex;flex-direction:column;gap:var(--carbon-padding);max-inline-size:calc(130px + var(--carbon-max-char) + 8ch);min-inline-size:130px;padding:var(--carbon-padding)}#carbonads .carbon-wrap{display:flex;flex-wrap:wrap;gap:1.5ex}#carbonads .carbon-img{flex:0 0 130px}#carbonads .carbon-img img,.cursor_eG29 .hideAction_vcyE>svg,.scalar-card-checkbox .scalar-card-checkbox-input:checked~.scalar-card-checkbox-checkmark[data-v-dc47ca65]:after{display:block}#carbonads .carbon-text{flex-basis:var(--carbon-max-char);flex-grow:1;line-height:1.4;text-align:left}#carbonads .carbon-poweredby{background:var(--carbon-bg-secondary);font-size:.6em;font-weight:600;letter-spacing:.2ch;line-height:1.4;padding:6px 8px;text-align:center;text-transform:uppercase}.sidebar-ad{--carbon-bg-primary:var(--background,#18191a);--carbon-bg-secondary:#282a36;--carbon-text-color:#e6e6e6}.sidebar-ad #carbonads{margin:.5rem}.sidebar-ad #carbonads .carbon-img img,.sidebar-ad .avatar__photo-link{border-radius:5px}.sidebar-ad #carbonads .carbon-wrap{justify-content:center}.sidebar-ad #carbonads>span{box-shadow:none}.sidebar-ad #carbonads .carbon-poweredby{border-radius:5px;font-size:.7rem}::selection{background:var(--primary);color:var(--background);text-shadow:none}@keyframes a{0%{opacity:0;transform:translateY(-100%)}to{opacity:1;transform:translateY(0)}}@keyframes b{0%{opacity:1}to{opacity:0;transform:translateY(-100%)}}.banner_woPo{align-items:center;animation:.3s ease-out a;background:var(--accent-green);color:var(--bright-white);display:flex;flex-wrap:wrap;font-size:.95rem;font-weight:600;gap:.25rem 1.25rem;justify-content:center;padding:.25rem 1rem;position:relative;width:100%;z-index:100}.dismissing_ZrNM{animation:.25s ease-in forwards b}.link2_y3x6,.link_ecgS{color:var(--black);font-size:.95rem;font-weight:400;transition:.2s}.link2_y3x6.link2_y3x6,.link_ecgS.link2_y3x6{opacity:.85}.link2_y3x6.link2_y3x6:hover,.link_ecgS.link2_y3x6:hover{opacity:1;transform:scale(1.03)}.closeBtn_fC0A{background:none;border:none;border-radius:4px;color:var(--black);cursor:pointer;font-size:1.5rem;line-height:1;opacity:.8;padding:0 .25rem .1rem;position:absolute;right:1rem;transition:.2s}.closeBtn_fC0A:hover{background:#1d8c4f;opacity:1}#__docusaurus-base-url-issue-banner-container,.docSidebarContainer_YfHR,.hideAction_vcyE>svg,.icons-only[data-v-b59b0acf] span,.introduction-card-item[data-v-dfab866f]:empty,.introduction-card-row[data-v-5764c94a] .scalar-card:nth-of-type(2) .scalar-card-header,.navbarSearchContainer_Bca1:empty,.scalar-app .empty\:hidden:empty,.sidebarLogo_isFc,.themedComponent_mlkZ,.toggleIcon_g3eP,html[data-announcement-bar-initially-dismissed=true] .announcementBar_mb4j{display:none}.backToTopButton_sjWU{background-color:var(--ifm-color-emphasis-200);border-radius:50%;bottom:1.3rem;box-shadow:var(--ifm-global-shadow-lw);height:3rem;opacity:0;position:fixed;right:1.3rem;transform:scale(0);transition:all var(--ifm-transition-fast) var(--ifm-transition-timing-default);visibility:hidden;width:3rem;z-index:calc(var(--ifm-z-index-fixed) - 1)}.backToTopButton_sjWU:after{background-color:var(--ifm-color-emphasis-1000);content:" ";display:inline-block;height:100%;-webkit-mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;width:100%}.backToTopButtonShow_xfvO{opacity:1;transform:scale(1);visibility:visible}.skipToContent_fXgn{background-color:var(--ifm-background-surface-color);color:var(--ifm-color-emphasis-900);left:100%;padding:calc(var(--ifm-global-spacing)/2) var(--ifm-global-spacing);position:fixed;top:1rem;z-index:calc(var(--ifm-z-index-fixed) + 1)}.skipToContent_fXgn:focus{box-shadow:var(--ifm-global-shadow-md);left:1rem}.closeButton_CVFx{line-height:0;padding:0}.content_knG7{font-size:85%;padding:5px 0;text-align:center}.content_knG7 a{color:inherit}.announcementBar_mb4j{align-items:center;background-color:var(--ifm-color-white);border-bottom:1px solid var(--ifm-color-emphasis-100);color:var(--ifm-color-black);display:flex;height:var(--docusaurus-announcement-bar-height)}.announcementBarPlaceholder_vyr4{flex:0 0 10px}.announcementBarClose_gvF7{align-self:stretch;flex:0 0 30px}.toggle_vylO{height:2rem;width:2rem}.toggleButton_gllP{align-items:center;border-radius:50%;display:flex;height:100%;justify-content:center;transition:background var(--ifm-transition-fast);width:100%}.toggleButton_gllP:hover{background:var(--ifm-color-emphasis-200)}[data-theme-choice=dark] .darkToggleIcon_wfgR,[data-theme-choice=light] .lightToggleIcon_pyhR,[data-theme-choice=system] .systemToggleIcon_QzmC,[data-theme=dark] .themedComponent--dark_xIcU,[data-theme=light] .themedComponent--light_NVdE,html:not([data-theme]) .themedComponent--light_NVdE{display:initial}.scalar-app .cursor-not-allowed,.toggleButtonDisabled_aARS{cursor:not-allowed}.darkNavbarColorModeToggle_X3D1:hover{background:var(--ifm-color-gray-800)}[data-theme=dark]:root{--docusaurus-collapse-button-bg:#ffffff0d;--docusaurus-collapse-button-bg-hover:#ffffff1a}.collapseSidebarButton_PEFL{display:none;margin:0}.categoryLinkLabel_W154,.linkLabel_WmDU{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical}.iconExternalLink_nPIU{margin-left:.3rem}.linkLabel_WmDU{line-clamp:2;-webkit-line-clamp:2}.categoryLink_byQd,.property-detail-truncate[data-v-1295f965],.scalar-app .overflow-hidden{overflow:hidden}.menu__link--sublist-caret:after{margin-left:var(--ifm-menu-link-padding-vertical)}.categoryLinkLabel_W154{flex:1;line-clamp:2;-webkit-line-clamp:2}.docMainContainer_TBSr,.docRoot_UBD9{display:flex;width:100%}.docsWrapper_hBAB{display:flex;flex:1 0 auto}.notFoundPage_Wsy5{align-items:center;background:var(--hero-background);display:flex;justify-content:center;min-height:calc(100vh - 4rem);overflow:hidden;position:relative}.container_a6qs{padding:2rem;position:relative;text-align:center;z-index:2}.glitchCode_Pt6F{animation:3s ease-in-out infinite d;color:var(--primary);font-family:Racing Sans One,monospace;font-size:10rem;font-weight:900;line-height:1;position:relative;text-shadow:0 0 20px color-mix(in srgb,var(--primary) 40%,#0000),0 0 60px color-mix(in srgb,var(--primary) 20%,#0000)}.title_B9uO{color:var(--text-color);font-size:2rem;font-weight:700;margin:1rem 0 .5rem}.subtitle_Z226{color:var(--mid-grey);font-size:1.1rem;line-height:1.6;margin-bottom:2.5rem;margin-left:auto;margin-right:auto;max-width:480px}.navLinks_NElv{display:flex;flex-wrap:wrap;gap:1rem;justify-content:center;margin-top:1rem}.navLink_KmGT{align-items:center;border-radius:8px;cursor:pointer;display:inline-flex;font-size:1rem;font-weight:600;gap:.5rem;padding:.75rem 1.5rem;transition:.25s}.navLink_KmGT:hover{transform:translateY(-2px)}.primaryLink_UP3n{background:var(--primary);color:var(--hero-background)}.primaryLink_UP3n:hover{box-shadow:0 4px 20px color-mix(in srgb,var(--primary) 50%,#0000);color:var(--hero-background)}.secondaryLink_VStI{background:#0000;border:2px solid var(--primary);color:var(--primary)}.secondaryLink_VStI:hover{background:color-mix(in srgb,var(--primary) 10%,#0000);color:var(--primary)}.particles_xv3x{inset:0;overflow:hidden;position:absolute;z-index:1}.particle_Q0bD{animation:linear infinite c;background:var(--primary);border-radius:50%;height:4px;opacity:.3;position:absolute;width:4px}.particle_Q0bD:first-child{animation-delay:-5s;animation-duration:14s;height:4px;left:55%;opacity:.37;top:89%;width:6px}.particle_Q0bD:nth-child(2){animation-delay:-2s;animation-duration:20s;height:4px;left:63%;opacity:.16;top:65%;width:5px}.particle_Q0bD:nth-child(3){animation-delay:-1s;animation-duration:14s;height:3px;left:43%;opacity:.24;top:2%;width:3px}.particle_Q0bD:nth-child(4){animation-delay:-5s;animation-duration:14s;height:4px;left:100%;opacity:.17;top:78%;width:5px}.particle_Q0bD:nth-child(5){animation-delay:-5s;animation-duration:18s;height:5px;left:81%;opacity:.17;top:8%;width:6px}.particle_Q0bD:nth-child(6){animation-delay:-4s;animation-duration:17s;height:3px;left:31%;opacity:.35;top:19%;width:4px}.particle_Q0bD:nth-child(7){animation-delay:-3s;animation-duration:14s;height:3px;left:21%;opacity:.23;top:14%;width:5px}.particle_Q0bD:nth-child(8){animation-delay:-5s;animation-duration:13s;height:5px;left:70%;opacity:.21;top:21%;width:6px}.particle_Q0bD:nth-child(9){animation-delay:-1s;animation-duration:10s;height:4px;left:19%;opacity:.19;top:41%;width:4px}.particle_Q0bD:nth-child(10){animation-delay:-2s;animation-duration:20s;height:3px;left:62%;opacity:.33;top:44%;width:4px}.particle_Q0bD:nth-child(11){animation-delay:-2s;animation-duration:14s;height:3px;left:73%;opacity:.32;top:61%;width:3px}.particle_Q0bD:nth-child(12){animation-delay:-4s;animation-duration:9s;height:5px;left:78%;opacity:.22;top:21%;width:6px}.particle_Q0bD:nth-child(13){animation-delay:-1s;animation-duration:9s;height:3px;left:9%;opacity:.3;top:9%;width:3px}.particle_Q0bD:nth-child(14){animation-delay:-3s;animation-duration:20s;height:4px;left:72%;opacity:.14;top:60%;width:3px}.particle_Q0bD:nth-child(15){animation-delay:-5s;animation-duration:20s;height:4px;left:100%;opacity:.3;top:62%;width:6px}.particle_Q0bD:nth-child(16){animation-delay:-5s;animation-duration:12s;height:4px;left:82%;opacity:.25;top:14%;width:6px}.particle_Q0bD:nth-child(17){animation-delay:-5s;animation-duration:16s;height:3px;left:19%;opacity:.11;top:68%;width:4px}.particle_Q0bD:nth-child(18){animation-delay:-4s;animation-duration:13s;height:6px;left:23%;opacity:.24;top:4%;width:5px}.particle_Q0bD:nth-child(19){animation-delay:-5s;animation-duration:14s;height:4px;left:35%;opacity:.3;top:89%;width:5px}.particle_Q0bD:nth-child(20){animation-delay:-1s;animation-duration:18s;height:6px;left:92%;opacity:.16;top:27%;width:3px}@keyframes c{0%,to{opacity:0;transform:translateY(0) translateX(0)}10%,90%{opacity:.3}50%{transform:translateY(-100px) translateX(30px)}}@keyframes d{0%,to{opacity:1}50%{opacity:.85}}.dropdownNavbarItemMobile_J0Sd,button.headers-card-title[data-v-ab19704d],button.schema-card-title[data-v-d9bd8110]{cursor:pointer}.iconLanguage_nlXk{margin-right:5px;vertical-align:text-bottom}.searchBar_RVTs .dropdownMenu_qbY6{background:var(--search-local-modal-background,#f5f6f7);border-radius:6px;box-shadow:var(--search-local-modal-shadow,inset 1px 1px 0 0 #ffffff80,0 3px 8px 0 #555a64);left:auto!important;margin-top:8px;padding:var(--search-local-spacing,12px);position:relative;right:0!important;width:var(--search-local-modal-width,560px)}.searchInput_YFbd:focus{outline:2px solid var(--search-local-input-active-border-color,var(--ifm-color-primary));outline-offset:0}div.ask-ai,html[data-theme=dark] div.ask-ai{--ask-ai-primary:var(--ifm-color-primary);--ask-ai-primary-hover:var(--ifm-color-primary-light);--ask-ai-foreground:var(--ifm-color-content);--ask-ai-border:var(--ifm-color-emphasis-300);--ask-ai-error:var(--ifm-color-danger);--ask-ai-button-bg:var(--ifm-color-emphasis-200)}.ask-ai{--ask-ai-background:var(--search-local-modal-background,#f5f6f7);--ask-ai-muted:var(--search-local-muted-color,#969faf)}html[data-theme=dark] .ask-ai{--ask-ai-background:var(--search-local-modal-background,var(--ifm-background-color));--ask-ai-muted:var(--search-local-muted-color,var(--ifm-color-secondary-darkest))}html[data-theme=dark] .searchBar_RVTs .dropdownMenu_qbY6{background:var(--search-local-modal-background,var(--ifm-background-color));box-shadow:var(--search-local-modal-shadow,inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309)}.searchBar_RVTs .dropdownMenu_qbY6 .suggestion_fB_2{align-items:center;background:var(--search-local-hit-background,#fff);border-radius:4px;box-shadow:var(--search-local-hit-shadow,0 1px 3px 0 #d4d9e1);color:var(--search-local-hit-color,#444950);cursor:pointer;display:flex;flex-direction:row;height:var(--search-local-hit-height,56px);padding:0 var(--search-local-spacing,12px);width:100%}.hitTree_kk6K,.noResults_l6Q3{align-items:center;display:flex}html[data-theme=dark] .dropdownMenu_qbY6 .suggestion_fB_2{background:var(--search-local-hit-background,var(--ifm-color-emphasis-100));box-shadow:var(--search-local-hit-shadow,none);color:var(--search-local-hit-color,var(--ifm-font-color-base))}.scalar-app .mb-1,.searchBar_RVTs .dropdownMenu_qbY6 .suggestion_fB_2:not(:last-child){margin-bottom:4px}.searchBar_RVTs .dropdownMenu_qbY6 .suggestion_fB_2.cursor_eG29{background-color:var(--search-local-highlight-color,var(--ifm-color-primary))}.hitFooter_E9YW a,.hitIcon_a7Zy,.hitPath_ieM4,.hitTree_kk6K,.noResultsIcon_EBY5{color:var(--search-local-muted-color,#969faf)}html[data-theme=dark] .hitIcon_a7Zy,html[data-theme=dark] .hitPath_ieM4,html[data-theme=dark] .hitTree_kk6K,html[data-theme=dark] .noResultsIcon_EBY5{color:var(--search-local-muted-color,var(--ifm-color-secondary-darkest))}.hitTree_kk6K>svg{height:var(--search-local-hit-height,56px);opacity:.5;width:24px}.hitIcon_a7Zy,.hitTree_kk6K>svg{stroke-width:var(--search-local-icon-stroke-width,1.4)}.hitAction_NqkB,.hitIcon_a7Zy,.icon[data-v-9551cf83],.scalar-app .size-5{height:20px;width:20px}.hitWrapper_sAK8{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;margin:0 8px;overflow-x:hidden;width:80%}.hitWrapper_sAK8 mark{background:none;color:var(--search-local-highlight-color,var(--ifm-color-primary))}.hitTitle_vyVt{font-size:.9em}.hitPath_ieM4{font-size:.75em}.hitPath_ieM4,.hitTitle_vyVt{overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.noResults_l6Q3{flex-direction:column;justify-content:center;padding:var(--search-local-spacing,12px) 0}.noResultsIcon_EBY5{margin-bottom:var(--search-local-spacing,12px)}.hitFooter_E9YW{font-size:.85em;margin-top:var(--search-local-spacing,12px);text-align:center}.suggestion_fB_2.cursor_eG29,.suggestion_fB_2.cursor_eG29 .hitIcon_a7Zy,.suggestion_fB_2.cursor_eG29 .hitPath_ieM4,.suggestion_fB_2.cursor_eG29 .hitTree_kk6K,.suggestion_fB_2.cursor_eG29 mark{color:var(--search-local-hit-active-color,var(--ifm-color-white))!important}.searchBarContainer_NW3z{margin-left:16px}.searchBarContainer_NW3z .searchBarLoadingRing_YnHq{display:none;left:10px;position:absolute;top:6px}.searchBarContainer_NW3z .searchClearButton_qk4g{background:none;border:none;line-height:1rem;padding:0;position:absolute;right:.8rem;top:50%;transform:translateY(-50%)}.dragover-above[data-v-a89d6a6e],.dragover-asChild[data-v-a89d6a6e],.dragover-below[data-v-a89d6a6e],.navbar__search,.scalar-app .relative{position:relative}.scalar-app .bg-none,.searchIndexLoading_EJ1f .navbar__search-input{background-image:none}.searchHintContainer_Pkmr{align-items:center;display:flex;gap:4px;height:100%;justify-content:center;pointer-events:none;position:absolute;right:10px;top:0}.searchHint_iIMx{background-color:var(--ifm-navbar-search-input-background-color);border:1px solid var(--ifm-color-emphasis-500);box-shadow:inset 0 -1px 0 var(--ifm-color-emphasis-500);color:var(--ifm-navbar-search-input-placeholder-color)}html[dir=rtl] .searchHintContainer_Pkmr{left:10px;right:auto}html[dir=rtl] .searchBarContainer_NW3z .searchClearButton_qk4g{left:.8rem;right:auto}html[dir=rtl] .searchBarContainer_NW3z .searchBarLoadingRing_YnHq{left:auto;right:10px}html[dir=rtl] .navbar__search-input{padding:0 2.25em 0 .5em}.loadingRing_RJI3{display:inline-block;height:20px;opacity:var(--search-local-loading-icon-opacity,.5);position:relative;width:20px}.loadingRing_RJI3 div{animation:1.2s cubic-bezier(.5,0,.5,1) infinite e;border:2px solid var(--search-load-loading-icon-color,var(--ifm-navbar-search-input-color));border-color:var(--search-load-loading-icon-color,var(--ifm-navbar-search-input-color)) #0000 #0000 #0000;border-radius:50%;box-sizing:border-box;display:block;height:16px;margin:2px;position:absolute;width:16px}.loadingRing_RJI3 div:first-child{animation-delay:-.45s}.loadingRing_RJI3 div:nth-child(2){animation-delay:-.3s}.loadingRing_RJI3 div:nth-child(3){animation-delay:-.15s}@keyframes e{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.navbarHideable_m1mJ{transition:transform var(--ifm-transition-fast) ease}.navbarHidden_jGov{transform:translate3d(0,calc(-100% - 2px),0)}.errorBoundaryError_a6uf{color:red;white-space:pre-wrap}.errorBoundaryFallback_VBag{color:red;padding:.55rem}.navbar__items--right>:last-child,.scalar-app .pr-0{padding-right:0}.footerLogoLink_BH7S{opacity:.5;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.footerLogoLink_BH7S:hover,.hash-link:focus,:hover>.hash-link{opacity:1}.anchorTargetStickyNavbar_Vzrq{scroll-margin-top:calc(var(--ifm-navbar-height) + .5rem)}.anchorTargetHideOnScrollNavbar_vjPI{scroll-margin-top:.5rem}.hash-link{opacity:0;padding-left:.5rem;transition:opacity var(--ifm-transition-fast);user-select:none}.hash-link:before{content:"#"}.mainWrapper_z2l0{display:flex;flex:1 0 auto;flex-direction:column}.docusaurus-mt-lg{margin-top:3rem}#__docusaurus{display:flex;flex-direction:column;min-height:100%}.searchContextInput_mXoe,.searchQueryInput_CFBF{background:var(--ifm-background-color);border:var(--ifm-global-border-width) solid var(--ifm-color-content-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-font-color-base);font-size:var(--ifm-font-size-base);margin-bottom:1rem;padding:.5rem;width:100%}.searchResultItem_U687{border-bottom:1px solid #dfe3e8;padding:1rem 0}.searchResultItemPath_uIbk{color:var(--ifm-color-content-secondary);font-size:.8rem;margin:.5rem 0 0}.searchResultItemSummary_oZHr{font-style:italic;margin:.5rem 0 0}.dashy-api-page{font-size:10px}.api-loading,.dashy-api-page main{min-height:calc(100vh - var(--ifm-navbar-height,60px))}.api-loading{color:var(--mid-grey,#888);display:grid;place-items:center}.dashy-api-page .parameter-item-trigger{align-items:normal}.dashy-api-page ol,.dashy-api-page ul,:not(.containsTaskList_mC6p>li)>.containsTaskList_mC6p{padding-left:0}.dashy-api-page .section{padding:30px 0}.dashy-api-page div.flex-col:nth-child(4)>div:nth-child(2),.scalar-app .\!hidden{display:none!important}.scalar-app .text-wrap{text-wrap:wrap}.scalar-app .-translate-x-full,.scalar-app .-translate-y-1\.5,.scalar-app .-translate-y-1\/2,.scalar-app .centered,.scalar-app .translate-x-0,.scalar-app .translate-x-2\.5,.scalar-app .translate-x-\[14px\],.scalar-app .translate-x-full,.scalar-app .translate-y-0,.scalar-app .translate-y-1\.5{translate:var(--tw-translate-x) var(--tw-translate-y)}.scalar-app .from-b-1,.scalar-app .to-b-1\.5,.scalar-app .to-b-2{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.scalar-app .from-b-1,.scalar-app .to-b-1\.5,.scalar-app .to-b-2,.scalar-app .to-transparent{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from) var(--tw-gradient-from-position),var(--tw-gradient-to) var(--tw-gradient-to-position))}.scalar-app .mask-y-from-\[calc\(100\%-8px\)\],.scalar-app .mask-y-to-100\%{--tw-mask-top:linear-gradient(to top,var(--tw-mask-top-from-color)var(--tw-mask-top-from-position),var(--tw-mask-top-to-color)var(--tw-mask-top-to-position));--tw-mask-bottom:linear-gradient(to bottom,var(--tw-mask-bottom-from-color)var(--tw-mask-bottom-from-position),var(--tw-mask-bottom-to-color)var(--tw-mask-bottom-to-position));-webkit-mask-composite:source-in;mask-composite:intersect;-webkit-mask-image:var(--tw-mask-linear),var(--tw-mask-radial),var(--tw-mask-conic);mask-image:var(--tw-mask-linear),var(--tw-mask-radial),var(--tw-mask-conic);--tw-mask-linear:var(--tw-mask-left),var(--tw-mask-right),var(--tw-mask-bottom),var(--tw-mask-top);--tw-mask-top:linear-gradient(to top,var(--tw-mask-top-from-color) var(--tw-mask-top-from-position),var(--tw-mask-top-to-color) var(--tw-mask-top-to-position));--tw-mask-bottom:linear-gradient(to bottom,var(--tw-mask-bottom-from-color) var(--tw-mask-bottom-from-position),var(--tw-mask-bottom-to-color) var(--tw-mask-bottom-to-position))}.scalar-app .backdrop-blur{--tw-backdrop-blur:blur(8px)}.scalar-app .active\:brightness-90:active{--tw-brightness:brightness(90%);filter:var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, )}.parameter-item-headers[data-v-be55ce3a],.scalar-app .markdown table{border:var(--scalar-border-width) solid var(--scalar-border-color)}.scalar-app .markdown pre code{margin:var(--markdown-spacing-sm) 0}:where(body.dark-mode)>.scalar-tooltip:before{--tw-shadow:inset 0 0 0 var(--tw-shadow-color,var(--scalar-border-width)) var(--scalar-border-color)}.full-size-styles[data-v-c36b47da]{border-right:var(--scalar-border-width) solid var(--scalar-border-color)}.apiPillRemove[data-v-e7c7c266]:hover,.authenticationProvided[data-v-e3416cd5],.collapsible-section-header[data-v-999a158a],.collapsible-section:hover .collapsible-section-trigger svg[data-v-999a158a],.documentNameActive[data-v-5e4bca68],.enum-toggle-button[data-v-d5367294]:hover,.property-const[data-v-b69e3089],.property-default-value[data-v-4da5c70a]:hover svg,.property-default:hover .property-default-label span[data-v-4da5c70a],.property-example-value[data-v-72def0ea]:hover svg,.property-example:hover .property-example-label span[data-v-72def0ea],.requestSuccess[data-v-acc2c0d8],.section-accordion-button:hover .section-accordion-chevron[data-v-9419dd23],.section-accordion-button:hover .section-accordion-chevron[data-v-ff689b94],.termsAgree[data-v-e7c7c266]:hover,button.headers-card-title[data-v-ab19704d]:hover,button.schema-card-title[data-v-d9bd8110]:hover{color:var(--scalar-color-1)}.authenticationProvided[data-v-e3416cd5],.authenticationRequired[data-v-d15ef40b],.requestApproved[data-v-bb311586],.requestFailed[data-v-bc27e533],.requestRejected[data-v-9803a54c],.requestSuccess[data-v-acc2c0d8]{align-items:center;display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);gap:6px;min-height:40px;position:relative}.authenticationRequired[data-v-d15ef40b],.hljs.language-curl .hljs-string,.hljs.language-php .hljs-variable,.scalar-app .hljs-built_in,.scalar-app .hljs-bullet,.scalar-app .hljs-doctag,.scalar-app .hljs-link,.scalar-app .hljs-meta,.scalar-app .hljs-name,.scalar-app .hljs-regexp,.scalar-app .hljs-section,.scalar-app .hljs-string,.scalar-app .hljs-subst,.scalar-app .hljs-symbol,.scalar-app .hljs-title,.scalar-app .markdown .markdown-alert.markdown-alert-note .markdown-alert-icon:before,.scalar-app .text-blue{color:var(--scalar-color-blue)}.askForAuthentication[data-v-8af07610]{border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);border-top:var(--scalar-border-width) solid var(--scalar-border-color);box-shadow:0 var(--scalar-border-width) 0 var(--scalar-background-1),0 calc(var(--scalar-border-width)*-1) 0 var(--scalar-background-1);display:flex;flex-direction:column;margin-bottom:12px;padding:0;position:relative;width:100%}.authContent[data-v-8af07610]{display:grid;margin:auto;max-width:520px;min-height:0;overflow:hidden;width:100%}.authContentInner[data-v-8af07610]>div{margin:36px 0 48px}.askForAuthentication.open .authContent[data-v-8af07610],.requestPreview.open .requestContent[data-v-0eb5f95d]{grid-template-rows:1fr}.continueButton[data-v-8af07610],.scalar-app .self-end{align-self:flex-end}.toggleButton[data-v-0eb5f95d],.toggleButton[data-v-8af07610]{align-items:center;background:none;border:none;border-radius:var(--scalar-radius-lg);color:var(--scalar-color-3);cursor:pointer;display:flex;justify-content:space-between;position:relative;text-align:left}.authContentInner[data-v-8af07610],.requestContentInner[data-v-0eb5f95d]{min-height:0;overflow:hidden}.authorizeButton[data-v-8af07610]{background:var(--scalar-color-blue)!important;color:#fff!important;display:flex;gap:5px;margin:0!important;z-index:1}.autosendPaused[data-v-d08225db],.buildingRequest[data-v-5749c429],.sendingRequest[data-v-65dc6dfb]{align-items:center;color:var(--scalar-color-blue);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);gap:6px;min-height:40px;position:relative}.playIcon[data-v-5749c429],.playIcon[data-v-653c66b3],.playIcon[data-v-65dc6dfb],.playIcon[data-v-9d9724d2]{align-items:center;background:var(--scalar-background-1);border-radius:50%;display:flex;height:16px;justify-content:center;padding:4px;position:relative;width:16px;z-index:1}.playIcon[data-v-5749c429]:before,.playIcon[data-v-65dc6dfb]:before{animation:.42s linear infinite g;background:var(--scalar-background-1);border:1.75px solid;border-bottom:1.75px solid #0000;border-image:initial;border-radius:50%;box-sizing:border-box;content:"";display:inline-block;height:16px;position:absolute;width:16px}.buildingRequest svg[data-v-5749c429],.loadingApiSpecs svg[data-v-9d9724d2],.sendingRequest svg[data-v-653c66b3],.sendingRequest svg[data-v-65dc6dfb]{border-radius:50%;height:100%;width:100%;z-index:1}.openapi-color,.requestApproved[data-v-bb311586],.scalar-app .hljs-addition,.scalar-app .hljs-literal,.scalar-app .hljs-selector-tag,.scalar-app .hljs-type,.scalar-app .text-green{color:var(--scalar-color-green)}.requestFailed[data-v-bc27e533],.requestRejected[data-v-9803a54c],.scalar-app .hljs-deletion,.scalar-app .text-red{color:var(--scalar-color-red)}.requestFailedIcon[data-v-bc27e533]{border-radius:50%;box-shadow:inset 0 0 0 1.5px currentColor;height:16px;padding:4px;width:16px}.light-mode .bg-preview[data-v-0956ad2d],.light-mode .bg-preview[data-v-92f84612],.light-mode .bg-preview[data-v-c02b5bb8]{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill-opacity='10%25'%3E%3Cpath d='M0 0h8v8H0zM8 8h8v8H8z'/%3E%3C/svg%3E")}.dark-mode .bg-preview[data-v-0956ad2d],.dark-mode .bg-preview[data-v-92f84612],.dark-mode .bg-preview[data-v-c02b5bb8]{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23FFF' fill-opacity='10%25'%3E%3Cpath d='M0 0h8v8H0zM8 8h8v8H8z'/%3E%3C/svg%3E")}.requestHeaderContainer[data-v-0eb5f95d]{align-items:center;display:flex;justify-content:space-between;padding:0 5px}.requestPreview[data-v-0eb5f95d]{border-radius:12px;display:flex;flex-direction:column;position:relative;width:100%}.requestContent[data-v-0eb5f95d]{display:grid;min-height:0;overflow:hidden}.code[data-v-0eb5f95d]{background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));border-radius:12px;display:flex;flex-direction:column;font-size:var(--scalar-font-size-4);margin-bottom:12px;overflow:hidden}.apiPill:hover .apiPillRemove[data-v-e7c7c266],.dark-mode .code[data-v-0eb5f95d],.dark-mode .nav-item[data-v-507381a3],.dark-mode .paymentInfo[data-v-7ab1200e],.dark-mode .promptForm[data-v-e7c7c266],.searchItem[data-v-7945f74c]:hover,.settingsButton[aria-disabled=true][data-v-e7c7c266]{background:var(--scalar-background-2)}.code h1[data-v-0eb5f95d]{color:var(--scalar-color-3);font-size:var(--scalar-font-size-3);padding:8px}.code[data-v-0eb5f95d] .codeBlock{max-height:calc(50vh - 100px);padding-top:0}.autosendContainer[data-v-0eb5f95d],.operation-title[data-v-55addca4]{display:flex;justify-content:space-between}.sendButton[data-v-0eb5f95d]{background:var(--scalar-color-blue);color:#fff;font-weight:var(--scalar-semibold);padding:5px 10px}.sendButton[data-v-0eb5f95d]:active,.sendButton[data-v-0eb5f95d]:hover{background:color-mix(in srgb,var(--scalar-color-blue),#000 10%);color:#fff!important}.executeRequestTool[data-v-3e825a81]{border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);border-top:var(--scalar-border-width) solid var(--scalar-border-color);box-shadow:0 var(--scalar-border-width) 0 var(--scalar-background-1),0 calc(var(--scalar-border-width)*-1) 0 var(--scalar-background-1);display:flex;flex-direction:column;gap:10px;margin-bottom:12px}.tool[data-v-3e825a81]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:15px;margin-bottom:20px;padding:15px}.contextItem[data-v-b6e5aa96]{align-items:center;background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));border-radius:12px;color:var(--scalar-color-2);cursor:pointer;display:inline-block;display:flex;font-size:10px;padding:5px 10px;vertical-align:middle}.light-mode .shimmer[data-v-b6e5aa96],.shimmer[data-v-b6e5aa96]{animation:1.4s ease-in-out infinite f;background:var(--scalar-background-2)}.shimmer[data-v-b6e5aa96]{background-image:linear-gradient(90deg,#202020 0,var(--scalar-background-2) 40%,var(--scalar-background-3) 80%);background-size:200% 100%}.light-mode .shimmer[data-v-b6e5aa96]{background-image:linear-gradient(90deg,#fafafa 0,var(--scalar-background-2) 40%,var(--scalar-background-3) 80%);background-size:200% 100%}@keyframes f{0%{background-position:200% 0}to{background-position:-200% 0}}.playIcon[data-v-653c66b3]:before,.playIcon[data-v-9d9724d2]:before{animation:.42s linear infinite g;background:var(--scalar-background-1);border:1.5px solid;border-bottom:1.5px solid #0000;border-image:initial;border-radius:50%;box-sizing:border-box;content:"";display:inline-block;height:16px;position:absolute;width:16px}.loadingApiSpecs[data-v-9d9724d2],.sendingRequest[data-v-653c66b3]{align-items:center;color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);gap:6px;margin-bottom:10px}.operations[data-v-ecee6203]{align-items:center;display:flex;gap:5px;margin-bottom:12px}.operations[data-v-ecee6203]:empty{margin-bottom:-12px}@keyframes g{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.catalogModal .scalar-modal-body,.children[data-v-84242873],.right[data-v-bc24f891]{display:flex;flex-direction:column}.searchInput[data-v-bc24f891]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);margin-bottom:10px}.catalog[data-v-bc24f891]{display:grid;font-size:var(--scalar-font-size-3);gap:10px;grid-template-columns:1fr 1fr;overflow-y:scroll}.item[data-v-bc24f891]{align-items:center;background-color:var(--scalar-background-2);border-radius:var(--scalar-radius-lg);display:flex;gap:10px;padding:15px;transition:background-color .16s}.item[data-v-bc24f891]:hover{background-color:color-mix(in srgb,var(--scalar-background-3),#0000 40%)!important}.logo[data-v-bc24f891]{width:25px}.item-top[data-v-bc24f891]{display:flex;gap:10px}.version[data-v-bc24f891]{background:var(--scalar-background-3);border-radius:var(--scalar-radius);color:var(--scalar-color-3);font-size:var(--scalar-font-size-5);padding:2px 5px}.description[data-v-bc24f891],.endpoint-label-path[data-v-55addca4] em,.property-detail-prefix[data-v-1295f965],.property-optional[data-v-b69e3089],.property-required[data-v-b69e3089],.schema-card-description[data-v-d9bd8110]{color:var(--scalar-color-2)}.dropdown-item[data-v-2d142bb5]{align-items:center;display:flex;gap:10px}.approvalSection[data-v-a7e6c699],.paymentSection[data-v-7ab1200e],.uploadSection[data-v-9551cf83]{align-items:center;background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 95%);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px 16px 0 0;display:flex;justify-content:space-between;margin-bottom:-16px;padding:8px 8px 24px 12px;position:absolute;top:0;transform:translate3d(0,calc(-100% + 16px),0);width:100%}.approvalText[data-v-7ab1200e],.approvalText[data-v-a7e6c699],.infoText[data-v-913a3815],.uploadText[data-v-9551cf83]{font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold)}.approveContainer[data-v-a7e6c699],.paymentContainer[data-v-7ab1200e]{display:flex;gap:5px}.actionButton[data-v-a7e6c699]{align-items:center;border-radius:50px;display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);padding:6px 12px}.rejectButton[data-v-7ab1200e],.rejectButton[data-v-a7e6c699]{background:var(--scalar-color-red);color:#fff}.rejectButton[data-v-7ab1200e]:active,.rejectButton[data-v-7ab1200e]:hover,.rejectButton[data-v-a7e6c699]:active,.rejectButton[data-v-a7e6c699]:hover{background:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 10%);color:#fff!important}.approveButton[data-v-a7e6c699]{background:var(--scalar-color-blue);color:#fff}.approveButton[data-v-7ab1200e]:active,.approveButton[data-v-7ab1200e]:hover,.approveButton[data-v-a7e6c699]:active,.approveButton[data-v-a7e6c699]:hover,.upgradeButton[data-v-913a3815]:active,.upgradeButton[data-v-913a3815]:hover{background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 10%);color:#fff!important}.error[data-v-63a481da]{background:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);position:absolute;top:0;transform:translate3d(0,calc(-100% + 16px),0)}.error[data-v-63a481da],.freeMessagesInfoSection[data-v-913a3815]{align-items:center;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px 16px 0 0;display:flex;margin-bottom:-16px;padding:8px 8px 24px 12px}.freeMessagesInfoSection[data-v-913a3815]{background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 95%);justify-content:space-between;position:relative;width:100%}.actionsContainer[data-v-913a3815]{align-items:center;display:flex;gap:8px}.actionButton[data-v-7ab1200e],.actionButton[data-v-913a3815]{align-items:center;border-radius:50px;display:flex;font-weight:var(--scalar-semibold);padding:6px 12px}.approveButton[data-v-7ab1200e],.upgradeButton[data-v-913a3815]{background:var(--scalar-color-blue);color:#fff;font-size:var(--scalar-font-size-3)}.closeButton[data-v-913a3815]{align-items:center;background:#0000;border:none;border-radius:50%;color:var(--scalar-color-2);cursor:pointer;display:flex;height:28px;justify-content:center;width:28px}.closeButton[data-v-913a3815]:hover{background:color-mix(in srgb,var(--scalar-color-blue),var(--scalar-background-1) 80%);color:var(--scalar-color-1)}.paymentInfo[data-v-7ab1200e]{background:var(--scalar-background-1);border-radius:16px;bottom:70px;box-shadow:var(--scalar-shadow-2);opacity:0;padding:12px;pointer-events:none;position:absolute;right:0;transform:translate3d(0,-5px,0);transition:.2s ease-in-out;width:300px}.paymentInfo h3[data-v-7ab1200e]{font-size:var(--scalar-font-size-1);font-weight:var(--scalar-bold);margin-bottom:18px}.paymentInfo h3 span[data-v-7ab1200e]{font-size:var(--scalar-font-size-2)}.paymentContainer:hover .paymentInfo[data-v-7ab1200e]{opacity:1;transform:translateZ(0)}.paymentInfoItem[data-v-7ab1200e]{color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);justify-content:space-between;margin-top:8px}.paymentInfoSection[data-v-7ab1200e]:not(:last-child){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);padding-bottom:8px}.searchItem[data-v-7945f74c]{align-items:center;display:flex;font-size:var(--scalar-font-size-3);gap:9px;padding:8px 10px}.searchInput[data-v-7945f74c]{margin-bottom:5px}.apiPillLogo[data-v-e7c7c266],.searchItemLogo[data-v-7945f74c]{width:15px}.searchIcon[data-v-7945f74c]{margin-right:7px}.searchResultsEmpty[data-v-7945f74c]{color:var(--scalar-color-2);font-size:var(--scalar-font-size-3);margin:10px}.uploadSection.error[data-v-9551cf83]{background:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%)}.uploadSection.done[data-v-9551cf83]{background:color-mix(in srgb,var(--scalar-color-green),var(--scalar-background-1) 95%)}.actionContainer[data-v-e7c7c266]{background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px;box-shadow:0 24px 0 2px var(--scalar-background-1);position:relative;width:100%}.promptForm[data-v-e7c7c266]{background:var(--scalar-background-1);border-radius:16px;box-shadow:var(--scalar-shadow-1),0 0 0 var(--scalar-border-width) var(--scalar-border-color);display:flex;flex-direction:column;position:relative;width:100%}.addAPIButton[data-v-e7c7c266],.addAPIButton[data-v-e7c7c266]:hover,.scalar-app .markdown code{box-shadow:0 0 0 var(--scalar-border-width) var(--scalar-border-color)}.inputActionsContainer[data-v-e7c7c266]{display:flex;justify-content:space-between;padding:0 8px 8px}.inputActionsLeft[data-v-e7c7c266]{align-items:center;display:flex;flex-wrap:wrap;gap:5px}.inputActionsRight[data-v-e7c7c266]{display:flex;gap:5px;position:relative}.apiPill[data-v-e7c7c266]{align-items:center;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px;color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);gap:4px;height:28px;padding:0 8px;pointer-events:all;-webkit-user-select:none;user-select:none;z-index:1}.apiPillRemove[data-v-e7c7c266]{align-items:center;border-radius:50%;display:flex;height:24px;justify-content:center;margin-right:-6px;width:24px}.address-bar-history-button[data-v-a93fa60f]:hover,.address-bar-history-button[data-v-c15c6573]:hover,.dark-mode .addAPIButton[data-v-e7c7c266]:hover,.dark-mode .apiPill:hover .apiPillRemove[data-v-e7c7c266],.dark-mode .settingsButton[data-v-e7c7c266]:hover,.start-item[data-v-673f10b6]:hover{background:var(--scalar-background-3)}.addAPIButton[data-v-e7c7c266]:hover,code.property-detail-value[data-v-1295f965]{background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}.prompt[data-v-e7c7c266]{border:none;field-sizing:content;max-height:250px;max-width:100%;min-height:64px;outline:0;overflow-y:auto;resize:none;scrollbar-width:thin;width:100%;z-index:1;font-family:var(--scalar-font);font-size:16px;padding:12px 12px 14px}.prompt[data-v-e7c7c266]:disabled,.property-default-value[data-v-4da5c70a] svg,.property-enum-value-description[data-v-f4b54bdd],.property-example-value[data-v-72def0ea] svg,.scalar-app .text-c-3,.scalar-app .text-grey,[data-v-103d9d56] .cm-placeholder{color:var(--scalar-color-3)}.addAPIButton[data-v-e7c7c266]{align-items:center;border-radius:100%;color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-bold);gap:4px;height:28px;justify-content:center;pointer-events:all;width:28px;z-index:1}.settingsButton[data-v-e7c7c266]{border-radius:50%!important;color:var(--scalar-color-3)!important;margin:0!important;z-index:1}.sendButton[data-v-e7c7c266]{background:var(--scalar-color-blue)!important;border:var(--scalar-border-width) solid var(--scalar-color-blue);border-radius:50%!important;margin:0!important;z-index:1}.document[data-v-5e4bca68],.section-accordion-content[data-v-ff689b94]{border-top:var(--scalar-border-width) solid var(--scalar-border-color)}.sendButton[data-v-e7c7c266]:not([aria-disabled=true]){color:#fff!important}.ask-agent-scalar-send[data-v-f43e32ab]:hover,.sendButton[data-v-e7c7c266]:not([aria-disabled=true]):hover{background:color-mix(in srgb,var(--scalar-color-blue),#0000 10%)!important}.sendButton[aria-disabled=true][data-v-e7c7c266]{background:var(--scalar-background-2)!important;border:var(--scalar-border-width) solid var(--scalar-border-color);color:var(--scalar-color-3)!important}.cm-pill.bg-grey,.dark-mode .sendButton[aria-disabled=true][data-v-e7c7c266],.light-mode .cm-pill{background:var(--scalar-background-3)!important}.contextContainer[data-v-e7c7c266]{color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);justify-content:space-between;padding:10px 12px 12px;-webkit-user-select:none;user-select:none;width:100%}.settingsButton[data-v-e7c7c266]{border-radius:var(--scalar-radius-lg);font-weight:var(--scalar-semibold);padding:4px 6px}.settingsButton[data-v-e7c7c266]:hover{background:var(--scalar-background-2);box-shadow:0 0 var(--scalar-border-width) 0 var(--scalar-border-color);cursor:pointer}.agentLabel[data-v-e7c7c266]{cursor:text;font-size:0;height:100%;position:absolute;width:100%}.sendCheckboxContinue[data-v-e7c7c266]:has(input){align-items:center;background:var(--scalar-background-2);border-radius:14px;box-shadow:0 0 0 1.5px var(--scalar-background-2);color:var(--scalar-color-2);display:flex;font-size:var(--scalar-font-size-3);font-weight:var(--scalar-semibold);height:28px;-webkit-user-select:none;user-select:none}.dark-mode .sendCheckboxContinue[data-v-e7c7c266]:has(input){background:var(--scalar-background-3);box-shadow:0 0 0 1.5px var(--scalar-background-3)}.addMoreContext[data-v-e7c7c266]{align-items:center;color:var(--scalar-color-3);display:flex;font-size:var(--scalar-font-size-3);height:40px;padding:0 8px 0 12px;position:relative}.addMoreContext[data-v-e7c7c266]:before{background:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2));box-shadow:-.5px -.5px 0 var(--scalar-border-color),inset .5px .5px 1px var(--scalar-border-color);content:"";height:8px;left:18px;position:absolute;top:-3px;transform:rotate(45deg);width:8px}.dark-mode .addMoreContext[data-v-e7c7c266]:before{box-shadow:-.5px -.5px 0 var(--scalar-border-color)}.addAPIContext[data-v-e7c7c266]{align-items:center;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:50%;display:flex;height:28px;justify-content:center;width:28px}.termsAgree[data-v-e7c7c266]{align-items:center;border-radius:14px;cursor:pointer;display:flex;gap:5px;height:inherit;margin:0 5px}.termsAgree:hover .termsAgreeIcon[data-v-e7c7c266]{background:var(--scalar-color-1);color:var(--scalar-background-1)}.termsAgreeIcon[data-v-e7c7c266]{background:var(--scalar-background-2);border-radius:50%;height:inherit;padding:2px;width:inherit}.chat[data-v-db4ff6c9]{display:flex;flex:1;flex-direction:column;max-width:744px;padding:24px 0;width:100%}.userMessage[data-v-db4ff6c9]{background:var(--scalar-background-2);border-radius:18px;color:var(--scalar-color-1);font-size:16px;line-height:24px;margin-bottom:12px;margin-left:auto;max-width:80%;padding-bottom:6px;padding-top:6px;padding-inline:16px;width:fit-content}div+.userMessage[data-v-db4ff6c9]{margin-top:64px}.chat[data-v-db4ff6c9]>div:has(.executeRequestTool)+div:has(.executeRequestTool){margin-top:-12px}.spacer[data-v-db4ff6c9]{min-height:280px;width:100%}.formContainer[data-v-db4ff6c9]{bottom:20px;max-width:744px;position:fixed;width:100%;z-index:1}.chat[data-v-db4ff6c9] .markdown,.operation-details-card[data-v-55addca4] .parameter-list .parameter-list,.scalar-app .mb-3{margin-bottom:12px}.agentLogo[data-v-56f7e8dd]{margin-bottom:15px}.startContainer[data-v-56f7e8dd]{align-items:center;display:flex;flex-direction:column;height:100%;justify-content:center;max-width:720px;position:relative;width:100%}.heading[data-v-56f7e8dd]{font-size:1.5rem;font-weight:var(--scalar-font-bold);margin-bottom:50px}.documentName[data-v-31f052ad],.documentName[data-v-5e4bca68],.proxyUrlContainer label[data-v-5e4bca68],.scalar-app .hljs-name,.scalar-app .hljs-section,.scalar-app .hljs-strong,.schema-card-title[data-v-d9bd8110],.settingsHeading[data-v-5e4bca68]{font-weight:var(--scalar-semibold)}.disclaimerText[data-v-56f7e8dd]{color:var(--scalar-color-3);font-size:var(--scalar-font-size-3);line-height:1.44;margin-top:40px;text-align:center;text-wrap:balance}.wrapper[data-v-f1eee0af]{align-items:center;display:flex;flex-direction:column;height:100%;width:100%}.docSettings[data-v-31f052ad],.documentList[data-v-5e4bca68]{flex-direction:column;font-size:var(--scalar-font-size-3);margin-bottom:12px;display:flex}.docSettings[data-v-31f052ad]{gap:12px;max-height:600px}.settingsModal .scalar-modal-layout{z-index:10!important}.settingsModal .scalar-modal-body{overflow-x:hidden;overflow-y:auto}.document[data-v-5e4bca68]{border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);display:flex;flex-direction:column;left:-12px;padding:0 12px;position:relative;width:calc(100% + 24px)}.document[data-v-5e4bca68]:first-of-type:not(:last-of-type),.scalar-app .markdown tr:last-of-type td{border-bottom:none}.documentName[data-v-5e4bca68]{align-items:center;color:var(--scalar-color-2);display:flex;gap:4px;padding:12px 0}.settingsHeading[data-v-5e4bca68]{align-items:center;display:flex;font-size:19px;gap:5px;margin-bottom:12px}.proxyUrlContainer[data-v-5e4bca68]{display:flex;flex-direction:column;font-size:var(--scalar-font-size-3);gap:5px}.noDocuments[data-v-5e4bca68]{color:var(--scalar-color-2);margin-bottom:10px}.references-classic-header[data-v-8a3822ca]{align-items:center;display:flex;gap:12px;margin:auto;max-width:var(--refs-content-max-width);padding:12px 0}.references-classic-header-content[data-v-8a3822ca]{display:flex;flex-grow:1;gap:12px}.references-classic-header-container[data-v-8a3822ca]{padding:0 60px;position:relative}.references-classic-header-icon[data-v-8a3822ca]{color:var(--scalar-color-1);height:24px}.section-wrapper[data-v-ff689b94]{color:var(--scalar-color-1);margin-top:-12px;padding-top:12px}.section-accordion[data-v-ff689b94]{background:var(--scalar-background-2);border-radius:var(--scalar-radius-lg);display:flex;flex-direction:column;scroll-margin-top:var(--refs-viewport-offset)}.section-accordion-transparent[data-v-ff689b94]{background:#0000;border:var(--scalar-border-width) solid var(--scalar-border-color)}.section-accordion-button[data-v-ff689b94]{align-items:center;cursor:pointer;display:flex;gap:6px;padding:6px}.section-accordion-button-content[data-v-ff689b94],.section-column[data-v-699c28e3]{flex:1;min-width:0}.section-accordion-button-actions[data-v-ff689b94]{align-items:center;color:var(--scalar-color-3);display:flex;gap:6px}.section-accordion-chevron[data-v-ff689b94]{color:var(--scalar-color-3);cursor:pointer;margin-right:4px;opacity:1}.section-accordion-content[data-v-ff689b94]{display:flex;flex-direction:column}.section-accordion-description[data-v-ff689b94]{color:var(--scalar-color--1);font-size:var(--scalar-mini);font-weight:var(--scalar-semibold);padding:10px 12px 0}.section-accordion-content-card[data-v-ff689b94] .property:last-of-type{padding-bottom:9px}.screenreader-only[data-v-df2e1026]{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0}.schema-type-icon[data-v-70cb5c13]{color:var(--scalar-color-1);display:none}.schema-type[data-v-70cb5c13]{color:var(--scalar-color-1);font-family:var(--scalar-font-code)}.error[data-v-d9bd8110],.scalar-app .bg-red{background-color:var(--scalar-color-red)}.schema-card[data-v-d9bd8110]{color:var(--scalar-color-1);font-size:var(--scalar-font-size-4)}.schema-card-title[data-v-d9bd8110]{align-items:center;border-bottom:var(--scalar-border-width) solid #0000;color:var(--scalar-color-2);display:flex;font-size:var(--scalar-mini);gap:4px;height:var(--schema-title-height);padding:6px 8px}.schema-card-title-icon--open[data-v-d9bd8110]{transform:rotate(45deg)}.headers-properties-open>.headers-card-title[data-v-ab19704d],.schema-properties-open>.schema-card-title[data-v-d9bd8110]{border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom-left-radius:0;border-bottom-right-radius:0}.scalar-app .w-fit,.schema-properties-open>.schema-properties[data-v-d9bd8110]{width:fit-content}.schema-card-description+.schema-properties[data-v-d9bd8110]{margin-top:8px;width:fit-content}.schema-card--level-0:first-of-type>.schema-card-description[data-v-d9bd8110]:has(+.schema-properties){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);margin-bottom:-8px;padding-bottom:8px}.schema-card--level-0~.schema-card--level-0>.schema-card-description[data-v-d9bd8110]:has(+.schema-properties){padding-top:8px}.scalar-app .container,.scalar-app .w-full,.schema-properties-name[data-v-d9bd8110],.schema-properties-open.schema-properties[data-v-d9bd8110],.schema-properties-open>.schema-card--open[data-v-d9bd8110],.schema-properties-open[data-v-d9bd8110]{width:100%}.schema-properties[data-v-d9bd8110]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);display:flex;flex-direction:column;width:fit-content}.schema-properties .schema-properties[data-v-d9bd8110]{border-radius:13.5px}.scalar-app .rounded-lg,.schema-properties .schema-properties.schema-properties-open[data-v-d9bd8110]{border-radius:var(--scalar-radius-lg)}.scalar-app .self-start,.schema-card--compact[data-v-d9bd8110]{align-self:flex-start}.headers-card.headers-card--open[data-v-ab19704d],.schema-card--compact.schema-card--open[data-v-d9bd8110]{align-self:auto}.schema-card-title--compact[data-v-d9bd8110]{border-bottom:none;color:var(--scalar-color-2);height:auto;padding:6px 10px 6px 8px}.fetchErrorCard_M8w3 p,.home-page-features-wrapper .feature .feature-title h3,.markdown[data-v-dc47ca65] *,.scalar-app .m-0,.schema-card-title--compact>.schema-card-title-icon[data-v-d9bd8110]{margin:0}.headers-card-title-icon--open[data-v-ab19704d],.schema-card-title--compact>.schema-card-title-icon--open[data-v-d9bd8110]{transform:rotate(45deg)}.scalar-app .static,.schema-properties-open>.schema-card-title--compact[data-v-d9bd8110]{position:static}.property--level-0>.schema-properties>.schema-card--level-0>.schema-properties[data-v-d9bd8110],:not(.composition-panel)>.schema-card--compact.schema-card--level-0>.schema-properties[data-v-d9bd8110]{border:none}.property--level-0 .schema-card--level-0:not(.schema-card--compact) .property--level-1[data-v-d9bd8110]{padding:0 0 8px}[data-v-d9bd8110] .schema-card-description p{color:var(--scalar-color-2);display:block;font-size:var(--scalar-small,var(--scalar-paragraph));line-height:1.5;margin-bottom:6px}.children .schema-card-description[data-v-d9bd8110]:first-of-type{padding-top:0}.property-enum-value[data-v-f4b54bdd]{align-items:stretch;color:var(--scalar-color-3);display:flex;line-height:1.5;overflow-wrap:break-word;position:relative;--decorator-width:1px;--decorator-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-color-1) 25%)}.property-enum-value-content[data-v-f4b54bdd]{display:flex;flex-direction:column;padding:3px 0}.property-enum-value-label[data-v-f4b54bdd]{color:var(--scalar-color-1);font-family:var(--scalar-font-code);font-size:var(--scalar-font-size-4);position:relative}.property-enum-value[data-v-f4b54bdd]:before{background-color:var(--decorator-color);content:"";display:block;margin-right:12px;width:var(--decorator-width)}.property-enum-value[data-v-f4b54bdd]:last-of-type:before,.property-enum-values:has(.enum-toggle-button) .property-enum-value[data-v-f4b54bdd]:nth-last-child(2):before{height:calc(.5lh + 4px)}.property-enum-value-label[data-v-f4b54bdd]:after{background-color:var(--decorator-color);content:"";height:var(--decorator-width);left:-12px;position:absolute;top:.5lh;width:8px}.property-enum-value[data-v-f4b54bdd]:last-of-type:after{background:var(--scalar-background-1);border-top:var(--scalar-border-width) solid var(--decorator-color);bottom:0;height:50%}.property-list[data-v-d5367294]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius);margin-top:10px}.property-list .property[data-v-d5367294]:last-of-type{padding-bottom:10px}.property-enum-values[data-v-d5367294]{font-size:var(--scalar-font-size-3);list-style:none;margin-top:8px;padding-left:2px}.property-default[data-v-4da5c70a],.property-example[data-v-72def0ea]{display:flex;flex-direction:column;font-size:var(--scalar-mini);position:relative}.property-default[data-v-4da5c70a]:hover:before,.property-example[data-v-72def0ea]:hover:before{border-radius:var(--scalar-radius);content:"";height:20px;left:0;position:absolute;top:0;width:100%}.property-default-label span[data-v-4da5c70a],.property-example-label span[data-v-72def0ea]{border-bottom:var(--scalar-border-width) dotted;color:var(--scalar-color-3);position:relative}.property-default-value[data-v-4da5c70a],.property-example-value[data-v-72def0ea]{align-items:center;display:flex;font-family:var(--scalar-font-code);gap:8px;padding:6px;width:100%;background:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius)}.property-default-value span[data-v-4da5c70a],.property-example-value span[data-v-72def0ea]{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.property-default-value-list[data-v-4da5c70a],.property-example-value-list[data-v-72def0ea]{background-color:var(--scalar-background-1);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);box-shadow:var(--scalar-shadow-1);display:none;flex-direction:column;gap:3px;left:50%;max-width:300px;min-width:200px;overflow:auto;padding:9px;position:absolute;top:18px;transform:translate3d(-50%,0,0);z-index:2}.property-default:focus-within .property-default-value-list[data-v-4da5c70a],.property-default:hover .property-default-value-list[data-v-4da5c70a]{display:flex}.property-detail+.property-detail[data-v-1295f965]:before,.schema>span[data-v-4df72868]:not(:first-child):before,.schema>span[data-v-f2ab7aa3]:not(:first-child):before{content:"·";display:block;margin:0 .5ch}code.property-detail-value[data-v-1295f965]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius);color:var(--scalar-color-2);font-family:var(--scalar-font-code);font-size:var(--scalar-font-size-3);padding:0 4px}.property-example:focus-within .property-example-value-list[data-v-72def0ea],.property-example:hover .property-example-value-list[data-v-72def0ea]{display:flex}.badge[data-v-3dedb7e4]{background:var(--badge-background-color,var(--scalar-background-2));border:var(--scalar-border-width) solid var(--badge-border-color,var(--scalar-border-color));border-radius:12px;color:var(--badge-text-color,var(--scalar-color-2));display:inline-block;font-size:var(--scalar-mini);padding:2px 6px}.badge.text-orange[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-orange),#0000 90%);border:#0000}.badge.text-yellow[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-yellow),#0000 90%);border:#0000}.badge.text-red[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-red),#0000 90%);border:#0000}.badge.text-purple[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-purple),#0000 90%);border:#0000}.badge.text-green[data-v-3dedb7e4]{background:color-mix(in srgb,var(--scalar-color-green),#0000 90%);border:#0000}.property[data-v-84242873]:not(:last-of-type),.section[data-v-be4443e9]:not(:last-of-type){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.property-heading[data-v-b69e3089]{align-items:baseline;display:flex;flex-wrap:wrap;row-gap:9px;white-space:nowrap}.property-heading[data-v-b69e3089]:has(+.children),.property-heading[data-v-b69e3089]:has(+.property-rule){margin-bottom:9px}.operation-details-card[data-v-55addca4] .contents button,.operation-details-card[data-v-55addca4] .selected-content-type,.property-heading[data-v-b69e3089]>*{margin-right:9px}.property-heading>.property-detail[data-v-b69e3089]:not(:last-of-type),.property-heading[data-v-b69e3089]:last-child{margin-right:0}.property-name[data-v-b69e3089]{font-size:var(--scalar-font-size-4);font-weight:var(--scalar-bold);max-width:100%;overflow-wrap:break-word;white-space:normal}.property-additional[data-v-b69e3089],.property-name[data-v-b69e3089],.scalar-app .\[\&_code\]\:font-code code,.scalar-app .font-code{font-family:var(--scalar-font-code)}.property-required[data-v-b69e3089]{color:var(--scalar-color-orange);font-size:var(--scalar-mini)}.property-read-only[data-v-b69e3089]{color:var(--scalar-color-blue);font-size:var(--scalar-mini)}.property-write-only[data-v-b69e3089]{color:var(--scalar-color-green);font-size:var(--scalar-mini)}.property-discriminator[data-v-b69e3089]{color:var(--scalar-color-purple);font-size:var(--scalar-mini)}.property-detail[data-v-b69e3089]{align-items:center;color:var(--scalar-color-2);display:flex;font-size:var(--scalar-mini);min-width:0}.deprecated[data-v-3c3c5405] *,.deprecated[data-v-ad8530a6],.deprecated[data-v-b69e3089],.deprecated[data-v-ec6c8861],.scalar-app .markdown del{-webkit-text-decoration:line-through;text-decoration:line-through}.property[data-v-84242873]{color:var(--scalar-color-1);display:flex;flex-direction:column;font-size:var(--scalar-small);padding:10px;position:relative}.property.property--level-0[data-v-84242873]:has(>.property-rule>.schema-card>.schema-properties.schema-properties-open>ul>li.property){padding-top:0}.property--compact.property--level-0[data-v-84242873],.property--compact.property--level-1[data-v-84242873]{padding:10px 0}.property--compact.property--level-0 .composition-panel .property--compact.property--level-1[data-v-84242873],.scalar-app .p-2{padding:8px}.property[data-v-84242873]:has(>.property-rule:first-of-type):not(.property--compact){padding-bottom:8px;padding-top:8px}.property--deprecated[data-v-84242873]{background:repeating-linear-gradient(-45deg,var(--scalar-background-2) 0,var(--scalar-background-2) 2px,#0000 2px,#0000 5px);background-size:100%}.property--deprecated[data-v-84242873]>*{opacity:.75}.property-description[data-v-84242873]{font-size:var(--scalar-small);line-height:1.4;margin-top:6px}.property-description[data-v-84242873]:has(+.property-rule){margin-bottom:9px}.request-body-description[data-v-17941e59] .markdown *,[data-v-84242873] .property-description *{color:var(--scalar-color-2)!important}.children+.property-rule[data-v-84242873],.property-description+.children[data-v-84242873]{margin-top:9px}.children .property--compact.property--level-1[data-v-84242873]{padding:12px}.property-example-value[data-v-84242873]{all:unset;border-top:var(--scalar-border-width) solid var(--scalar-border-color);font-family:var(--scalar-font-code)}.property-rule[data-v-84242873]{border-radius:var(--scalar-radius-lg);display:flex;flex-direction:column}.property-rule[data-v-84242873] .composition-panel .schema-card--level-1>.schema-properties.schema-properties-open,.start-section-colors .start-item-active[data-v-673f10b6]:last-of-type:before,.start-section-colors .start-item[data-v-673f10b6]:last-of-type{border-radius:0 0 var(--scalar-radius-lg) var(--scalar-radius-lg)}.property-rule[data-v-84242873] .composition-panel>.schema-card>.schema-card-description{border-left:var(--scalar-border-width) solid var(--scalar-border-color);border-right:var(--scalar-border-width) solid var(--scalar-border-color);padding:10px}.callback-operation-container[data-v-df994c31] .parameter-list,.callback-operation-container[data-v-df994c31] .request-body,.callback-operation-container[data-v-df994c31] .request-body-description,.callback-operation-container[data-v-df994c31] .request-body-header,.introduction-card-row[data-v-5764c94a] .scalar-card:nth-of-type(2) .scalar-card-header.scalar-card--borderless+.scalar-card-content,.property-rule[data-v-84242873] .composition-panel>.schema-card>.schema-card-description+.schema-properties,.scalar-app .mt-0{margin-top:0}.property-example[data-v-84242873]{background:#0000;border:none;display:flex;flex-direction:row;gap:8px}.property-example-label[data-v-84242873],.property-example-value[data-v-84242873]{padding:3px 0 0}.property-example-value[data-v-84242873]{background:var(--scalar-background-2);border-radius:var(--scalar-radius);border-top:0;padding:3px 4px}.collapsible-section[data-v-999a158a],.section-container[data-v-20a1472a]{border-top:var(--scalar-border-width) solid var(--scalar-border-color);position:relative}.property-name[data-v-84242873]{font-family:var(--scalar-font-code);font-weight:var(--scalar-semibold)}.property-name-additional-properties[data-v-84242873]:before,.property-name-pattern-properties[data-v-84242873]:before{background-color:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius);color:var(--scalar-color-1);display:inline-block;font-size:var(--scalar-micro);margin-right:4px;padding:2px 4px;text-transform:uppercase}.property-name-pattern-properties[data-v-84242873]:before{content:"regex"}.property-name-additional-properties[data-v-84242873]:before{content:"unknown property name"}.section-header-label[data-v-f1ac6c38]{display:inline}.reference-models-anchor[data-v-e4ca3c0f]{align-items:center;color:var(--scalar-color-1);display:flex;font-size:20px;padding-left:6px}.reference-models-label[data-v-e4ca3c0f]{display:block;font-size:var(--scalar-mini)}.reference-models-label[data-v-e4ca3c0f] em,.scalar-app .markdown b,.scalar-app .markdown strong{font-weight:var(--scalar-bold)}.section[data-v-be4443e9]{display:flex;flex-direction:column;margin:auto;max-width:var(--refs-content-max-width);padding:90px 0;position:relative;scroll-margin-top:var(--refs-viewport-offset)}.section[data-v-be4443e9]:has(~div.contents){border-bottom:var(--scalar-border-width) solid var(--scalar-border-color)}.references-classic .section[data-v-be4443e9]{gap:24px;padding:48px 0}.collapsible-section .collapsible-section-trigger[data-v-999a158a]{align-items:center;cursor:pointer;display:flex;font-size:var(--scalar-font-size-3);padding:10px 0;position:relative;z-index:1}.collapsible-section-trigger svg[data-v-999a158a]{color:var(--scalar-color-3);left:-19px;position:absolute}.collapsible-section .collapsible-section-trigger[data-v-999a158a] .anchor-copy{line-height:18.5px}.collapsible-section-content[data-v-999a158a]{margin:0 0 10px;padding:0;scroll-margin-top:140px}.section-container[data-v-20a1472a]{padding:0 60px;width:100%}.section-container[data-v-20a1472a]:has(.introduction-section){border-top:none}.section-accordion-wrapper[data-v-9419dd23]{padding:0 60px}.section-accordion[data-v-9419dd23]{margin:auto;max-width:var(--refs-content-max-width);position:relative;width:100%}.section-accordion-content[data-v-9419dd23]{display:flex;flex-direction:column;gap:12px;padding-top:12px}.section-accordion-button[data-v-9419dd23]{border-radius:var(--scalar-radius);cursor:pointer;display:flex;margin:-6px 0;padding:6px 0;width:100%}.section-accordion-chevron[data-v-9419dd23]{color:var(--scalar-color-3);left:-22px;position:absolute;top:12px}.section-accordion-title[data-v-9419dd23]{align-items:flex-start;display:flex;flex:1;flex-direction:column;padding:0 6px}.section-accordion-title[data-v-9419dd23] .section-header-wrapper{grid-template-columns:1fr}.show-more[data-v-d1c2b649]{align-items:center;appearance:none;border:none;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:30px;color:var(--scalar-color-1);display:flex;font-size:var(--scalar-small);font-weight:var(--scalar-semibold);gap:6px;justify-content:center;margin:auto;padding:8px 12px 8px 16px;position:relative;top:-48px}.show-more[data-v-d1c2b649]:hover{background:var(--scalar-background-2);cursor:pointer}.show-more[data-v-d1c2b649]:active{box-shadow:0 0 0 1px var(--scalar-border-color)}.loading[data-v-8e0226d7]{animation:1.5s infinite alternate h;background:var(--scalar-background-3);border-radius:var(--scalar-radius-lg);margin:.6em 0;max-width:100%;min-height:1.6em}.loading[data-v-8e0226d7]:first-of-type{margin-bottom:24px;margin-top:0;min-height:3em}.loading[data-v-8e0226d7]:last-of-type{width:60%}.loading.single-line[data-v-8e0226d7]{margin:.6em 0;max-width:80%;min-height:3em}@keyframes h{0%{opacity:1}to{opacity:.33}}.section-header-wrapper[data-v-465a7a78]{display:grid;grid-template-columns:1fr}.section-header[data-v-465a7a78]{color:var(--scalar-color-1);font-size:var(--font-size,var(--scalar-heading-1));font-weight:var(--font-weight,var(--scalar-bold));word-wrap:break-word;line-height:1.45;margin-bottom:12px;margin-top:0}.scalar-app .mb-1\.5,.section-header.tight[data-v-465a7a78]{margin-bottom:6px}.section-header.loading[data-v-465a7a78]{width:80%}.operation-path[data-v-ec6c8861]{overflow:hidden;font-weight:var(--scalar-semibold);line-break:anywhere}.headers-card[data-v-ab19704d]{align-self:flex-start;color:var(--scalar-color-1);font-size:var(--scalar-font-size-4);margin-bottom:6px;margin-top:12px;position:relative;z-index:0}.headers-card-title[data-v-ab19704d]{align-items:center;border-radius:13.5px;color:var(--scalar-color-3);display:flex;font-size:var(--scalar-micro);font-weight:var(--scalar-semibold);gap:4px;padding:6px 10px}.headers-properties[data-v-ab19704d]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:13.5px;display:flex;flex-direction:column;width:fit-content}.headers-properties-open[data-v-ab19704d]{border-radius:var(--scalar-radius-lg);width:100%}.headers-card .property[data-v-ab19704d]:last-of-type{padding-bottom:10px}.headers-card-title>.headers-card-title-icon[data-v-ab19704d]{height:14px;margin:0;width:14px}.headers-card-title>.headers-card-title-icon--open[data-v-ab19704d],.toggle-icon-sun-ray[data-v-60be8692]:nth-of-type(3){transform:rotate(45deg)}.parameter-item[data-v-be55ce3a]{border-top:var(--scalar-border-width) solid var(--scalar-border-color);display:flex;flex-direction:column;position:relative}.codeBlockStandalone_MEMb,.operation-details-card[data-v-55addca4] .request-body-schema .property--level-0,.parameter-item-container[data-v-be55ce3a],.scalar-app .p-0,.scalar-card-header.scalar-card-header-tabs[data-v-fec8fbbb],[data-v-04661eb4] .cm-editor,[data-v-28c8509c] .cm-editor,[data-v-3157c3c7] .cm-editor,[data-v-36811e28] .cm-editor,[data-v-83bfcc8a] .cm-editor,[data-v-c1a50a6e] .cm-editor,[data-v-cb2a35da] .cm-line,[data-v-ddfccc08] .cm-editor,[data-v-f5565642] .cm-line{padding:0}.parameter-item-name[data-v-be55ce3a]{color:var(--scalar-color-1);font-family:var(--scalar-font-code);font-size:var(--scalar-font-size-4);font-weight:var(--scalar-bold);overflow-wrap:break-word;position:relative}.parameter-item-description-summary[data-v-be55ce3a],.parameter-item-description[data-v-be55ce3a]{color:var(--scalar-color-2);font-size:var(--scalar-mini)}.parameter-item-description-summary.parameter-item-description-summary[data-v-be55ce3a]>*{--markdown-line-height:1}.parameter-item-trigger+.parameter-item-container[data-v-be55ce3a] .property--level-0>.property-heading .property-detail-value{font-size:var(--scalar-micro)}.parameter-item-required-optional[data-v-be55ce3a]{color:var(--scalar-color-2);font-weight:var(--scalar-semibold);margin-right:6px;position:relative}.parameter-item--required[data-v-be55ce3a]{color:var(--scalar-color-orange);font-size:var(--scalar-micro);font-weight:var(--scalar-semibold);text-transform:uppercase}.parameter-item-description[data-v-be55ce3a]{color:var(--scalar-color-2);font-size:var(--scalar-small);line-height:1.4;margin-top:6px}.parameter-item-description[data-v-be55ce3a] p{color:var(--scalar-color-2);font-size:var(--scalar-small);line-height:1.4;margin-top:4px}.parameter-schema[data-v-be55ce3a]{margin-top:3px;padding-bottom:9px}.parameter-item-trigger[data-v-be55ce3a]{align-items:baseline;display:flex;flex-wrap:wrap;gap:6px;line-height:var(--scalar-line-height-5);outline:0;padding:10px 0}.parameter-item-trigger-open[data-v-be55ce3a],.scalar-app .pb-0{padding-bottom:0}.parameter-item-icon[data-v-be55ce3a]{color:var(--scalar-color-3);left:-19px;position:absolute;top:.5lh;translate:0 -50%}.scalar-app .-translate-x-1\/2,.scalar-app .-translate-x-full,.scalar-app .-translate-y-1\.5,.scalar-app .-translate-y-1\/2,.scalar-app .centered,.scalar-app .centered-x,.scalar-app .centered-y,.scalar-app .translate-x-0,.scalar-app .translate-x-1\/2,.scalar-app .translate-x-2\.5,.scalar-app .translate-x-\[14px\],.scalar-app .translate-x-full,.scalar-app .translate-y-0,.scalar-app .translate-y-1\.5,.scalar-app .translate-y-1\/2{translate:var(--tw-translate-x) var(--tw-translate-y)}.parameter-item-trigger:focus-visible .parameter-item-icon[data-v-be55ce3a],.parameter-item-trigger:hover .parameter-item-icon[data-v-be55ce3a]{color:var(--scalar-color-1)}.parameter-item-trigger:focus-visible .parameter-item-icon[data-v-be55ce3a]{border-radius:var(--scalar-radius);outline:1px solid var(--scalar-color-accent);outline-offset:2px}.request-body[data-v-17941e59],.scalar-app .mt-6,.start-h1[data-v-673f10b6]:not(:first-of-type){margin-top:24px}.request-body-header[data-v-17941e59]{align-items:center;border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);display:flex;flex-flow:wrap;justify-content:space-between;padding-bottom:12px}.request-body-title[data-v-17941e59]{align-items:center;color:var(--scalar-color-1);display:flex;font-size:var(--scalar-font-size-2);font-weight:var(--scalar-semibold);gap:8px}.request-body-required[data-v-17941e59]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:16px;color:var(--scalar-color-orange);font-size:var(--scalar-micro);font-weight:400;height:20px;padding:2px 8px}.request-body-description[data-v-17941e59]{font-size:var(--scalar-small);margin-top:6px;width:100%}.request-body-header+.request-body-schema[data-v-17941e59]:has(>.schema-card>.schema-card-description),.request-body-header+.request-body-schema[data-v-17941e59]:has(>.schema-card>.schema-properties>*>.property--level-0){padding-top:8px}.callback-sticky-offset[data-v-df994c31]{top:var(--refs-viewport-offset,0);z-index:1}.callback-operation-container[data-v-df994c31] .request-body-header{--scalar-font-size-2:var(--scalar-font-size-4);background:color-mix(in srgb,var(--scalar-background-2) 50%,#0000);border-bottom:none;border:.5px solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg) var(--scalar-radius-lg) 0 0;padding:10px}.callback-operation-container[data-v-df994c31] .request-body-schema>.schema-card>.schema-card-description,.scalar-app .px-2{padding-inline:8px}.callback-operation-container[data-v-df994c31] ul li.property.property--level-1,.scalar-app .p-2\.5{padding:10px}.callback-operation-container[data-v-df994c31] .request-body-schema{background-color:var(--scalar-background-1);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:0 0 var(--scalar-radius-lg) var(--scalar-radius-lg);border-top:none;overflow:hidden}.callback-operation-container[data-v-df994c31] .parameter-list-title{background:color-mix(in srgb,var(--scalar-background-2) 50%,#0000);border:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom:none;border-radius:var(--scalar-radius-lg) var(--scalar-radius-lg) 0 0;margin-bottom:0;padding:10px;--scalar-font-size-2:var(--scalar-font-size-4)}.callback-operation-container[data-v-df994c31] .parameter-list-items{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:0 0 var(--scalar-radius-lg) var(--scalar-radius-lg)}.callback-operation-container[data-v-df994c31] .parameter-list-items>li:first-of-type{border-top:none}.callback-operation-container[data-v-df994c31] .parameter-list-items>li{padding:0 8px}.empty-state[data-v-fa0d0b45]{align-items:center;border-radius:var(--scalar-radius-lg);color:var(--scalar-color-2);display:flex;font-size:var(--scalar-mini);justify-content:center;margin:10px 0 10px 12px;min-height:56px;text-align:center}.rule-title[data-v-fa0d0b45]{border-radius:var(--scalar-radius);color:var(--scalar-color-1);display:inline-block;font-family:var(--scalar-font-code);margin:12px 0 6px}.endpoint-type[data-v-55addca4],.show-api-client-button[data-v-342ba62a],.tab[data-v-804dba49]{font-family:var(--scalar-font);position:relative}.rule[data-v-fa0d0b45]{border-radius:var(--scalar-radius-lg);margin:0 12px}.rule-items[data-v-fa0d0b45]{border-left:1px solid var(--scalar-border-color);counter-reset:list-number;display:flex;flex-direction:column;gap:12px;padding:12px 0}.rule-item[data-v-fa0d0b45]{border:1px solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);counter-increment:list-number;margin-left:24px;overflow:hidden}.rule-item[data-v-fa0d0b45]:before{border:1px solid var(--scalar-border-color);border-radius:0 0 0 var(--scalar-radius-lg);border-right:0;border-top:0;color:var(--scalar-color-2);color:var(--scalar-color-1);content:" ";display:block;height:6px;margin-top:6px;position:absolute;transform:translate(-25px);width:24px}.tab[data-v-804dba49]{background:none;border:none;color:var(--scalar-color-2);cursor:pointer;font-size:var(--scalar-small);font-weight:var(--scalar-font-normal);line-height:calc(var(--scalar-small) + 2px);line-height:22px;margin-right:3px;padding:0;text-transform:uppercase;white-space:nowrap}.tab[data-v-804dba49]:before{background:var(--scalar-background-3);border-radius:var(--scalar-radius);content:"";height:calc(100% + 4px);left:-6px;opacity:0;position:absolute;top:-2px;width:calc(100% + 12px);z-index:0}.tab[data-v-804dba49]:focus-visible:before,.tab[data-v-804dba49]:hover:before{opacity:1}.tab[data-v-804dba49]:focus-visible:before{outline:1px solid var(--scalar-color-accent)}.tab span[data-v-804dba49]{position:relative;z-index:1}.tab-selected[data-v-804dba49]{color:var(--scalar-color-1);font-weight:var(--scalar-semibold)}.tab-selected[data-v-804dba49]:after{background:currentColor;bottom:calc(var(--tab-list-padding-y)*-1);content:"";height:1px;left:0;position:absolute;width:100%}.tab-list[data-v-fec8fbbb]{display:flex;flex:1;gap:6px;position:relative;--tab-list-padding-y:7px;--tab-list-padding-x:12px;overflow:auto;padding:var(--tab-list-padding-y) var(--tab-list-padding-x)}.request-card[data-v-cbc307e3],.response-card[data-v-dc47ca65]{font-size:var(--scalar-font-size-3)}.code-copy[data-v-dc47ca65]{align-items:center;appearance:none;-webkit-appearance:none;background:#0000;border:none;color:var(--scalar-color-3);cursor:pointer;display:flex;justify-content:center;margin-right:12px;outline:0;padding:0}.client-libraries[data-v-6a49c111]:active,.code-copy[data-v-dc47ca65]:hover,.hljs.language-curl .hljs-literal,.hljs.language-html,.hljs.language-objectivec .hljs-meta,.scalar-app .\[\&_a\:hover\]\:text-c-1 a:hover,.scalar-app .\[\&_em\]\:text-c-1 em,.scalar-app .hljs-attr,.scalar-app .hljs-attribute,.scalar-app .hljs-selector-id,.scalar-app .hljs-template-variable,.scalar-app .hljs-title.class_,.scalar-app .hljs-variable,.scalar-app .text-c-1{color:var(--scalar-color-1)}.code-copy svg[data-v-dc47ca65],.scalar-app .size-3\.25{height:13px;width:13px}.response-card-footer[data-v-dc47ca65]{display:flex;flex-direction:row;flex-shrink:0;gap:8px;justify-content:space-between;padding:7px 12px}.response-example-selector[data-v-dc47ca65]{align-self:flex-start;margin:-4px}.response-description[data-v-dc47ca65]{align-items:center;box-sizing:border-box;color:var(--scalar-color--1);display:flex;font-size:var(--scalar-small);font-weight:var(--scalar-semibold)}.schema-type[data-v-dc47ca65]{background:var(--scalar-background-3);border-radius:4px;margin-right:4px;padding:2px 4px}.schema-example[data-v-dc47ca65],.schema-type[data-v-dc47ca65]{color:var(--scalar-color-2);font-size:var(--scalar-micro);font-weight:var(--scalar-semibold)}.example-response-tab[data-v-dc47ca65]{display:block;margin:6px}.scalar-card-checkbox[data-v-dc47ca65]{align-items:center;color:var(--scalar-color-2);cursor:pointer;display:flex;font-size:var(--scalar-small);font-weight:var(--scalar-font-normal);gap:6px;justify-content:center;min-height:17px;padding:7px 6px;position:relative;-webkit-user-select:none;user-select:none;white-space:nowrap;width:fit-content}.scalar-card-checkbox:has(.scalar-card-checkbox-input:focus-visible) .scalar-card-checkbox-checkmark[data-v-dc47ca65]{outline:1px solid var(--scalar-color-accent)}.scalar-card-checkbox[data-v-dc47ca65]:hover{color:var(--scalar-color--1)}.scalar-card-checkbox .scalar-card-checkbox-input[data-v-dc47ca65]{cursor:pointer;height:0;opacity:0;position:absolute;width:0}.scalar-card-checkbox-checkmark[data-v-dc47ca65]{background-color:initial;background-color:var(--scalar-background-3);border-radius:var(--scalar-radius);box-shadow:inset 0 0 0 var(--scalar-border-width) var(--scalar-border-color);height:16px;width:16px}.scalar-card-checkbox[data-v-dc47ca65]:has(.scalar-card-checkbox-input:checked){color:var(--scalar-color-1);font-weight:var(--scalar-semibold)}.scalar-card-checkbox .scalar-card-checkbox-input:checked~.scalar-card-checkbox-checkmark[data-v-dc47ca65]{background-color:var(--scalar-button-1);box-shadow:none}.scalar-card-checkbox-checkmark[data-v-dc47ca65]:after{content:"";display:none;position:absolute}.scalar-card-checkbox .scalar-card-checkbox-checkmark[data-v-dc47ca65]:after{border-width:1px;border-bottom:1.5px solid var(--scalar-button-1-color);border-left:0 solid var(--scalar-button-1-color);border-right:1.5px solid var(--scalar-button-1-color);border-top:0 solid var(--scalar-button-1-color);height:9px;right:11.5px;top:12.5px;transform:rotate(45deg);width:5px}.show-api-client-button[data-v-342ba62a]{align-items:center;appearance:none;background:var(--scalar-button-1);border:none;border-radius:var(--scalar-radius);box-shadow:inset 0 0 0 1px #0000001a;box-sizing:border-box;color:var(--scalar-background-2);cursor:pointer;display:flex;font-size:var(--scalar-small);font-weight:var(--scalar-semibold);justify-content:center;line-height:22px;outline-offset:2px;padding:1px 6px;white-space:nowrap}.show-api-client-button span[data-v-342ba62a],.show-api-client-button svg[data-v-342ba62a]{color:var(--scalar-button-1-color);fill:currentColor;z-index:1}.show-api-client-button[data-v-342ba62a]:hover{background:var(--scalar-button-1-hover)}.scalar-app .mr-1,.show-api-client-button svg[data-v-342ba62a]{margin-right:4px}.operation-details[data-v-55addca4]{align-items:center;display:flex;flex-shrink:1;gap:9px;margin-top:0;min-width:0}.operation-details[data-v-55addca4] .endpoint-anchor .scalar-button svg{height:16px;width:16px}.endpoint-type[data-v-55addca4]{align-items:center;display:flex;flex-shrink:0;font-size:var(--scalar-small);font-weight:var(--scalar-bold);gap:6px;justify-content:center;padding:6px;text-transform:uppercase;width:60px;z-index:0}.endpoint-type[data-v-55addca4]:after{background:currentColor;border-radius:var(--scalar-radius);content:"";inset:0;opacity:.15;position:absolute;z-index:-1}.endpoint-anchor[data-v-55addca4]{align-items:center;display:flex;flex-shrink:1;min-width:0}.endpoint-anchor.label[data-v-55addca4]{display:flex}.endpoint-label[data-v-55addca4]{align-items:baseline;color:var(--scalar-color-1);display:flex;flex-shrink:1;gap:9px;min-width:0}.endpoint-label-path[data-v-55addca4]{font-family:var(--scalar-font-code);font-size:var(--scalar-mini);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.endpoint-label-name[data-v-55addca4]{color:var(--scalar-color-2);flex-shrink:1000000000;font-size:var(--scalar-small);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.endpoint-try-hint[data-v-55addca4]{flex-shrink:0;padding:2px}.endpoint-copy[data-v-55addca4],.scalar-app .text-current{color:currentColor}.endpoint-copy[data-v-55addca4] svg,.scalar-app .stroke-2{stroke-width:2px}.endpoint-content[data-v-55addca4]{display:grid;gap:9px;grid-auto-columns:1fr;grid-auto-flow:row;padding:9px}.endpoint-content[data-v-55addca4]>*,.scalar-app .min-w-0{min-width:0}.operation-details-card[data-v-55addca4]{display:flex;flex-direction:column;gap:12px;min-width:0}:is(.operation-details-card-item[data-v-55addca4] .parameter-list,.operation-details-card-item[data-v-55addca4] .callbacks-list){border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);margin-top:0}.introduction-card[data-v-5764c94a],.operation-details-card-item[data-v-55addca4]{display:flex;flex-direction:column;gap:12px}.operation-details-card[data-v-55addca4] .parameter-item:last-of-type .parameter-schema,.scalar-app .pb-3{padding-bottom:12px}.operation-details-card[data-v-55addca4] .parameter-item{margin:0;padding:0}.operation-details-card[data-v-55addca4] .property{margin:0;padding:9px}:is(.operation-details-card[data-v-55addca4] .parameter-list-title,.operation-details-card[data-v-55addca4] .request-body-title,.operation-details-card[data-v-55addca4] .callbacks-title){color:var(--scalar-color-2);font-size:var(--scalar-mini);font-weight:var(--scalar-bold);line-height:1.33;margin:0;padding:9px;text-transform:uppercase}.operation-details-card[data-v-55addca4] .callback-list-item-title{padding-left:28px;padding-right:12px}.operation-details-card[data-v-55addca4] .callback-list-item-icon{left:6px}.operation-details-card[data-v-55addca4] .callback-operation-container{padding-inline:9px;padding-bottom:9px}:is(.operation-details-card[data-v-55addca4] .callback-operation-container>.request-body,.operation-details-card[data-v-55addca4] .callback-operation-container>.parameter-list){border:none}.operation-details-card[data-v-55addca4] .callback-operation-container>.request-body>.request-body-header{border-bottom:var(--scalar-border-width) solid var(--scalar-border-color);padding:0 0 9px}.operation-details-card[data-v-55addca4] .request-body-description{border-top:var(--scalar-border-width) solid var(--scalar-border-color);margin-top:0;padding:9px 9px 0}.operation-details-card[data-v-55addca4] .request-body{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);margin-top:0}.operation-details-card[data-v-55addca4] .request-body .schema-card--level-0>.schema-card-description{padding-inline:9px}.operation-details-card[data-v-55addca4] .request-body-header{border-bottom:0;padding-bottom:0}.operation-details-card[data-v-55addca4] .schema-card--open+.schema-card:not(.schema-card--open){margin-inline:9px;margin-bottom:9px}.operation-example-card[data-v-55addca4]{max-height:calc(var(--refs-viewport-height) - 48px);position:sticky;top:calc(var(--refs-viewport-offset) + 24px)}.ask-agent-scalar-input[data-v-f43e32ab]{all:initial;align-items:center;appearance:none;border:none;box-sizing:border-box;color:var(--scalar-color-1);cursor:pointer;display:flex;field-sizing:content;font-family:var(--scalar-font);font-size:var(--scalar-small);font-weight:var(--scalar-regular);justify-content:center;line-height:22px;max-width:88px;outline:0;outline-offset:2px;position:relative;white-space:nowrap}.ask-agent-scalar-input[data-v-f43e32ab]:focus{cursor:text;max-width:calc(100% - 50px);width:100%!important}.ask-agent-scalar-input[data-v-f43e32ab]:not(:placeholder-shown){cursor:text;field-sizing:border-box;height:100%;max-width:calc(100% - 50px);width:100%!important}.ask-agent-scalar-input[data-v-f43e32ab]::placeholder{color:var(--scalar-color-1);font-family:var(--scalar-font)}.ask-agent-scalar-input[data-v-f43e32ab]:focus::placeholder{color:var(--scalar-color-2)}.agent-button-container[data-v-f43e32ab]{align-items:center;background:color-mix(in srgb,var(--scalar-background-3),#fff 15%);border-radius:var(--scalar-radius);color:var(--scalar-color-1);display:flex;gap:4px;height:100%;margin-right:4px;padding:1px 6px;position:relative;z-index:2}.agent-button-container[data-v-f43e32ab]:hover:not(:focus-within){background:color-mix(in srgb,var(--scalar-background-3),#fff 20%)}.agent-button-container[data-v-f43e32ab]:focus-within,.agent-button-container[data-v-f43e32ab]:has(.ask-agent-scalar-input:not(:placeholder-shown)){border-radius:var(--scalar-radius);height:calc(100% - 4px);left:2px;position:absolute;top:2px;width:calc(100% - 4px)}.ask-agent-scalar-send[data-v-f43e32ab]{align-items:center;background:var(--scalar-color-blue);border-radius:var(--scalar-radius);color:#fff;display:none;flex-shrink:0;height:24px;justify-content:center;margin-left:auto;width:24px}.agent-button-container:has(.ask-agent-scalar-input:not(:placeholder-shown)) .ask-agent-scalar-send[data-v-f43e32ab]{display:flex}.section-columns[data-v-8b9602bf]{display:flex;gap:48px}@container narrow-references-container (max-width: 900px){.references-classic-header[data-v-8a3822ca]{padding:12px 24px}.references-classic-header-container[data-v-8a3822ca],.section-container[data-v-20a1472a]{padding:0}.references-classic .section[data-v-be4443e9],.section[data-v-be4443e9]{padding:48px 24px}.section-accordion-chevron[data-v-9419dd23]{left:-16px;top:14px;width:16px}.section-accordion-wrapper[data-v-9419dd23]{padding:calc(var(--refs-viewport-offset)) 24px 0 24px}.show-more[data-v-d1c2b649]{top:-24px}.section-columns[data-v-8b9602bf],.section-content--with-columns[data-v-9735459e]{flex-direction:column;gap:24px}.section-column[data-v-699c28e3]:nth-of-type(2){padding-top:0}}.examples[data-v-3c3c5405]{position:sticky;top:calc(var(--refs-viewport-offset) + 24px)}.examples[data-v-3c3c5405]>*{max-height:calc((var(--refs-viewport-height) - 60px)/ 2);position:relative}.examples[data-v-3c3c5405]>:first-of-type:last-of-type{max-height:calc(var(--refs-viewport-height) - 60px)}.start-section[data-v-673f10b6]:last-of-type,.tag-section[data-v-1124be5d]{margin-bottom:48px}.tag-description[data-v-1124be5d]{padding-bottom:4px;text-align:left}.endpoint[data-v-ad8530a6]{cursor:pointer;display:flex;-webkit-text-decoration:none;text-decoration:none;white-space:nowrap}.download-button span[data-v-59b5011b],.download-link[data-v-59b5011b],.scalar-app .markdown a{--font-visited:var(--scalar-link-color-visited,var(--scalar-color-2));font-weight:var(--scalar-link-font-weight,var(--scalar-semibold));-webkit-text-decoration:var(--scalar-text-decoration);text-decoration:var(--scalar-text-decoration);text-decoration-thickness:1px;text-underline-offset:.25rem}.endpoint:focus-visible .endpoint-path[data-v-ad8530a6],.endpoint:hover .endpoint-path[data-v-ad8530a6]{-webkit-text-decoration:underline;text-decoration:underline}.endpoint .delete[data-v-ad8530a6],.endpoint .get[data-v-ad8530a6],.endpoint .post[data-v-ad8530a6],.endpoint .put[data-v-ad8530a6],.scalar-app .whitespace-nowrap{white-space:nowrap}.endpoint-method[data-v-ad8530a6],.endpoint-path[data-v-ad8530a6]{color:var(--scalar-color-1);cursor:pointer;display:inline-flex;font-family:var(--scalar-font-code);font-size:var(--scalar-small);line-height:1.55;min-width:62px}.endpoint-method[data-v-ad8530a6],.scalar-app .markdown tr>[align=right],.scalar-app .text-right{text-align:right}.endpoint-path[data-v-ad8530a6]{margin-left:12px;text-transform:none}.endpoints-card[data-v-f726f753]{font-size:var(--scalar-font-size-3);position:sticky;top:calc(var(--refs-viewport-offset) + 24px)}.endpoints[data-v-f726f753]{background:var(--scalar-background-2);overflow:auto;padding:10px 12px;width:100%}.section-container[data-v-0d42fbc8]{border-top:var(--scalar-border-width) solid var(--scalar-border-color)}.section-container[data-v-0d42fbc8]:has(.show-more){background-color:color-mix(in srgb,var(--scalar-background-2),#0000)}.section-flare[data-v-2a9c8c02]{pointer-events:none;right:0;top:0}.download-container[data-v-59b5011b]{display:flex;flex-direction:column;gap:16px;margin:0 .5px 8px;position:relative;width:fit-content;z-index:1}.download-container.download-both[data-v-59b5011b]:hover:before,.download-container[data-v-59b5011b]:has(:focus-visible):before{background:var(--scalar-background-1);border-radius:var(--scalar-radius-lg);box-shadow:var(--scalar-shadow-2);content:"";height:90px;left:-12px;pointer-events:none;position:absolute;top:-11px;width:calc(100% + 24px)}.download-button[data-v-59b5011b]{align-items:center;color:var(--scalar-link-color);cursor:pointer;display:flex;gap:4px;height:fit-content;justify-content:center;outline:0;padding:0;position:relative;white-space:nowrap!important}.download-button[data-v-59b5011b]:before{border-radius:var(--scalar-radius);content:"";height:calc(100% + 16px);left:-9px;position:absolute;top:-8px;width:calc(100% + 18px)}.download-button[data-v-59b5011b]:last-of-type:before{width:calc(100% + 15px)}.download-button[data-v-59b5011b]:hover:before{background:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color)}.download-button[data-v-59b5011b]:focus-visible:before{background:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color);outline-style:var(--tw-outline-style);outline-width:1px}.download-button span[data-v-59b5011b]{--font-color:var(--scalar-link-color,var(--scalar-color-accent));color:var(--font-color);text-decoration-color:var(--font-color);align-items:center;display:flex;gap:6px;line-height:1.625;z-index:1}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .to-b-1\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}.scalar-app .markdown a{text-decoration-color:color-mix(in srgb,var(--font-color)30%,#0000)}.scalar-app .markdown .markdown-alert.markdown-alert-note{border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-blue),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-2),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-orange),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-red),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-success{border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-green),#0000 50%)}.download-button span[data-v-59b5011b]{text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000)}}.download-link[data-v-59b5011b],.download-link[data-v-59b5011b]:hover,.scalar-app .markdown a{--font-color:var(--scalar-link-color,var(--scalar-color-accent));text-decoration-color:var(--font-color)}.download-button:hover span[data-v-59b5011b],.scalar-app .markdown a:hover{color:var(--scalar-link-color-hover,var(--scalar-color-accent));text-decoration-color:var(--scalar-color-1,currentColor);-webkit-text-decoration:var(--scalar-text-decoration-hover);text-decoration:var(--scalar-text-decoration-hover)}.download-button[data-v-59b5011b]:nth-of-type(2){border-width:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.download-container:has(:focus-visible) .download-button[data-v-59b5011b]:nth-of-type(2),.download-container:hover .download-button[data-v-59b5011b]:nth-of-type(2){clip-path:none;height:auto;margin:0;overflow:visible;padding:0;position:absolute;top:42px;white-space:normal;width:auto}.extension[data-v-59b5011b]{background:var(--scalar-link-color,var(--scalar-color-accent));color:var(--scalar-background-1);z-index:1}.download-container:has(:focus-visible) .extension[data-v-59b5011b],.download-container:hover .extension[data-v-59b5011b]{opacity:1}.download-link[data-v-59b5011b],.scalar-app .markdown a{color:var(--font-color)}.introduction-card-row[data-v-5764c94a],.scalar-app .gap-6{gap:24px}@container (max-width: 900px){.endpoint-content[data-v-55addca4]{grid-template-columns:1fr}.introduction-card-row[data-v-5764c94a]{align-items:stretch;flex-direction:column;gap:0}}.introduction-card[data-v-5764c94a] .security-scheme-label{font-weight:var(--scalar-semibold);text-transform:uppercase}.references-classic .introduction-description[data-v-5065bb4a] img,.scalar-app .max-w-\[720px\]{max-width:720px}.sticky-cards[data-v-0b1e2255]{display:flex;flex-direction:column;position:sticky;top:calc(var(--refs-viewport-offset) + 24px)}.introduction-card-item[data-v-dfab866f]{display:flex;flex-direction:column;justify-content:flex-start}.introduction-card-item[data-v-dfab866f]:has(.description) .server-form-container{border-bottom-left-radius:0;border-bottom-right-radius:0}.introduction-card-item[data-v-dfab866f] .request-item{border-bottom:0}.client-libraries-content[data-v-6a49c111]{background-color:var(--scalar-background-1);border-left:var(--scalar-border-width) solid var(--scalar-border-color);border-right:var(--scalar-border-width) solid var(--scalar-border-color);container:client-libraries-content/inline-size;display:flex;justify-content:center;overflow:hidden;padding:0 12px}.client-libraries[data-v-6a49c111]{align-items:center;border-bottom:1px solid #0000;color:var(--scalar-color-3);cursor:pointer;display:flex;gap:6px;justify-content:center;padding:8px 2px;position:relative;-webkit-user-select:none;user-select:none;white-space:nowrap;width:100%}.client-libraries[data-v-6a49c111]:not(.client-libraries__active):hover:before{background:var(--scalar-background-2);border-radius:var(--scalar-radius);content:"";height:calc(100% - 4px);left:2px;position:absolute;top:2px;width:calc(100% - 4px);z-index:0}.client-libraries[data-v-6a49c111]:focus-visible{box-shadow:inset 0 0 0 1px var(--scalar-color-accent);outline:0}.client-libraries-icon[data-v-6a49c111]{align-items:center;aspect-ratio:1;box-sizing:border-box;color:currentColor;display:flex;justify-content:center;max-height:14px;max-width:14px;min-width:14px;position:relative;width:100%}.client-libraries-icon__more svg[data-v-6a49c111],.img_ev3q,.scalar-app .h-auto{height:auto}@container client-libraries-content (width < 400px){.client-libraries__select[data-v-6a49c111]{width:fit-content}.client-libraries__select .client-libraries-icon__more+span[data-v-6a49c111]{display:none}}@container client-libraries-content (width < 380px){.client-libraries[data-v-6a49c111]{width:100%}.client-libraries span[data-v-6a49c111]{display:none}}.client-libraries__active[data-v-6a49c111]{border-bottom:1px solid var(--scalar-color-1);color:var(--scalar-color-1)}.client-libraries .client-libraries-text[data-v-6a49c111]{align-items:center;display:flex;font-size:var(--scalar-small);position:relative}.client-libraries__active .client-libraries-text[data-v-6a49c111]{color:var(--scalar-color-1);font-weight:var(--scalar-semibold)}.selected-client[data-v-dd2e9b07]{background:var(--scalar-background-1);border-top:none;border:var(--scalar-border-width) solid var(--scalar-border-color);border-bottom-left-radius:var(--scalar-radius-xl);border-bottom-right-radius:var(--scalar-radius-xl);color:var(--scalar-color-1);font-family:var(--scalar-font-code);font-size:var(--scalar-small);min-height:fit-content;overflow:hidden;padding:9px 12px;text-overflow:ellipsis;white-space:nowrap}.client-libraries-heading[data-v-dd2e9b07]{align-items:center;background-color:var(--scalar-background-2);border:var(--scalar-border-width) solid var(--scalar-border-color);border-top-left-radius:var(--scalar-radius-xl);border-top-right-radius:var(--scalar-radius-xl);color:var(--scalar-color-1);display:flex;font-size:var(--scalar-small);font-weight:var(--scalar-font-medium);max-height:32px;padding:9px 12px}.scalar-app .mt-2,[data-v-dd2e9b07] .scalar-codeblock-pre .hljs{margin-top:8px}.narrow-references-container{container-name:narrow-references-container;container-type:inline-size}.ref-search-meta[data-v-c1c368f9]{background:var(--scalar-background-1);border-bottom-left-radius:var(--scalar-radius-lg);border-bottom-right-radius:var(--scalar-radius-lg);border-top:var(--scalar-border-width) solid var(--scalar-border-color);color:var(--scalar-color-3);display:flex;font-size:var(--scalar-font-size-4);font-weight:var(--scalar-semibold);gap:12px;padding:6px 12px}.agent-scalar[data-v-b45a83b1]{animation:.35s forwards i;background:var(--scalar-background-1);border-right:var(--scalar-border-width) solid var(--scalar-border-color);box-shadow:var(--scalar-shadow-2);height:100dvh;left:0;position:fixed;top:0;transform:translate3d(calc(-100% + var(--scalar-sidebar-width,288px)),0,0);width:calc(100% - 50px);z-index:2}.agent-scalar-container[data-v-b45a83b1]{height:100%;margin-left:auto;overflow:auto;padding:0 24px;width:calc(100% - var(--scalar-sidebar-width,288px))}.scalar-app-exit[data-v-b45a83b1]{cursor:pointer;height:100vh;left:0;position:fixed;top:0;transition:.3s ease-in-out;width:100vw;z-index:2}.scalar-app-exit-animation[data-v-b45a83b1]:before{animation:.5s cubic-bezier(.77,0,.175,1) forwards u;background:#00000038;content:"";height:100%;position:absolute;width:100%}.dark-mode .scalar .scalar-app-exit-animation[data-v-b45a83b1]:before,.dark-mode .scalar .scalar-app-exit[data-v-6118685a]{background:#00000073}@keyframes i{0%{transform:translate3d(calc(-100% + var(--scalar-sidebar-width,288px)),0,0)}to{transform:translateZ(0)}}.app-exit-button[data-v-192fe698],.app-exit-button[data-v-b45a83b1],.app-exit-button[data-v-c8df97c6]{background:#0000001a;color:#fff}.app-exit-button[data-v-192fe698]:hover,.app-exit-button[data-v-b45a83b1]:hover,.app-exit-button[data-v-c8df97c6]:hover{background:#ffffff1a}@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,::backdrop,:after,:before{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-mask-linear:linear-gradient(#fff,#fff);--tw-mask-radial:linear-gradient(#fff,#fff);--tw-mask-conic:linear-gradient(#fff,#fff);--tw-mask-left:linear-gradient(#fff,#fff);--tw-mask-right:linear-gradient(#fff,#fff);--tw-mask-bottom:linear-gradient(#fff,#fff);--tw-mask-top:linear-gradient(#fff,#fff);--tw-mask-top-from-position:0%;--tw-mask-top-to-position:100%;--tw-mask-top-from-color:#000;--tw-mask-top-to-color:#0000;--tw-mask-bottom-from-position:0%;--tw-mask-bottom-to-position:100%;--tw-mask-bottom-from-color:#000;--tw-mask-bottom-to-color:#0000;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-mask-linear:linear-gradient(#fff,#fff);--tw-mask-radial:linear-gradient(#fff,#fff);--tw-mask-conic:linear-gradient(#fff,#fff);--tw-mask-left:linear-gradient(#fff,#fff);--tw-mask-right:linear-gradient(#fff,#fff);--tw-mask-bottom:linear-gradient(#fff,#fff);--tw-mask-top:linear-gradient(#fff,#fff);--tw-mask-top-from-position:0%;--tw-mask-top-to-position:100%;--tw-mask-top-from-color:#000;--tw-mask-top-to-color:#0000;--tw-mask-bottom-from-position:0%;--tw-mask-bottom-to-position:100%;--tw-mask-bottom-from-color:#000;--tw-mask-bottom-to-color:#0000;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-space-x-reverse:0}[data-v-59b5011b],[data-v-59b5011b]::backdrop,[data-v-59b5011b]:after,[data-v-59b5011b]:before{--tw-outline-style:solid}}}@layer scalar-base{body{line-height:inherit;line-height:inherit;background-color:var(--scalar-background-1);margin:0}:host,:root{--leading-snug:1.375;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--leading-relaxed:1.625;--leading-snug:1.375;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--leading-normal:1.5}@supports (color:color-mix(in lab,red,red)){.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}.dark-mode,.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red);--scalar-sidebar-search-background:var(--scalar-background-2)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1)95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1) 95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1)20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1)20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1)20%)}.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1) 20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1) 20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 20%)}}.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1) 20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1) 20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 20%)}.dark-mode,.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1) 95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1));--scalar-sidebar-search-background:var(--scalar-background-2);--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red)}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red);--scalar-sidebar-search-background:var(--scalar-background-2)}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1) 95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1) 20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1) 20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 20%)}}}}:root{--scalar-border-width:.5px;--scalar-radius:3px;--scalar-radius-lg:6px;--scalar-radius-xl:8px;--scalar-font:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;--scalar-font-code:"JetBrains Mono",ui-monospace,Menlo,Monaco,"Cascadia Mono","Segoe UI Mono","Roboto Mono","Oxygen Mono","Ubuntu Monospace","Source Code Pro","Fira Mono","Droid Sans Mono","Courier New",monospace;--scalar-heading-1:24px;--scalar-page-description:16px;--scalar-heading-2:20px;--scalar-heading-3:16px;--scalar-heading-4:16px;--scalar-heading-5:16px;--scalar-heading-6:16px;--scalar-paragraph:16px;--scalar-small:14px;--scalar-mini:13px;--scalar-micro:12px;--scalar-bold:600;--scalar-semibold:500;--scalar-regular:400;--scalar-font-size-1:21px;--scalar-font-size-2:16px;--scalar-font-size-3:14px;--scalar-font-size-4:13px;--scalar-font-size-5:12px;--scalar-font-size-6:12px;--scalar-font-size-7:10px;--scalar-line-height-1:32px;--scalar-line-height-2:24px;--scalar-line-height-3:20px;--scalar-line-height-4:18px;--scalar-line-height-5:16px;--scalar-font-normal:400;--scalar-font-medium:500;--scalar-font-bold:700;--scalar-text-decoration:none;--scalar-link-font-weight:inherit;--scalar-sidebar-indent:20px;--scalar-text-decoration:underline;--scalar-text-decoration-hover:underline}.dark-mode{--csstools-color-scheme--light: ;color-scheme:dark;--scalar-scrollbar-color:#ffffff2e;--scalar-scrollbar-color-active:#ffffff5c;--scalar-shadow-1:0 1px 3px 0 #0000001a;--scalar-shadow-2:0 0 0 .5px var(--scalar-border-color),#0f0f0f33 0px 3px 6px,#0f0f0f66 0px 9px 24px;--scalar-lifted-brightness:1.45;--scalar-backdrop-brightness:.5}.light-mode{--csstools-color-scheme--light:initial;color-scheme:light;--scalar-scrollbar-color-active:#0000005c;--scalar-scrollbar-color:#0000002e;--scalar-shadow-1:0 1px 3px 0 #0000001c;--scalar-shadow-2:#00000014 0px 13px 20px 0px,#00000014 0px 3px 8px 0px,#eeeeed 0px 0 0 .5px;--scalar-lifted-brightness:1;--scalar-backdrop-brightness:1;--scalar-background-1:#fff;--scalar-background-2:#f6f6f6;--scalar-background-3:#e7e7e7;--scalar-background-accent:#8ab4f81f;--scalar-color-1:#1b1b1b;--scalar-color-2:#757575;--scalar-color-3:#8e8e8e;--scalar-color-accent:#09f;--scalar-border-color:#dfdfdf;--scalar-color-green:#069061;--scalar-color-red:#ef0006;--scalar-color-yellow:#edbe20;--scalar-color-blue:#0082d0;--scalar-color-orange:#ff5800;--scalar-color-purple:#5203d1;--scalar-button-1:#000;--scalar-button-1-hover:#000c;--scalar-button-1-color:#ffffffe6;--scalar-tooltip-background:#1a1a1ae6;--scalar-tooltip-color:#ffffffd9;--scalar-color-alert:var(--scalar-color-orange);--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red)}.light-mode .dark-mode{color-scheme:dark!important}.dark-mode,.light-mode{--scalar-text-decoration-color:currentColor;--scalar-text-decoration-color-hover:currentColor;--scalar-link-color:var(--scalar-color-1);--scalar-link-color-hover:var(--scalar-link-color);--scalar-sidebar-background-1:var(--scalar-background-1);--scalar-sidebar-color-1:var(--scalar-color-1);--scalar-sidebar-color-2:var(--scalar-color-2);--scalar-sidebar-border-color:var(--scalar-border-color);--scalar-sidebar-item-hover-background:var(--scalar-background-2);--scalar-sidebar-item-hover-color:var(--scalar-sidebar-color-2);--scalar-sidebar-item-active-background:var(--scalar-background-2);--scalar-sidebar-color-active:var(--scalar-sidebar-color-1);--scalar-sidebar-indent-border:var(--scalar-sidebar-border-color);--scalar-sidebar-indent-border-hover:var(--scalar-sidebar-border-color);--scalar-sidebar-indent-border-active:var(--scalar-sidebar-border-color);--scalar-sidebar-search-background:var(--scalar-background-2);--scalar-sidebar-search-color:var(--scalar-color-3);--scalar-sidebar-search-border-color:var(--scalar-border-color)}.dark-mode{--scalar-background-1:#0f0f0f;--scalar-background-2:#1a1a1a;--scalar-background-3:#272727;--scalar-color-1:#e7e7e7;--scalar-color-2:#a4a4a4;--scalar-color-3:#797979;--scalar-color-accent:#00aeff;--scalar-background-accent:#3ea6ff1f;--scalar-border-color:#2d2d2d;--scalar-color-green:#00b648;--scalar-color-red:#dc1b19;--scalar-color-yellow:#ffc90d;--scalar-color-blue:#4eb3ec;--scalar-color-orange:#ff8d4d;--scalar-color-purple:#b191f9;--scalar-button-1:#fff;--scalar-button-1-hover:#ffffffe6;--scalar-button-1-color:#000;--scalar-tooltip-background:var(--scalar-background-1);--scalar-tooltip-color:#fffffff2;--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red);--scalar-sidebar-search-background:var(--scalar-background-2)}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red);--scalar-sidebar-search-background:var(--scalar-background-2)}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-color-danger:var(--scalar-color-red);--scalar-background-alert:var(--scalar-color-orange);--scalar-background-danger:var(--scalar-color-red);--scalar-sidebar-search-background:var(--scalar-background-2)}.light-mode{--scalar-color-alert:var(--scalar-color-orange)}.dark-mode{--scalar-tooltip-background:var(--scalar-background-1)}@supports (color:color-mix(in lab,red,red)){.dark-mode,.light-mode{--scalar-background-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-background-1) 95%);--scalar-background-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 95%);--scalar-sidebar-search-background:color-mix(in srgb,var(--scalar-background-2),var(--scalar-background-1))}.light-mode{--scalar-color-alert:color-mix(in srgb,var(--scalar-color-orange),var(--scalar-color-1) 20%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-color-1) 20%)}.dark-mode{--scalar-tooltip-background:color-mix(in srgb,var(--scalar-background-1),#fff 10%);--scalar-color-danger:color-mix(in srgb,var(--scalar-color-red),var(--scalar-background-1) 20%)}}}}}@supports (color:color(display-p3 1 1 1)){.light-mode{--scalar-color-accent:#009ced;--scalar-color-green:#008c64;--scalar-color-red:#f9000b;--scalar-color-yellow:#f1bd00;--scalar-color-blue:#0084c2;--scalar-color-orange:#ff702f;--scalar-color-purple:#5a00d8}.dark-mode{--scalar-color-accent:#00adf0;--scalar-color-green:#00b057;--scalar-color-red:#ea0011;--scalar-color-yellow:#ffca2e;--scalar-color-blue:#00b6f0;--scalar-color-orange:#ff925d;--scalar-color-purple:#b790ff}}}@layer scalar-theme;.scalar-app .\@container{container-type:inline-size}.scalar-app .-top-2{top:-8px}.scalar-app .top-\(--refs-header-height\){top:var(--refs-header-height)}.scalar-app .top-\(--scalar-custom-header-height\,0\){top:var(--scalar-custom-header-height,0)}.scalar-app .top-3\.5{top:14px}.scalar-app .top-\[calc\(9px\+0\.5lh\)\]{top:calc(9px + .5lh)}.scalar-app .-left-4\.5{left:-18px}.scalar-app .-left-5{left:-20px}.scalar-app .-left-6{left:-24px}.scalar-app .order-789{order:789}.scalar-app .-m-2{margin:-8px}.scalar-app .-mx-2{margin-inline:-8px}.scalar-app .my-2{margin-block:8px}.scalar-app .my-3{margin-block:12px}.scalar-app .-mt-1{margin-top:-4px}.scalar-app .size-4\.5,.t-editor__callout .callout-content__icon img[data-v-fa58cbab],.t-editor__callout .callout-content__icon svg[data-v-fa58cbab]{height:18px;width:18px}.scalar-app .h-\(--refs-sidebar-height\){height:var(--refs-sidebar-height)}.scalar-app .h-\(--scalar-header-height\){height:var(--scalar-header-height)}.scalar-app .h-\[calc\(100\%\+16px\)\]{height:calc(100% + 16px)}.scalar-app .max-h-\[60vh\]{max-height:60vh}.scalar-app .min-h-3{min-height:12px}.scalar-app .min-h-7{min-height:28px}.scalar-app .min-h-dvh{min-height:100dvh}.scalar-app .w-\(--refs-sidebar-width\){width:var(--refs-sidebar-width)}.scalar-app .w-4\.5{width:18px}.scalar-app .w-96{width:384px}.scalar-app .w-120{width:480px}.scalar-app .max-w-\(--refs-content-max-width\){max-width:var(--refs-content-max-width)}.scalar-app .max-w-64{max-width:256px}.scalar-app .min-w-3{min-width:12px}.scalar-app .min-w-7{min-width:28px}.scalar-app .rotate-45{rotate:45deg}.scalar-app .scroll-mt-16{scroll-margin-top:64px}.scalar-app .scroll-mt-24{scroll-margin-top:96px}.scalar-app .list-none{list-style-type:none}.scalar-app .content-end{align-content:flex-end}.scalar-app .gap-7{gap:28px}.scalar-app .\!rounded-b-xl{border-bottom-left-radius:var(--scalar-radius-xl)!important;border-bottom-right-radius:var(--scalar-radius-xl)!important}.scalar-app .bg-linear-to-l{--tw-gradient-position:to left;background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .from-40\%{--tw-gradient-from-position:40%}.scalar-app .to-transparent{--tw-gradient-to:#0000}.scalar-app .p-7{padding:28px}.scalar-app .px-15{padding-inline:60px}.scalar-app .py-2\.25{padding-block:9px}.scalar-app .pb-12{padding-bottom:48px}.scalar-app .text-base\/5\.25{font-size:var(--scalar-font-size-3);line-height:21px}.scalar-app .leading-\[1\.45\]{--tw-leading:1.45;line-height:1.45}.scalar-app .leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.scalar-app .\[--scalar-address-bar-height\:0px\]{--scalar-address-bar-height:0px}.scalar-app .\[grid-area\:header\]{grid-area:c}.scalar-app .\[grid-area\:navigation\]{grid-area:d}.scalar-app .group-last\:mr-0:is(:where(.group):last-child *){margin-right:0}.scalar-app .group-open\:rotate-90:is(:where(.group):is([open],:popover-open,:open) *){rotate:90deg}.scalar-app .group-open\:flex-wrap:is(:where(.group):is([open],:popover-open,:open) *){flex-wrap:wrap}.scalar-app .group-open\:whitespace-normal:is(:where(.group):is([open],:popover-open,:open) *){white-space:normal}.scalar-app .group-focus-within\/parameter-item\:opacity-100:is(:where(.group\/parameter-item):focus-within *){opacity:1}.scalar-app .group-has-focus-visible\/heading\:opacity-100:is(:where(.group\/heading):has(:focus-visible) *){opacity:1}:is(.scalar-app .\*\:first\:p-3>*):first-child{padding:12px}.scalar-app .focus-visible\:opacity-100:focus-visible{opacity:1}.scalar-app .\[\&_a\]\:underline a,.scalar-app .underline{text-decoration-line:underline}.scalar-app .\[\&_em\]\:not-italic em{font-style:normal}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-mask-linear{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-radial{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-conic{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-left{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-right{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-bottom{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-top{syntax:"*";inherits:false;initial-value:linear-gradient(#fff,#fff)}@property --tw-mask-top-from-position{syntax:"*";inherits:false;initial-value:0}@property --tw-mask-top-to-position{syntax:"*";inherits:false;initial-value:100%}@property --tw-mask-top-from-color{syntax:"*";inherits:false;initial-value:#000}@property --tw-mask-top-to-color{syntax:"*";inherits:false;initial-value:#0000}@property --tw-mask-bottom-from-position{syntax:"*";inherits:false;initial-value:0}@property --tw-mask-bottom-to-position{syntax:"*";inherits:false;initial-value:100%}@property --tw-mask-bottom-from-color{syntax:"*";inherits:false;initial-value:#000}@property --tw-mask-bottom-to-color{syntax:"*";inherits:false;initial-value:#0000}@property --tw-ease{syntax:"*";inherits:false}@keyframes j{0%,70%{opacity:0}to{opacity:1}}@keyframes k{0%{transform:scale(3.5) rotate(0)}to{transform:scale(3.5) rotate(1turn)}}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@keyframes l{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translate(0)}}.scalar-app .bg-c-accent,.scalar-app .bg-c-accent\/10,.scalar-app .bg-c-accent\/5{background-color:var(--scalar-color-accent)}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@supports (color:color-mix(in lab,red,red)){.download-link[data-v-59b5011b]{text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000)}.scalar-app .bg-b-1\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.scalar-app .markdown a{text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000);-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}.scalar-app .markdown .markdown-alert{background-color:color-mix(in srgb,var(--scalar-background-2),#0000);background-color:var(--scalar-background-2)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:color-mix(in srgb,var(--scalar-color-blue),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-blue),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:color-mix(in srgb,var(--scalar-color-2),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-2),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:color-mix(in srgb,var(--scalar-color-orange),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-orange),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:color-mix(in srgb,var(--scalar-color-red),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-red),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:color-mix(in srgb,var(--scalar-color-green),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-green),#0000 50%)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2));background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2));--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background));border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.scalar-app .to-b-1\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}}.scalar-app .bg-b-1\.5{background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue);border:var(--scalar-border-width) solid var(--scalar-color-blue)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2);border:var(--scalar-border-width) solid var(--scalar-color-2)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange);border:var(--scalar-border-width) solid var(--scalar-color-orange)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red);border:var(--scalar-border-width) solid var(--scalar-color-red)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green);border:var(--scalar-border-width) solid var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.scalar-app .to-b-1\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000);text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000)}.scalar-app .markdown .markdown-alert{background-color:color-mix(in srgb,var(--scalar-background-2),#0000)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:color-mix(in srgb,var(--scalar-color-blue),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-blue),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:color-mix(in srgb,var(--scalar-color-2),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-2),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:color-mix(in srgb,var(--scalar-color-orange),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-orange),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:color-mix(in srgb,var(--scalar-color-red),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-red),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:color-mix(in srgb,var(--scalar-color-green),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-green),#0000 50%)}}}.scalar-app .right-0\.75{right:3px}.scalar-app .ml-2{margin-left:8px}.scalar-app .group-focus-visible\/button\:opacity-0:is(:where(.group\/button):focus-visible *),.scalar-app .group-has-\[\~\*\:focus-within\]\/button\:opacity-0:is(:where(.group\/button):has(~:focus-within) *),.scalar-app .group-has-\[\~\*\:hover\]\/button\:opacity-0:is(:where(.group\/button):has(~:hover) *),.scalar-app .group-has-\[\~\*_\[aria-expanded\=true\]\]\/button\:opacity-0:is(:where(.group\/button):has(~* [aria-expanded=true]) *){opacity:0}.scalar-app .peer-focus-visible\/button\:opacity-100:is(:where(.peer\/button):focus-visible~*){opacity:1}.scalar-app .after\:pointer-events-none:after,.scalar-app .before\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.scalar-app .after\:absolute:after,.scalar-app .before\:absolute:before{content:var(--tw-content);position:absolute}.scalar-app .after\:inset-0:after{content:var(--tw-content);inset:0}.scalar-app .after\:inset-x-0:after{content:var(--tw-content);inset-inline:0}.scalar-app .after\:-top-0\.5:after{content:var(--tw-content);top:-2px}.scalar-app .after\:-bottom-0\.5:after{bottom:-2px;content:var(--tw-content)}.scalar-app .after\:block:after{content:var(--tw-content);display:block}.scalar-app .after\:h-0\.75:after{content:var(--tw-content);height:3px}.scalar-app .after\:rounded:after{border-radius:var(--scalar-radius);content:var(--tw-content)}.scalar-app .after\:bg-blue:after{background-color:var(--scalar-color-blue);content:var(--tw-content)}.scalar-app .after\:opacity-15:after{content:var(--tw-content);opacity:.15}.scalar-app .focus-within\:opacity-100:focus-within{opacity:1}.scalar-app .has-\[\&\[aria-expanded\=true\]\]\:opacity-100:has([aria-expanded=true]){opacity:1}:where(.scalar-app){color:var(--scalar-color-1);font-family:var(--scalar-font);-webkit-text-size-adjust:100%;line-height:1.15;tab-size:4}:where(.scalar-app) *,:where(.scalar-app) :after,:where(.scalar-app) :before{border-color:var(--scalar-border-color);border-style:solid;border-width:0;box-sizing:border-box;color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:inherit;font-style:inherit;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:unset;outline-color:var(--scalar-color-accent);outline-style:none;outline-width:1px;padding:unset;text-align:inherit;-webkit-text-decoration:inherit;text-decoration:inherit;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}:where(.scalar-app) :after,:where(.scalar-app) :before{--tw-content:""}:where(.scalar-app) button,:where(.scalar-app) input,:where(.scalar-app) optgroup,:where(.scalar-app) select,:where(.scalar-app) textarea{background:none}:where(.scalar-app) ::file-selector-button{background:none}:where(.scalar-app) menu,:where(.scalar-app) ol,:where(.scalar-app) ul{list-style:none}:where(.scalar-app) input:where(:not([type=button]):not([type=reset]):not([type=submit])),:where(.scalar-app) select,:where(.scalar-app) textarea{border-radius:var(--scalar-radius);border-width:1px}:where(.scalar-app) input::placeholder{color:var(--scalar-color-3);font-family:var(--scalar-font)}:where(.scalar-app) input[type=search]::-webkit-search-cancel-button{appearance:none}:where(.scalar-app) input[type=search]::-webkit-search-decoration{appearance:none}:where(.scalar-app) summary::-webkit-details-marker{display:none}:where(.scalar-app) input:-webkit-autofill{-webkit-background-clip:text!important;background-clip:text!important}:where(.scalar-app) :focus-visible{border-radius:var(--scalar-radius);outline-style:solid}:where(.scalar-app) [role=button]:focus-visible,:where(.scalar-app) button:focus-visible{outline-offset:-1px}:where(.scalar-app) [role=button],:where(.scalar-app) button{cursor:pointer}:where(.scalar-app) :disabled{cursor:default}:where(.scalar-app) audio,:where(.scalar-app) canvas,:where(.scalar-app) embed,:where(.scalar-app) iframe,:where(.scalar-app) img,:where(.scalar-app) object,:where(.scalar-app) svg,:where(.scalar-app) video{display:block;vertical-align:middle}:where(.scalar-app) [hidden]{display:none}.scalar-app .cm-scroller,.scalar-app .custom-scroll{scrollbar-color:#0000 #0000;scrollbar-width:thin;-webkit-overflow-scrolling:touch;overflow-y:auto}.scalar-app .custom-scroll-self-contain-overflow,.scalar-app .overscroll-contain{overscroll-behavior:contain}.scalar-app .cm-scroller:hover,.scalar-app .custom-scroll:hover,.scalar-app.scalar-scrollbars-obtrusive .cm-scroller,.scalar-app.scalar-scrollbars-obtrusive .custom-scroll{scrollbar-color:var(--scalar-scrollbar-color,#0000) #0000}.scalar-app .cm-scroller:hover::-webkit-scrollbar-thumb,.scalar-app .custom-scroll:hover::-webkit-scrollbar-thumb{background:var(--scalar-scrollbar-color);background-clip:content-box;border:3px solid #0000}.scalar-app .cm-scroller::-webkit-scrollbar-thumb:active,.scalar-app .custom-scroll::-webkit-scrollbar-thumb:active{background:var(--scalar-scrollbar-color-active);background-clip:content-box;border:3px solid #0000}.scalar-app .cm-scroller::-webkit-scrollbar-corner,.scalar-app .custom-scroll::-webkit-scrollbar-corner{background:none}.scalar-app .cm-scroller::-webkit-scrollbar,.scalar-app .custom-scroll::-webkit-scrollbar{height:12px;width:12px}.scalar-app .cm-scroller::-webkit-scrollbar-track,.scalar-app .custom-scroll::-webkit-scrollbar-track{background:none}.scalar-app .cm-scroller::-webkit-scrollbar-thumb,.scalar-app .custom-scroll::-webkit-scrollbar-thumb{background:padding-box content-box;border:3px solid #0000;border-radius:20px}.scalar-app .invisible{visibility:hidden}.scalar-app .-inset-y-0\.5{inset-block:-2px}.scalar-app .-inset-y-0\.75{inset-block:-3px}.scalar-app .inset-y-0{inset-block:0}.scalar-app .inset-y-0\.5{inset-block:2px}.scalar-app .-top-1{top:-4px}.scalar-app .top-\(--nested-items-offset\){top:var(--nested-items-offset)}.scalar-app .top-0\.5{top:2px}.scalar-app .top-1\/2{top:50%}.scalar-app .top-2\.5{top:10px}.scalar-app .top-22{top:88px}.scalar-app .top-\[1lh\]{top:1lh}.scalar-app .top-\[calc\(10px\+0\.5lh\)\]{top:calc(10px + .5lh)}.scalar-app .top-px{top:1px}.scalar-app .-right-1{right:-4px}.scalar-app .-right-1\.5{right:-6px}.scalar-app .right-1\.25{right:5px}.scalar-app .right-2\.5{right:10px}.scalar-app .left-2{left:8px}.scalar-app .left-2\.5{left:10px}.scalar-app .left-4{left:16px}.scalar-app .left-border{left:var(--scalar-border-width)}.scalar-app .left-px{left:1px}.scalar-app .-z-2{z-index:-2}.scalar-app .z-tooltip{z-index:99999}.scalar-app .-m-1{margin:-4px}.scalar-app .-m-1\.5{margin:-6px}.scalar-app .-m-px{margin:-1px}.scalar-app .m-1{margin:4px}.scalar-app .-mx-0\.75{margin-inline:-3px}.scalar-app .-mx-0\.25,.scalar-app .-mx-px{margin-inline:-1px}.scalar-app .mx-px{margin-inline:1px}.scalar-app .-my-1\.5{margin-block:-6px}.scalar-app .-my-2{margin-block:-8px}.scalar-app .my-0\.75{margin-block:3px}.scalar-app .my-1\.5{margin-block:6px}.scalar-app .-mt-1\.5{margin-top:-6px}.scalar-app .mt-\[15svh\]{margin-top:15svh}.scalar-app .mt-\[20svh\]{margin-top:20svh}.scalar-app .-mr-0\.25{margin-right:-1px}.scalar-app .mr-0{margin-right:0}.scalar-app .mr-\[calc\(20px-var\(--scalar-sidebar-indent\)\)\]{margin-right:calc(20px - var(--scalar-sidebar-indent))}.scalar-app .-mb-1{margin-bottom:-4px}.scalar-app .-ml-0\.75{margin-left:-3px}.scalar-app .line-clamp-\(--markdown-clamp\){-webkit-line-clamp:var(--markdown-clamp);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.scalar-app .size-2{height:8px;width:8px}.scalar-app .size-2\.75{height:11px;width:11px}.scalar-app .size-6{height:24px;width:24px}.scalar-app .size-60{height:240px;width:240px}.scalar-app .size-\[23px\]{height:23px;width:23px}.group\/sidebar-section:first-of-type>.group\/spacer-before,.group\/sidebar-section:last-of-type>.group\/spacer-after,.scalar-app .h-0{height:0}.scalar-app .h-1{height:4px}.scalar-app .h-24{height:96px}.scalar-app .h-32{height:128px}.scalar-app .h-border{height:var(--scalar-border-width)}.scalar-app .h-lh{height:1lh}.scalar-app .max-h-20{max-height:80px}.scalar-app .max-h-\[80svh\]{max-height:80svh}.scalar-app .max-h-\[90svh\]{max-height:90svh}.scalar-app .max-h-dvh{max-height:100dvh}.scalar-app .max-h-radix-popper{max-height:calc(var(--radix-popper-available-height) - 8px)}.scalar-app .min-h-96{min-height:384px}.scalar-app .min-h-12,.scalar-app .min-h-header{min-height:48px}.scalar-app .w-12{width:48px}.scalar-app .w-24{width:96px}.scalar-app .w-32{width:128px}.scalar-app .w-40{width:160px}.scalar-app .w-48{width:192px}.scalar-app .w-\[38px\]{width:38px}.scalar-app .w-\[calc\(100vw-12px\)\]{width:calc(100vw - 12px)}.scalar-app .w-\[var\(--scalar-sidebar-indent\)\]{width:var(--scalar-sidebar-indent)}.scalar-app .w-border{width:var(--scalar-border-width)}.scalar-app .w-min{width:min-content}.scalar-app .w-screen{width:100vw}.scalar-app .max-w-\[360px\]{max-width:360px}.scalar-app .max-w-\[480px\]{max-width:480px}.scalar-app .max-w-\[540px\]{max-width:540px}.scalar-app .max-w-\[640px\]{max-width:640px}.scalar-app .max-w-\[800px\]{max-width:800px}.scalar-app .max-w-\[1000px\]{max-width:1000px}.scalar-app .max-w-xs{max-width:320px}.scalar-app .min-w-6{min-width:24px}.scalar-app .min-w-40{min-width:160px}.scalar-app .min-w-min{min-width:min-content}.scalar-app .flex-shrink,.scalar-app .shrink{flex-shrink:1}.scalar-app .-translate-x-full{--tw-translate-x:-100%}.scalar-app .translate-x-2\.5{--tw-translate-x:10px}.scalar-app .translate-x-\[14px\]{--tw-translate-x:14px}.scalar-app .translate-x-full{--tw-translate-x:100%}.scalar-app .centered,.scalar-app .centered-x{--tw-translate-x:-50%;left:50%}.scalar-app .-translate-y-1\.5{--tw-translate-y:-6px}.scalar-app .-translate-y-1\/2{--tw-translate-y:-50%}.scalar-app .translate-y-0{--tw-translate-y:0px}.scalar-app .translate-y-1\.5{--tw-translate-y:6px}.scalar-app .centered,.scalar-app .centered-y{--tw-translate-y:-50%}.scalar-app .scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%}.scalar-app .scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%}.scalar-app .appearance-none{appearance:none}.scalar-app .grid-flow-col{grid-auto-flow:column}.scalar-app .\!items-end{align-items:flex-end!important}.scalar-app .\!items-start{align-items:flex-start!important}.scalar-app .\!justify-end{justify-content:flex-end!important}.scalar-app .\!justify-start{justify-content:flex-start!important}.scalar-app .gap-2\.25{gap:9px}.scalar-app .gap-x-4{column-gap:16px}.scalar-app .gap-y-8{row-gap:32px}:where(.scalar-app .divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(var(--scalar-border-width)*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(var(--scalar-border-width)*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style)}.scalar-app .overflow-x-clip{overflow-x:clip}.scalar-app .rounded-\[inherit\]{border-radius:inherit}.scalar-app .border-1,.scalar-app .border-\[1px\]{border-style:var(--tw-border-style);border-width:1px}.scalar-app .border-c-alert{border-color:var(--scalar-color-alert)}.scalar-app .border-red{border-color:var(--scalar-color-red)}.scalar-app .border-sidebar-border{border-color:var(--scalar-sidebar-border-color,var(--scalar-border-color))}.scalar-app .border-sidebar-border-search{border-color:var(--scalar-sidebar-search-border-color,var(--scalar-border-color))}.scalar-app .bg-\(--bg-light\){background-color:var(--bg-light)}.scalar-app .bg-b-1,.scalar-app .bg-b-1\.5,.scalar-code-block.bg-b-1 .scalar-code-copy-backdrop{background-color:var(--scalar-background-1)}.scalar-app .bg-\(--scalar-background-alert\),.scalar-app .bg-b-alert{background-color:var(--scalar-background-alert)}.scalar-app .active\:bg-b-btn:active,.scalar-app .bg-b-btn{background-color:var(--scalar-button-1)}.scalar-app .bg-b-tooltip{background-color:var(--scalar-tooltip-background)}.scalar-app .bg-backdrop{background-color:#00000038}.scalar-app .bg-border{background-color:var(--scalar-border-color)}.scalar-app .bg-c-danger{background-color:var(--scalar-color-danger)}.scalar-app .bg-inherit{background-color:inherit}.scalar-app .bg-sidebar-b-search{background-color:var(--scalar-sidebar-search-background,var(--scalar-background-2))}.scalar-app .bg-sidebar-indent-border{background-color:var(--scalar-sidebar-indent-border,var(--scalar-border-color))}.scalar-app .bg-sidebar-indent-border-active{background-color:var(--scalar-sidebar-indent-border-active,var(--scalar-color-accent))}.scalar-app .bg-transparent{background-color:#0000}.scalar-app .bg-linear-to-b{--tw-gradient-position:to bottom;background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .from-b-1{--tw-gradient-from:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}.scalar-app .to-b-2{--tw-gradient-to:var(--scalar-background-2)}.scalar-app .mask-y-from-\[calc\(100\%-8px\)\]{--tw-mask-top-from-position:calc(100% - 8px);--tw-mask-bottom-from-position:calc(100% - 8px)}.scalar-app .mask-y-to-100\%{--tw-mask-top-to-position:100%;--tw-mask-bottom-to-position:100%}.scalar-app .mask-repeat{-webkit-mask-repeat:repeat;mask-repeat:repeat}.scalar-app .p-0\.25,.scalar-app .p-px{padding:1px}.scalar-app .p-6{padding:24px}.scalar-app .px-9{padding-inline:36px}.scalar-app .py-4{padding-block:16px}.scalar-app .py-\[6\.75px\]{padding-block:6.75px}.scalar-app .pr-\[100\%\]{padding-right:100%}.scalar-app .pl-8{padding-left:32px}.scalar-app .pl-\[100\%\]{padding-left:100%}.scalar-app .text-base\/4{font-size:var(--scalar-font-size-3);line-height:16px}.scalar-app .text-lg,[data-v-68d5218e] .markdown h2{font-size:var(--scalar-font-size-2);line-height:var(--tw-leading,1.25)}.scalar-app .text-sm\/4{font-size:var(--scalar-font-size-4);line-height:16px}.scalar-app .text-sm\/none{font-size:var(--scalar-font-size-4);line-height:1}.scalar-app .text-xs\/4{font-size:var(--scalar-font-size-5);line-height:16px}.scalar-app .font-sidebar{--tw-font-weight:var(--scalar-sidebar-font-weight,var(--scalar-regular));font-weight:var(--scalar-sidebar-font-weight,var(--scalar-regular))}.scalar-app .font-sidebar-active{--tw-font-weight:var(--scalar-sidebar-font-weight-active,var(--scalar-semibold));font-weight:var(--scalar-sidebar-font-weight-active,var(--scalar-semibold))}.scalar-app .break-words,.scalar-app .wrap-break-word{overflow-wrap:break-word}.scalar-app .text-\(--scalar-color-alert\),.scalar-app .text-c-alert{color:var(--scalar-color-alert)}.scalar-app .text-c-tooltip{color:var(--scalar-tooltip-color)}.scalar-app .text-sidebar-c-1{color:var(--scalar-sidebar-color-1,var(--scalar-color-1))}.scalar-app .text-sidebar-c-search{color:var(--scalar-sidebar-search-color,var(--scalar-color-3))}.scalar-app .opacity-40,.theme-code-block:hover .buttonGroup_M5ko button{opacity:.4}.scalar-app .-outline-offset-2{outline-offset:-2px}.scalar-app .outline-offset-1{outline-offset:1px}.scalar-app .-outline-offset-1,.scalar-app .outline-offset-\[-1px\]{outline-offset:-1px}.scalar-app .ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.scalar-app .ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}:is(.scalar-app .\*\:size-3>*){height:12px;width:12px}:is(.scalar-app .\*\:size-4>*){height:16px;width:16px}:is(.scalar-app .\*\:h-5>*){height:20px}:is(.scalar-app .\*\:min-w-5>*){min-width:20px}:is(.scalar-app .\*\:flex-1>*){flex:1}:is(.scalar-app .\*\:justify-center>*){justify-content:center}:is(.scalar-app .\*\:gap-px>*){gap:1px}:is(.scalar-app .\*\:rounded>*){border-radius:var(--scalar-radius)}:is(.scalar-app .\*\:border>*){border-style:var(--tw-border-style);border-width:var(--scalar-border-width)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}:is(.scalar-app .\*\:px-1>*){padding-inline:4px}:is(.scalar-app .\*\:text-xs>*){font-size:var(--scalar-font-size-5)}.scalar-app .group-focus-visible\/toggle\:outline:is(:where(.group\/toggle):focus-visible *){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .group-hocus\/copy-button\:sr-only:is(:is(:where(.group\/copy-button):hover,:where(.group\/copy-button):focus-visible) *){border-width:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.scalar-app .group-hocus\/copy-button\:not-sr-only:is(:is(:where(.group\/copy-button):hover,:where(.group\/copy-button):focus-visible) *){clip-path:none;height:auto;margin:0;overflow:visible;padding:0;position:static;white-space:normal;width:auto}.scalar-app .group-hocus\/copy-button\:block:is(:is(:where(.group\/copy-button):hover,:where(.group\/copy-button):focus-visible) *){display:block}.scalar-app .group-hocus-within\/code-block\:-left-0\.5:is(:is(:where(.group\/code-block):hover,:where(.group\/code-block):focus-within) *){left:-2px}.scalar-app .group-hocus-within\/code-block\:inline:is(:is(:where(.group\/code-block):hover,:where(.group\/code-block):focus-within) *){display:inline}.scalar-app .group-hocus-within\/code-block\:opacity-100:is(:is(:where(.group\/code-block):hover,:where(.group\/code-block):focus-within) *){opacity:1}.scalar-app .placeholder\:font-\[inherit\]::placeholder{font-family:inherit}.scalar-app .first\:rounded-t-\[inherit\]:first-child,:is(.scalar-app .\*\:first\:rounded-t-\[inherit\]>*):first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.scalar-app .last\:rounded-b-\[inherit\]:last-child,:is(.scalar-app .\*\:last\:rounded-b-\[inherit\]>*):last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.scalar-app .focus-within\:outline-none:focus-within{--tw-outline-style:none;outline-style:none}.scalar-app .focus-visible\:border-c-btn:focus-visible{border-color:var(--scalar-button-1-color)}.scalar-app .focus-visible\:outline:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .has-\[\:focus-visible\]\:bg-sidebar-b-1:has(:focus-visible){background-color:var(--scalar-sidebar-background-1,var(--scalar-background-1))}.scalar-app .has-\[\:focus-visible\]\:outline:has(:focus-visible),.scalar-app .has-\[input\:focus-visible\]\:outline:has(:is(input:focus-visible)){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .dark\:bg-\(--bg-dark\):where(.dark-mode,.dark-mode *){background-color:var(--bg-dark)}.scalar-app .dark\:bg-b-3:where(.dark-mode,.dark-mode *){background-color:var(--scalar-background-3)}.scalar-app .dark\:bg-backdrop-dark:where(.dark-mode,.dark-mode *){background-color:#00000073}.scalar-app .dark\:bg-linear-to-t:where(.dark-mode,.dark-mode *){--tw-gradient-position:to top;background-image:linear-gradient(var(--tw-gradient-stops))}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .bg-linear-to-l{--tw-gradient-position:to left in oklab}.scalar-app .bg-linear-to-b{--tw-gradient-position:to bottom in oklab}.scalar-app .dark\:bg-linear-to-t:where(.dark-mode,.dark-mode *){--tw-gradient-position:to top in oklab}}[data-radix-popper-content-wrapper]{z-index:1!important}.loader-wrapper[data-v-27df5cd8]{--loader-size:50%;align-items:center;display:flex;justify-content:center;position:relative}.svg-loader[data-v-27df5cd8]{background-color:#0000;fill:none;height:var(--loader-size);overflow:visible;right:.9rem;stroke:currentColor;top:1rem;width:var(--loader-size)}.svg-path[data-v-27df5cd8]{fill:none;stroke-width:12px;transition:.3s}.svg-x-mark[data-v-27df5cd8]{stroke-dasharray:57;stroke-dashoffset:57px;transition-delay:0s}.svg-check-mark[data-v-27df5cd8]{stroke-dasharray:149;stroke-dashoffset:149px;transition-delay:0s}.icon-is-invalid .svg-x-mark[data-v-27df5cd8],.icon-is-valid .svg-check-mark[data-v-27df5cd8]{stroke-dashoffset:0;transition-delay:.3s}.circular-loader[data-v-27df5cd8]{animation:.7s linear infinite k,.4s j;background:none;transform:scale(3.5);transform-origin:50%}.loader-path[data-v-27df5cd8]{stroke-dasharray:50 200;stroke-dashoffset:-100px;stroke-linecap:round}.loader-path-off[data-v-27df5cd8]{opacity:0;stroke-dasharray:50 200;stroke-dashoffset:-100px;transition:opacity .3s}.scalar-app .bg-b-2,.scalar-app .markdown summary:hover,.scalar-code-block.bg-b-2 .scalar-code-copy-backdrop{background-color:var(--scalar-background-2)}.scalar-app .bg-b-3,.scalar-code-block.bg-b-2 .scalar-code-copy{background-color:var(--scalar-background-3)}.toggle-icon-ellipse[data-v-60be8692]{background:var(--scalar-background-1);border-radius:50%;box-shadow:inset 0 0 0 1px;display:inline-block;height:7px;overflow:hidden;position:relative;transition:width .3s ease-in-out,height .3s ease-in-out;width:7px}.toggle-icon-moon-mask[data-v-60be8692]{background:var(--scalar-background-1);border:1px solid;border-radius:50%;bottom:2.5px;display:block;height:100%;left:2.5px;position:absolute;transform:translate(4px,-4px);transition:transform .3s ease-in-out;width:100%}.toggle-icon-sun-ray[data-v-60be8692]{background:currentColor;border-radius:8px;height:1px;position:absolute;transition:transform .3s ease-in-out;width:12px}.scalar-app .transition,.scalar-app .transition-colors,.scalar-app .transition-opacity,.scalar-app .transition-transform{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.toggle-icon-sun-ray[data-v-60be8692]:nth-of-type(4){transform:rotate(-45deg)}.toggle-icon-dark .toggle-icon-ellipse[data-v-60be8692]{height:10px;-webkit-mask-image:radial-gradient(circle at 0 100%,pink 10px,#0000 12px);mask-image:radial-gradient(circle at 0 100%,pink 10px,#0000 12px);width:10px}.toggle-icon-dark .toggle-icon-sun-ray[data-v-60be8692]{transform:scale(0)}.scalar-icon[data-v-b651bb23],.scalar-icon[data-v-b651bb23] *{stroke-width:var(--c07589c2)}.scalar-app :where(code.hljs) *{font-family:var(--scalar-font-code);font-size:inherit;text-align:left;white-space:pre;word-break:normal;word-spacing:normal;word-wrap:normal;line-height:1.4;tab-size:4}.scalar-app code.hljs{all:unset;color:var(--scalar-color-2);counter-reset:linenumber;font-family:var(--scalar-font-code);font-size:inherit}.scalar-app .hljs{background:none;color:var(--scalar-color-2)}.scalar-app .hljs .line:before{color:var(--scalar-color-3);content:counter(linenumber);counter-increment:linenumber;display:inline-block;margin-right:.875rem;min-width:calc(var(--line-digits)*1ch);text-align:right}.scalar-app .hljs-comment,.scalar-app .hljs-quote{color:var(--scalar-color-3);font-style:italic}.hljs-built_in,.hljs.language-objectivec .hljs-built_in,.scalar-app .hljs-number,.scalar-app .hljs-selector-attr,.scalar-app .hljs-selector-pseudo,.scalar-app .hljs-title.function_,.scalar-app .markdown .markdown-alert.markdown-alert-warning .markdown-alert-icon:before,.scalar-app .text-orange{color:var(--scalar-color-orange)}.scalar-app .hljs-keyword,.scalar-app .markdown .markdown-alert.markdown-alert-important .markdown-alert-icon:before,.scalar-app .text-purple{color:var(--scalar-color-purple)}.scalar-app .hljs-formula{background:var(--scalar-color-1)}.scalar-app .credential .credential-value{color:#0000;font-size:0}.scalar-app .credential:after{color:var(--scalar-color-3);content:"·····";-webkit-user-select:none;user-select:none}.hljs.language-html .hljs-attr,.scalar-app .disabled\:text-c-2:disabled,.scalar-app .markdown .markdown-alert.markdown-alert-tip .markdown-alert-icon:before,.scalar-app .text-c-2{color:var(--scalar-color-2)}.scalar-app .markdown{--markdown-border:var(--scalar-border-width) solid var(--scalar-border-color);--scalar-refs-heading-spacing:24px;--markdown-border:var(--scalar-border-width) solid var(--scalar-border-color);--markdown-spacing-sm:12px;--markdown-spacing-md:16px;--markdown-line-height:1.625;--markdown-heading-line-height:1.15;font-family:var(--scalar-font);line-height:var(--markdown-line-height);word-break:break-word}.scalar-app .markdown>*{margin-bottom:var(--markdown-spacing-md)}.scalar-app .markdown h1,.scalar-app .markdown h2,.scalar-app .markdown h3,.scalar-app .markdown h4,.scalar-app .markdown h5,.scalar-app .markdown h6{display:block;font-weight:var(--scalar-bold);line-height:var(--markdown-heading-line-height,1.15);margin-bottom:var(--markdown-spacing-sm);margin-top:var(--scalar-refs-heading-spacing);scroll-margin-top:1rem}.scalar-app .markdown h2,.scalar-app .markdown h3{font-size:1.25rem}.scalar-app .markdown p{color:inherit;display:block;line-height:var(--markdown-line-height)}.scalar-app .markdown img{border-radius:var(--scalar-radius);display:inline-block;max-width:100%;overflow:hidden}.scalar-app .markdown ol,.scalar-app .markdown ul{display:flex;flex-direction:column;gap:2px;line-height:var(--markdown-line-height);padding-left:1.6em}.scalar-app .markdown li{margin-top:2px;padding-left:7px}.scalar-app ol>li::marker{font:var(--scalar-font);font-feature-settings:"tnum";font-variant-numeric:tabular-nums;font-weight:var(--scalar-semibold);white-space:nowrap}.scalar-app ol>*>li::marker{font:var(--scalar-font);font-feature-settings:"tnum";font-variant-numeric:tabular-nums;font-weight:var(--scalar-semibold);white-space:nowrap}.scalar-app .markdown ol,.scalar-app .markdown ol ol ol ol,.scalar-app .markdown ol ol ol ol ol ol ol{list-style-type:decimal}.scalar-app .markdown ul>*>li,.scalar-app .markdown ul>li{list-style-type:disc}.scalar-app .markdown table{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius);border-spacing:0;display:table;margin:1em 0;overflow-x:auto;position:relative;table-layout:fixed;width:100%}.scalar-app .markdown tbody,.scalar-app .markdown thead{vertical-align:middle}.scalar-app .markdown tbody{display:table-row-group}.scalar-app .markdown thead{display:table-header-group}.scalar-app .markdown tr{border-color:inherit;display:table-row;vertical-align:inherit}.scalar-app .markdown td,.scalar-app .markdown th{border-bottom:var(--markdown-border);border-right:var(--markdown-border);color:var(--scalar-color-1);display:table-cell;font-size:var(--scalar-small);line-height:var(--markdown-line-height);min-width:1em;padding:8.5px 16px;position:relative;vertical-align:top;word-break:break-word}.scalar-app .hidden,.scalar-app .markdown th:empty,.scalar-app .markdown.markdown-summary.markdown-summary hr,.scalar-app .markdown.markdown-summary.markdown-summary img,.scalar-app .markdown.markdown-summary.markdown-summary pre,.scalar-app .markdown.markdown-summary.markdown-summary svg{display:none}.scalar-app .markdown td:last-of-type,.scalar-app .markdown th:last-of-type{border-right:none}.scalar-app .markdown th{background:var(--scalar-background-2);border-left-color:#0000;font-weight:var(--scalar-bold);text-align:left}.scalar-app .markdown th:first-of-type{border-top-left-radius:var(--scalar-radius)}.scalar-app .markdown th:last-of-type{border-top-right-radius:var(--scalar-radius)}.scalar-app .markdown tr>[align=left],.scalar-app .text-left{text-align:left}.scalar-app .markdown tr>[align=center],.scalar-app .text-center{text-align:center}.scalar-app .markdown details{border:var(--markdown-border);border-radius:var(--scalar-radius-xl);color:var(--scalar-color-1)}.scalar-app .markdown details>:not(summary){margin:var(--markdown-spacing-md);margin-bottom:0}.scalar-app .markdown details>p:has(>strong):not(:has(:not(strong))){margin-bottom:8px}.scalar-app .markdown details>p:has(>strong):not(:has(:not(strong)))+*{margin-top:0}.scalar-app .markdown details>table{width:calc(100% - var(--markdown-spacing-md)*2)}.scalar-app .markdown summary{align-items:flex-start;border-radius:2.5px;cursor:pointer;display:flex;font-weight:var(--scalar-semibold);gap:8px;line-height:var(--markdown-line-height);min-height:40px;padding:7px 14px;position:relative;-webkit-user-select:none;user-select:none}.scalar-app .markdown details[open]{padding-bottom:var(--markdown-spacing-md)}.scalar-app .markdown details[open]>summary{border-bottom:var(--markdown-border);border-bottom-left-radius:0;border-bottom-right-radius:0}.scalar-app .markdown summary:before{background-color:var(--scalar-color-3);content:"";display:block;flex-shrink:0;height:var(--markdown-spacing-md);margin-top:5px;-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,');width:var(--markdown-spacing-md)}.scalar-app .markdown summary:hover:before{background-color:var(--scalar-color-1)}.scalar-app .markdown details[open]>summary:before{transform:rotate(90deg);transition:transform .1s ease-in-out}.scalar-app .markdown details:has(+details){border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0;margin-bottom:0}.scalar-app .markdown details:has(+details)+details,.scalar-app .markdown details:has(+details)+details>summary{border-top-left-radius:0;border-top-right-radius:0}.scalar-app .markdown a:visited{color:var(--font-visited)}.scalar-app .markdown sub,.scalar-app .markdown sup{font-size:var(--scalar-micro);font-weight:450}.scalar-app .markdown sup{vertical-align:super}.scalar-app .markdown sub{vertical-align:sub}.scalar-app .markdown code{background-color:var(--scalar-background-2);border-radius:2px;font-family:var(--scalar-font-code);font-size:var(--scalar-micro);padding:0 3px}.scalar-app .markdown .hljs,.scalar-code-block[data-v-8e5d8a14] .hljs *,.scalar-code-block[data-v-94c74c13] .hljs *,[data-v-2891f052] code.hljs *,[data-v-9aa4b63a] .cm-content,[data-v-fd127758] .cm-content{font-size:var(--scalar-small)}.scalar-app .markdown pre code{margin:var(--markdown-spacing-sm) 0;padding:var(--markdown-spacing-sm);white-space:pre;-webkit-overflow-scrolling:touch;display:block;line-height:1.5;max-width:100%;min-width:100px;overflow-x:auto}.scalar-app .markdown hr{border:none;border-bottom:var(--markdown-border)}.scalar-app .markdown blockquote{border-left:1px solid var(--scalar-color-1);display:block;font-size:var(--scalar-font-size-2);font-weight:var(--scalar-bold);margin:0;padding-left:var(--markdown-spacing-md)}.scalar-app .markdown li.task-list-item{list-style:none;position:relative}.scalar-app .markdown li.task-list-item>input{appearance:none;border:1px solid var(--scalar-color-3);border-radius:var(--scalar-radius);display:inline;height:var(--markdown-spacing-md);left:-1.4em;position:absolute;top:.225em;width:var(--markdown-spacing-md)}.scalar-app .markdown li.task-list-item>input[type=checkbox]:checked{background-color:var(--scalar-color-1);border-color:var(--scalar-color-1)}.scalar-app .markdown li.task-list-item>input[type=checkbox]:before{border:solid var(--scalar-background-1);border-width:0 1.5px 1.5px 0;content:"";height:10px;left:5px;opacity:0;position:absolute;top:1px;transform:rotate(45deg);width:5px}.scalar-app .floating-bg:hover:before,.scalar-app .markdown li.task-list-item>input[type=checkbox]:checked:before,.scalar-app .opacity-100{opacity:1}.scalar-app .markdown .markdown-alert{align-items:stretch;background-color:var(--scalar-background-2);border-radius:var(--scalar-radius);border:var(--markdown-border);display:flex;gap:var(--markdown-spacing-sm);padding:10px 14px;position:relative}.scalar-app .markdown .markdown-alert .markdown-alert-icon:before{background-color:currentColor;content:"";display:block;flex-shrink:0;height:18px;margin-top:3px;-webkit-mask-position:50%;mask-position:50%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:18px}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue);border:var(--scalar-border-width) solid var(--scalar-color-blue)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2);border:var(--scalar-border-width) solid var(--scalar-color-2)}.scalar-app .markdown .markdown-alert.markdown-alert-note .markdown-alert-icon:before,.scalar-app .markdown .markdown-alert.markdown-alert-tip .markdown-alert-icon:before{color:var(--scalar-color-blue);-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange);border:var(--scalar-border-width) solid var(--scalar-color-orange)}.scalar-app .markdown .markdown-alert.markdown-alert-important .markdown-alert-icon:before,.scalar-app .markdown .markdown-alert.markdown-alert-warning .markdown-alert-icon:before{-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red);border:var(--scalar-border-width) solid var(--scalar-color-red)}.scalar-app .markdown .markdown-alert.markdown-alert-caution .markdown-alert-icon:before{color:var(--scalar-color-red);-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green);border:var(--scalar-border-width) solid var(--scalar-color-green)}.scalar-app .markdown .markdown-alert.markdown-alert-success .markdown-alert-icon:before{color:var(--scalar-color-green);-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.scalar-app .markdown .markdown-alert .markdown-alert-content{line-height:var(--markdown-line-height);margin:0}.scalar-app .markdown.markdown-summary.markdown-summary :after,.scalar-app .markdown.markdown-summary.markdown-summary :before{content:none}.scalar-app .markdown.markdown-summary.markdown-summary :not(strong):not(em):not(a){display:contents;font-size:inherit;font-weight:inherit;line-height:var(--markdown-line-height)}.dark-mode .scalar-dropdown-item[data-v-3402682d]:hover,.dark-mode .scalar-dropdown-item[data-v-6660bbc5]:hover{filter:brightness(1.1)}.group\/item>*>.scalar-sidebar-indent .scalar-sidebar-indent-border[data-v-3e080c68]{inset-block:-1px}.group\/item:first-child>*>.scalar-sidebar-indent .scalar-sidebar-indent-border[data-v-3e080c68],.scalar-app .top-0{top:0}.group\/item:last-child>*>.scalar-sidebar-indent .scalar-sidebar-indent-border[data-v-3e080c68],.scalar-app .bottom-0{bottom:0}.group\/items.-translate-x-full .group\/button{display:none;max-height:0;transition-behavior:allow-discrete;transition-delay:.3s;transition-duration:0s;transition-property:display,max-height}.group\/item.group\/nested-items-open>*>.group\/items.translate-x-0 .group\/button{display:flex;max-height:3.40282e+38px}.group\/sidebar-section:has(+.group\/sidebar-section)>.group\/spacer-after{height:0;margin-bottom:-1px}:where(body)>.scalar-tooltip{--scalar-tooltip-padding:8px;font-size:var(--scalar-font-size-5);max-width:320px;padding:calc(var(--scalar-tooltip-padding) + var(--scalar-tooltip-offset));z-index:99999;--tw-font-weight:var(--scalar-semibold);color:var(--scalar-tooltip-color);font-weight:var(--scalar-semibold);line-height:16px;overflow-wrap:break-word}:where(body)>.scalar-tooltip:before{backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);background-color:var(--scalar-tooltip-background);border-radius:var(--scalar-radius);content:"";inset:var(--scalar-tooltip-offset);z-index:-1;--tw-backdrop-blur:blur(8px);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);position:absolute}:where(body.dark-mode)>.scalar-tooltip:before{--tw-shadow:inset 0 0 0 var(--tw-shadow-color,var(--scalar-border-width)) var(--scalar-border-color);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-modal-layout[data-v-c36b47da]{animation:.3s ease-in-out forwards u}.scalar-modal[data-v-c36b47da]{animation:.3s ease-in-out .1s forwards l;box-shadow:var(--scalar-shadow-2);transform:translateY(10px)}.scalar-modal-layout-full[data-v-c36b47da]{background:none!important;opacity:1!important}.modal-content-search .modal-body[data-v-c36b47da]{display:flex;flex-direction:column;max-height:440px;overflow:hidden;padding:0}.full-size-styles[data-v-c36b47da]{animation:.3s ease-in-out forwards u;background-color:var(--scalar-background-1)!important;border-radius:0!important;border-right:var(--scalar-border-width) solid var(--scalar-border-color);box-shadow:none!important;left:0;margin:initial;max-height:100%!important;position:absolute!important;top:0!important;transform:translate(0)}.scalar-app .shadow,.scalar-app .shadow-\[-8px_0_4px_var\(--scalar-background-1\)\],.scalar-app .shadow-\[0_-8px_0_8px_var\(--scalar-background-1\)\,0_0_8px_8px_var\(--scalar-background-1\)\],.scalar-app .shadow-border,.scalar-app .shadow-lg,.scalar-app .shadow-none{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.full-size-styles[data-v-c36b47da]:after{content:"";height:100dvh;position:absolute;right:-50dvw;top:0;width:50dvw}.sidebar-heading-type[data-v-1857170e]{align-items:center;color:var(--method-color,var(--scalar-color-1));display:inline-flex;flex-shrink:0;font-family:var(--scalar-font-code);font-size:10px;font-weight:var(--scalar-bold);gap:4px;line-height:14px;overflow:hidden;text-transform:uppercase;white-space:nowrap}.scalar-app .pointer-events-auto{pointer-events:auto}.scalar-app .collapse{visibility:collapse}.scalar-app .visible{visibility:visible}.scalar-app .floating-bg:before{background-color:var(--scalar-background-2);border-radius:var(--scalar-radius);content:"";height:calc(100% - 4px);left:-4px;opacity:0;position:absolute;top:2.5px;transition:opacity .2s ease-in-out;width:calc(100% + 8px);z-index:1}.scalar-app .centered,.scalar-app .centered-y{position:absolute;top:50%}.scalar-app .centered-x{position:absolute}.scalar-app .sr-only{border-width:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.scalar-app .absolute{position:absolute}.scalar-app .fixed{position:fixed}.scalar-app .sticky{position:sticky}.scalar-app .inset-0{inset:0}.scalar-app .inset-x-0{inset-inline:0}.scalar-app .inset-x-1{inset-inline:4px}.scalar-app .inset-x-px{inset-inline:1px}.scalar-app .-top-\(--scalar-address-bar-height\){top:calc(var(--scalar-address-bar-height)*-1)}.scalar-app .-top-1\.25{top:-5px}.scalar-app .-top-\[104px\]{top:-104px}.scalar-app .top-2{top:8px}.scalar-app .top-12{top:48px}.scalar-app .top-\[calc\(100\%\+4px\)\]{top:calc(100% + 4px)}.scalar-app .-right-1\.25{right:-5px}.scalar-app .-right-\[30px\]{right:-30px}.scalar-app .right-0{right:0}.scalar-app .right-1{right:4px}.scalar-app .right-1\.5{right:6px}.scalar-app .right-1\/2{right:50%}.scalar-app .right-2{right:8px}.scalar-app .right-4{right:16px}.scalar-app .right-7{right:28px}.scalar-app .right-14{right:56px}.scalar-app .right-16{right:64px}.scalar-app .bottom-1{bottom:4px}.scalar-app .bottom-1\/2{bottom:50%}.scalar-app .bottom-\[var\(--scalar-border-width\)\]{bottom:var(--scalar-border-width)}.scalar-app .left-0{left:0}.scalar-app .left-1\/2{left:50%}.scalar-app .left-3{left:12px}.scalar-app .-z-1{z-index:-1}.scalar-app .z-0{z-index:0}.scalar-app .z-1,.scalar-app .z-\[1\]{z-index:1}.scalar-app .z-2{z-index:2}.scalar-app .z-10{z-index:10}.scalar-app .z-20{z-index:20}.scalar-app .z-50{z-index:50}.scalar-app .z-\[1002\]{z-index:1002}.scalar-app .z-context{z-index:1000}.scalar-app .z-context-plus{z-index:1001}.scalar-app .z-overlay{z-index:10000}.scalar-app .order-last{order:9999}.scalar-app .col-span-full{grid-column:1/-1}.scalar-app .-m-0\.5{margin:-2px}.scalar-app .m-4{margin:16px}.scalar-app .m-auto{margin:auto}.scalar-app .m-header{margin:48px}.scalar-app .mx-1{margin-inline:4px}.scalar-app .mx-auto{margin-inline:auto}.scalar-app .-my-0\.5{margin-block:-2px}.scalar-app .-my-1{margin-block:-4px}.scalar-app .my-12{margin-block:48px}.scalar-app .-mt-\[\.5px\]{margin-top:-.5px}.scalar-app .mt-0\.25,.scalar-app .mt-px{margin-top:1px}.scalar-app .mt-1{margin-top:4px}.scalar-app .mt-1\.5{margin-top:6px}.scalar-app .mt-3{margin-top:12px}.scalar-app .mt-5{margin-top:20px}.scalar-app .mt-10{margin-top:40px}.scalar-app .mt-\[0\.5px\]{margin-top:.5px}.scalar-app .-mr-0\.5{margin-right:-2px}.scalar-app .-mr-1{margin-right:-4px}.scalar-app .-mr-1\.5{margin-right:-6px}.scalar-app .-mr-3{margin-right:-12px}.scalar-app .mr-0\.5{margin-right:2px}.scalar-app .mr-0\.75{margin-right:3px}.scalar-app .mr-1\.5{margin-right:6px}.scalar-app .mr-1\.25{margin-right:5px}.scalar-app .mr-2{margin-right:8px}.scalar-app .mr-2\.5{margin-right:10px}.scalar-app .mr-3{margin-right:12px}.scalar-app .mr-\[6\.25px\]{margin-right:6.25px}.scalar-app .mr-auto{margin-right:auto}.scalar-app .-mb-\[var\(--scalar-border-width\)\]{margin-bottom:calc(var(--scalar-border-width)*-1)}.scalar-app .mb-2{margin-bottom:8px}.scalar-app .mb-4{margin-bottom:16px}.scalar-app .mb-5{margin-bottom:20px}.scalar-app .mb-\[\.5px\]{margin-bottom:.5px}.scalar-app .-ml-0\.5{margin-left:-2px}.scalar-app .-ml-0\.25{margin-left:-1px}.scalar-app .-ml-1{margin-left:-4px}.scalar-app .-ml-2{margin-left:-8px}.scalar-app .-ml-12{margin-left:-48px}.scalar-app .ml-0\.5{margin-left:2px}.scalar-app .ml-0\.75{margin-left:3px}.scalar-app .ml-1{margin-left:4px}.scalar-app .ml-1\.25{margin-left:5px}.scalar-app .ml-3{margin-left:12px}.scalar-app .ml-auto{margin-left:auto}.scalar-app .box-content{box-sizing:initial}.home-page-features-wrapper .feature .feature-half.assets span.not-demo,.scalar-app .flex-center{align-items:center;display:flex;justify-content:center}.scalar-app .line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.scalar-app .\!block{display:block!important}.scalar-app .\!flex{display:flex!important}.scalar-app .block{display:block}.scalar-app .contents{display:contents}.scalar-app .flex{display:flex}.scalar-app .grid{display:grid}.scalar-app .inline,.tags_jXut{display:inline}.scalar-app .table{display:table}.scalar-app .aspect-\[4\/3\]{aspect-ratio:4/3}.scalar-app .aspect-square{aspect-ratio:1}.scalar-app .size-1\.5{height:6px;width:6px}.scalar-app .size-2\.5{height:10px;width:10px}.scalar-app .size-3{height:12px;width:12px}.scalar-app .size-3\.5{height:14px;width:14px}.scalar-app .size-3\/4{height:75%;width:75%}.scalar-app .size-4{height:16px;width:16px}.scalar-app .size-7{height:28px;width:28px}.scalar-app .size-8{height:32px;width:32px}.scalar-app .size-10{height:40px;width:40px}.scalar-app .size-full{height:100%;width:100%}.scalar-app .h-\(--scalar-address-bar-height\){height:var(--scalar-address-bar-height)}.scalar-app .h-1\.5{height:6px}.scalar-app .h-2\.5{height:10px}.scalar-app .h-2\.25{height:9px}.scalar-app .h-3{height:12px}.scalar-app .h-3\.5{height:14px}.scalar-app .h-4{height:16px}.scalar-app .h-5{height:20px}.scalar-app .h-6{height:24px}.scalar-app .h-7{height:28px}.scalar-app .h-8{height:32px}.scalar-app .h-9{height:36px}.scalar-app .h-10{height:40px}.scalar-app .h-12,.scalar-app .h-header{height:48px}.scalar-app .h-16{height:64px}.scalar-app .h-64{height:256px}.scalar-app .h-\[68px\]{height:68px}.scalar-app .h-\[calc\(100\%-273\.5px\)\]{height:calc(100% - 273.5px)}.scalar-app .h-\[calc\(100\%_-_50px\)\]{height:calc(100% - 50px)}.scalar-app .h-dvh{height:100dvh}.scalar-app .h-fit{height:fit-content}.scalar-app .h-min{height:min-content}.scalar-app .h-px{height:1px}.scalar-app .h-screen{height:100vh}.scalar-app .\!max-h-\[initial\]{max-height:none!important}.scalar-app .max-h-8{max-height:32px}.scalar-app .max-h-40{max-height:160px}.scalar-app .max-h-80{max-height:320px}.scalar-app .max-h-\[40dvh\]{max-height:40dvh}.scalar-app .max-h-\[50dvh\]{max-height:50dvh}.scalar-app .max-h-\[60svh\]{max-height:60svh}.scalar-app .max-h-\[auto\]{max-height:auto}.scalar-app .max-h-\[calc\(100\%-32px\)\]{max-height:calc(100% - 32px)}.scalar-app .max-h-\[inherit\]{max-height:inherit}.scalar-app .max-h-fit{max-height:fit-content}.scalar-app .max-h-screen{max-height:100vh}.scalar-app .min-h-0{min-height:0}.scalar-app .min-h-8{min-height:32px}.scalar-app .min-h-10{min-height:40px}.scalar-app .min-h-11{min-height:44px}.scalar-app .min-h-16,.scalar-app .min-h-\[64px\]{min-height:64px}.scalar-app .min-h-20{min-height:80px}.scalar-app .min-h-\[65px\]{min-height:65px}.scalar-app .min-h-\[calc\(1rem\*4\)\]{min-height:4rem}.scalar-app .min-h-\[calc\(4rem\+0\.5px\)\]{min-height:calc(4rem + .5px)}.scalar-app .min-h-\[calc\(4rem\+1px\)\]{min-height:calc(4rem + 1px)}.scalar-app .min-h-fit,[data-v-5997a667] .cm-content,[data-v-d486ea71] .cm-content{min-height:fit-content}.scalar-app .\!w-fit{width:fit-content!important}.scalar-app .w-0\.5{width:2px}.scalar-app .w-1\.5{width:6px}.scalar-app .w-1\/2{width:50%}.scalar-app .w-2\.5{width:10px}.scalar-app .w-2\.25{width:9px}.scalar-app .w-3{width:12px}.scalar-app .w-3\.5{width:14px}.scalar-app .w-4{width:16px}.scalar-app .w-5{width:20px}.scalar-app .w-6{width:24px}.scalar-app .w-7{width:28px}.scalar-app .w-8{width:32px}.scalar-app .w-10{width:40px}.scalar-app .w-16{width:64px}.scalar-app .w-20{width:80px}.scalar-app .w-56{width:224px}.scalar-app .w-64{width:256px}.scalar-app .w-72{width:288px}.scalar-app .w-\[60px\]{width:60px}.scalar-app .w-\[100px\]{width:100px}.scalar-app .w-\[150px\]{width:150px}.scalar-app .w-\[calc\(100\%-10px\)\]{width:calc(100% - 10px)}.scalar-app .w-\[calc\(100\%_-_8px\)\]{width:calc(100% - 8px)}.scalar-app .w-\[inherit\]{width:inherit}.scalar-app .w-auto{width:auto}.scalar-app .w-dvw{width:100dvw}.scalar-app .w-max{width:max-content}.scalar-app .max-w-8{max-width:32px}.scalar-app .max-w-40{max-width:160px}.scalar-app .max-w-\[14px\]{max-width:14px}.scalar-app .max-w-\[16rem\]{max-width:16rem}.scalar-app .max-w-\[37px\]{max-width:37px}.scalar-app .max-w-\[120px\]{max-width:120px}.scalar-app .max-w-\[150px\]{max-width:150px}.scalar-app .max-w-\[380px\]{max-width:380px}.scalar-app .max-w-\[420px\]{max-width:420px}.scalar-app .max-w-\[calc\(100dvw-24px\)\]{max-width:calc(100dvw - 24px)}.scalar-app .min-w-2\.25{min-width:9px}.scalar-app .min-w-3\.5{min-width:14px}.scalar-app .min-w-4{min-width:16px}.scalar-app .min-w-8{min-width:32px}.scalar-app .min-w-32{min-width:128px}.scalar-app .min-w-48{min-width:192px}.scalar-app .min-w-\[37px\]{min-width:37px}.scalar-app .min-w-\[100px\]{min-width:100px}.scalar-app .min-w-\[150px\]{min-width:150px}.scalar-app .min-w-\[296px\]{min-width:296px}.scalar-app .min-w-fit,[data-v-103d9d56] .cm-lineNumbers .cm-gutterElement,[data-v-776dfdde] .cm-lineNumbers .cm-gutterElement{min-width:fit-content}.scalar-app .min-w-full{min-width:100%}.scalar-app .flex-1{flex:1}.scalar-app .shrink-0{flex-shrink:0}.scalar-app .-translate-x-1\/2{--tw-translate-x:-50%}.scalar-app .translate-x-0{--tw-translate-x:0px}.scalar-app .translate-x-1\/2{--tw-translate-x:50%}.scalar-app .translate-y-1\/2{--tw-translate-y:50%}.scalar-app .scale-75{--tw-scale-x:75%;--tw-scale-y:75%;--tw-scale-z:75%}.scalar-app .rotate-90{rotate:90deg}.scalar-app .rotate-180{rotate:180deg}.scalar-app .transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.scalar-app .cursor-auto{cursor:auto}.scalar-app .cursor-default,.scalar-app .disabled\:cursor-default:disabled{cursor:default}.scalar-app .cursor-grab{cursor:grab}.scalar-app .cursor-help{cursor:help}.scalar-app .cursor-pointer{cursor:pointer}.scalar-app .cursor-text{cursor:text}.scalar-app .resize{resize:both}.scalar-app .resize-none{resize:none}.scalar-app .auto-rows-\[32px\]{grid-auto-rows:32px}.scalar-app .auto-rows-auto{grid-auto-rows:auto}.scalar-app .grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.scalar-app .grid-cols-\[44px_1fr_repeat\(3\,auto\)\]{grid-template-columns:44px 1fr repeat(3,auto)}.scalar-app .grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.scalar-app .grid-cols-\[repeat\(auto-fill\,minmax\(32px\,1fr\)\)\]{grid-template-columns:repeat(auto-fill,minmax(32px,1fr))}.scalar-app .flex-col{flex-direction:column}.scalar-app .flex-row{flex-direction:row}.scalar-app .flex-wrap{flex-wrap:wrap}.scalar-app .content-between{align-content:space-between}.scalar-app .content-start{align-content:flex-start}.scalar-app .justify-between{justify-content:space-between}.mdxPageWrapper_j9I6,.scalar-app .justify-center{justify-content:center}.scalar-app .justify-end{justify-content:flex-end}.scalar-app .justify-start{justify-content:flex-start}.scalar-app .\!gap-2{gap:8px!important}.scalar-app .gap-0\.5{gap:2px}.scalar-app .gap-0\.75{gap:3px}.scalar-app .gap-1{gap:4px}.scalar-app .gap-1\.5{gap:6px}.scalar-app .gap-1\.75{gap:7px}.scalar-app .gap-2{gap:8px}.scalar-app .gap-2\.5{gap:10px}.scalar-app .gap-3{gap:12px}.scalar-app .gap-4{gap:16px}.scalar-app .gap-8{gap:32px}.scalar-app .gap-10{gap:40px}.scalar-app .gap-12{gap:48px}.scalar-app .gap-\[1\.5px\]{gap:1.5px}.scalar-app .gap-px{gap:1px}.scalar-app .gap-x-2\.5{column-gap:10px}:where(.scalar-app .space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(4px*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(4px*var(--tw-space-x-reverse))}:where(.scalar-app .divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(var(--scalar-border-width)*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(var(--scalar-border-width)*var(--tw-divide-y-reverse))}.scalar-app .self-center{align-self:center}.scalar-app .overflow-auto,[data-v-103d9d56] .cm-scroller,[data-v-776dfdde] .cm-scroller{overflow:auto}.scalar-app .overflow-visible{overflow:visible}.scalar-app .overflow-x-auto,.scroll-timeline-x-hidden[data-v-cb2a35da],.scroll-timeline-x-hidden[data-v-f5565642]{overflow-x:auto}.scalar-app .overflow-x-hidden{overflow-x:hidden}.scalar-app .overflow-x-scroll{overflow-x:scroll}.scalar-app .overflow-y-auto{overflow-y:auto}.scalar-app .overflow-y-hidden{overflow-y:hidden}.scalar-app .overflow-y-scroll{overflow-y:scroll}.scalar-app .\!rounded-none{border-radius:0!important}.scalar-app .rounded,.scalar-app .rounded-md{border-radius:var(--scalar-radius)}.scalar-app .rounded-\[10px\]{border-radius:10px}.scalar-app .rounded-full{border-radius:9999px}.scalar-app .rounded-none{border-radius:0}.scalar-app .rounded-px{border-radius:1px}.scalar-app .rounded-xl{border-radius:var(--scalar-radius-xl)}.scalar-app .rounded-t{border-top-left-radius:var(--scalar-radius);border-top-right-radius:var(--scalar-radius)}.scalar-app .rounded-t-lg{border-top-left-radius:var(--scalar-radius-lg);border-top-right-radius:var(--scalar-radius-lg)}.codeBlockTitle_OeMC+.codeBlockContent_QJqH .codeBlock_a8dz,.scalar-app .rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.scalar-app .rounded-t-xl{border-top-left-radius:var(--scalar-radius-xl);border-top-right-radius:var(--scalar-radius-xl)}.scalar-app .rounded-b{border-bottom-left-radius:var(--scalar-radius);border-bottom-right-radius:var(--scalar-radius)}.scalar-app .last\:rounded-b-lg:last-child,.scalar-app .rounded-b-lg{border-bottom-left-radius:var(--scalar-radius-lg);border-bottom-right-radius:var(--scalar-radius-lg)}.scalar-app .rounded-b-none{border-bottom-left-radius:0;border-bottom-right-radius:0}.scalar-app .rounded-b-xl{border-bottom-left-radius:var(--scalar-radius-xl);border-bottom-right-radius:var(--scalar-radius-xl)}.scalar-app .\!border-0{border-style:var(--tw-border-style)!important;border-width:0!important}.scalar-app .border-b,.scalar-app .border-b-0,.scalar-app .border-b-\[1px\]{border-bottom-style:var(--tw-border-style)}.scalar-app .border{border-style:var(--tw-border-style);border-width:var(--scalar-border-width)}.scalar-app .border-0{border-style:var(--tw-border-style);border-width:0}.scalar-app .border-\[1\.5px\]{border-style:var(--tw-border-style);border-width:1.5px}.scalar-app .border-x{border-inline-style:var(--tw-border-style);border-inline-width:var(--scalar-border-width)}.scalar-app .border-x-0{border-inline-style:var(--tw-border-style);border-inline-width:0}.scalar-app .border-y{border-block-style:var(--tw-border-style);border-block-width:var(--scalar-border-width)}.scalar-app .border-t{border-top-style:var(--tw-border-style);border-top-width:var(--scalar-border-width)}.scalar-app .border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.scalar-app .\!border-r{border-right-style:var(--tw-border-style)!important;border-right-width:var(--scalar-border-width)!important}.scalar-app .border-r{border-right-style:var(--tw-border-style);border-right-width:var(--scalar-border-width)}.scalar-app .border-r-0,.scalar-app .last\:border-r-0:last-child{border-right-style:var(--tw-border-style);border-right-width:0}.scalar-app .border-r-1{border-right-style:var(--tw-border-style);border-right-width:1px}.scalar-app .border-b{border-bottom-width:var(--scalar-border-width)}.scalar-app .border-b-0{border-bottom-width:0}.scalar-app .border-b-\[1px\]{border-bottom-width:1px}.scalar-app .border-l{border-left-style:var(--tw-border-style);border-left-width:var(--scalar-border-width)}.scalar-app .border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.scalar-app .border-dashed{--tw-border-style:dashed;border-style:dashed}.scalar-app .border-none{--tw-border-style:none;border-style:none}.scalar-app .border-none\!{--tw-border-style:none!important;border-style:none!important}.scalar-app .\!border-current{border-color:currentColor!important}.scalar-app .border-\(--scalar-background-3\){border-color:var(--scalar-background-3)}.scalar-app .border-\(--scalar-color-alert\){border-color:var(--scalar-color-alert)}.scalar-app .border-c-1{border-color:var(--scalar-color-1)}.scalar-app .border-c-3{border-color:var(--scalar-color-3)}.scalar-app .border-c-accent,.scalar-app .border-c-accent\/30{border-color:var(--scalar-color-accent)}.scalar-app .border-c-danger{border-color:var(--scalar-color-danger)}.scalar-app .border-transparent{border-color:#0000}.scalar-app .border-r-transparent{border-right-color:#0000}.scalar-app .bg-b-danger{background-color:var(--scalar-background-danger)}.scalar-app .bg-c-3\/5,.scalar-app .bg-grey{background-color:var(--scalar-color-3)}.scalar-app .bg-current{background-color:currentColor}.scalar-app .bg-orange{background-color:var(--scalar-color-orange)}.scalar-app .bg-sidebar-b-1{background-color:var(--scalar-sidebar-background-1,var(--scalar-background-1))}.scalar-app .bg-sidebar-b-active{background-color:var(--scalar-sidebar-item-active-background,var(--scalar-background-2))}.scalar-app .fill-current{fill:currentColor}.scalar-app .stroke-\[1\.5\]{stroke-width:1.5px}.scalar-app .stroke-\[1\.75\]{stroke-width:1.75px}.scalar-app .stroke-\[2\.25\]{stroke-width:2.25px}.scalar-app .object-contain{object-fit:contain}.cm-foldGutter .cm-gutterElement .cm-foldMarker,.scalar-app .p-0\.5{padding:2px}.scalar-app .p-0\.75,.scalar-app .p-\[3px\]{padding:3px}.scalar-app .p-1{padding:4px}.scalar-app .p-1\.5{padding:6px}.scalar-app .p-1\.25,.scalar-app .p-\[5px\]{padding:5px}.scalar-app .p-1\.75{padding:7px}.scalar-app .p-3{padding:12px}.scalar-app .p-4{padding:16px}.scalar-app .\!px-3{padding-inline:12px!important}.scalar-app .px-0{padding-inline:0}.scalar-app .px-0\.5{padding-inline:2px}.scalar-app .px-0\.75{padding-inline:3px}.scalar-app .px-1{padding-inline:4px}.scalar-app .px-1\.5{padding-inline:6px}.scalar-app .px-1\.25{padding-inline:5px}.scalar-app .px-2\.5{padding-inline:10px}.scalar-app .px-3{padding-inline:12px}.scalar-app .px-3\.5{padding-inline:14px}.scalar-app .px-4{padding-inline:16px}.scalar-app .px-5{padding-inline:20px}.scalar-app .px-6{padding-inline:24px}.scalar-app .px-8{padding-inline:32px}.scalar-app .\!py-1\.5{padding-block:6px!important}.scalar-app .py-0{padding-block:0}.scalar-app .py-0\.5{padding-block:2px}.scalar-app .py-0\.25,.scalar-app .py-px{padding-block:1px}.scalar-app .py-0\.75{padding-block:3px}.scalar-app .py-1{padding-block:4px}.scalar-app .py-1\.5{padding-block:6px}.scalar-app .py-1\.25{padding-block:5px}.scalar-app .py-1\.75{padding-block:7px}.scalar-app .py-2{padding-block:8px}.scalar-app .py-2\.5{padding-block:10px}.scalar-app .py-3{padding-block:12px}.scalar-app .py-5{padding-block:20px}.scalar-app .py-8{padding-block:32px}.scalar-app .pt-0{padding-top:0}.scalar-app .pt-2{padding-top:8px}.scalar-app .pt-3{padding-top:12px}.scalar-app .pt-4{padding-top:16px}.scalar-app .pt-6,.swagger-editor .start[data-v-673f10b6]{padding-top:24px}.scalar-app .pt-8{padding-top:32px}.scalar-app .pt-px{padding-top:1px}.scalar-app .pr-0\.75{padding-right:3px}.scalar-app .pr-1{padding-right:4px}.scalar-app .pr-1\.5{padding-right:6px}.scalar-app .pr-2{padding-right:8px}.scalar-app .pr-2\.5{padding-right:10px}.scalar-app .pr-2\.25{padding-right:9px}.scalar-app .pr-3{padding-right:12px}.scalar-app .pr-6{padding-right:24px}.scalar-app .pr-8{padding-right:32px}.scalar-app .pr-9{padding-right:36px}.scalar-app .pr-10{padding-right:40px}.scalar-app .pr-12{padding-right:48px}.scalar-app .pr-\[26px\]{padding-right:26px}.scalar-app .pb-1\.5{padding-bottom:6px}.scalar-app .pb-2{padding-bottom:8px}.scalar-app .pb-5{padding-bottom:20px}.scalar-app .pb-6{padding-bottom:24px}.scalar-app .pb-8{padding-bottom:32px}.scalar-app .pb-14{padding-bottom:56px}.scalar-app .pb-\[75px\]{padding-bottom:75px}.scalar-app .\!pl-3{padding-left:12px!important}.scalar-app .pl-1{padding-left:4px}.scalar-app .pl-1\.5{padding-left:6px}.scalar-app .pl-1\.25{padding-left:5px}.scalar-app .pl-2{padding-left:8px}.scalar-app .pl-3{padding-left:12px}.scalar-app .pl-5{padding-left:20px}.scalar-app .pl-6{padding-left:24px}.scalar-app .pl-8\.5{padding-left:34px}.scalar-app .pl-9{padding-left:36px}.scalar-app .pl-12{padding-left:48px}.scalar-app .pl-px{padding-left:1px}.scalar-app .font-sans{font-family:var(--scalar-font)}.scalar-app .text-base{font-size:var(--scalar-font-size-3);line-height:var(--tw-leading,1.14286)}.scalar-app .text-base\/5{font-size:var(--scalar-font-size-3);line-height:20px}.scalar-app .text-xl{font-size:var(--scalar-font-size-1);line-height:var(--tw-leading,1.14286)}.scalar-app .text-3xs{font-size:var(--scalar-font-size-7)}.scalar-app .text-\[6px\]{font-size:6px}.scalar-app .text-\[9px\]{font-size:9px}.scalar-app .text-\[11px\]{font-size:11px}.scalar-app .text-\[21px\]{font-size:21px}.scalar-app .text-sm{font-size:var(--scalar-font-size-4)}.scalar-app .text-xs{font-size:var(--scalar-font-size-5)}.scalar-app .text-xxs{font-size:var(--scalar-font-size-6)}.scalar-app .\!leading-\[6px\]{--tw-leading:6px!important;line-height:6px!important}.scalar-app .leading-4{--tw-leading:16px;line-height:16px}.scalar-app .leading-5,.scalar-app .leading-\[20px\]{--tw-leading:20px;line-height:20px}.scalar-app .leading-5\.5,.scalar-app .leading-\[22px\]{--tw-leading:22px;line-height:22px}.scalar-app .leading-6{--tw-leading:24px;line-height:24px}.scalar-app .leading-\[1\.44\]{--tw-leading:1.44;line-height:1.44}.scalar-app .leading-\[7px\]{--tw-leading:7px;line-height:7px}.scalar-app .leading-\[21px\]{--tw-leading:21px;line-height:21px}.scalar-app .leading-\[normal\]{--tw-leading:normal;line-height:normal}.scalar-app .leading-none{--tw-leading:1;line-height:1}.scalar-app .leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.scalar-app .leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.scalar-app .font-bold{--tw-font-weight:var(--scalar-bold);font-weight:var(--scalar-bold)}.scalar-app .font-medium{--tw-font-weight:var(--scalar-semibold);font-weight:var(--scalar-semibold)}.scalar-app .font-normal{--tw-font-weight:var(--scalar-regular);font-weight:var(--scalar-regular)}.scalar-app .text-balance{text-wrap:balance}.scalar-app .text-nowrap{text-wrap:nowrap}.scalar-app .text-pretty{text-wrap:pretty}.scalar-app .break-all{word-break:break-all}.scalar-app .text-ellipsis{text-overflow:ellipsis}.scalar-app .whitespace-pre{white-space:pre}.scalar-app .whitespace-pre-wrap{white-space:pre-wrap}.scalar-app .\!text-c-1{color:var(--scalar-color-1)!important}.scalar-app .text-b-1{color:var(--scalar-background-1)}.scalar-app .text-b-2{color:var(--scalar-background-2)}.scalar-app .text-border{color:var(--scalar-border-color)}.scalar-app .text-c-accent{color:var(--scalar-color-accent)}.scalar-app .text-c-btn{color:var(--scalar-button-1-color)}.scalar-app .text-c-danger{color:var(--scalar-color-danger)}.scalar-app .text-sidebar-c-2{color:var(--scalar-sidebar-color-2,var(--scalar-color-2))}.scalar-app .text-sidebar-c-active{color:var(--scalar-sidebar-color-active,var(--scalar-sidebar-color-1))}.scalar-app .text-transparent{color:#0000}.scalar-app .text-white{color:#fff}.scalar-app .text-yellow{color:var(--scalar-color-yellow)}.scalar-app .line-through{text-decoration-line:line-through}.scalar-app .no-underline{text-decoration-line:none}.scalar-app .decoration-c-3{text-decoration-color:var(--scalar-color-3)}.scalar-app .underline-offset-2{text-underline-offset:2px}.scalar-app .opacity-0{opacity:0}.scalar-app .opacity-50{opacity:.5}.scalar-app .bg-blend-normal{background-blend-mode:normal}.scalar-app .mix-blend-luminosity{mix-blend-mode:luminosity}.scalar-app .shadow{--tw-shadow:var(--scalar-shadow-1)}.scalar-app .shadow-\[-8px_0_4px_var\(--scalar-background-1\)\]{--tw-shadow:-8px 0 4px var(--tw-shadow-color,var(--scalar-background-1))}.scalar-app .shadow-\[0_-8px_0_8px_var\(--scalar-background-1\)\,0_0_8px_8px_var\(--scalar-background-1\)\]{--tw-shadow:0 -8px 0 8px var(--tw-shadow-color,var(--scalar-background-1)),0 0 8px 8px var(--tw-shadow-color,var(--scalar-background-1))}.scalar-app .shadow-border{--tw-shadow:inset 0 0 0 var(--tw-shadow-color,var(--scalar-border-width)) var(--scalar-border-color)}.scalar-app .shadow-lg{--tw-shadow:var(--scalar-shadow-2)}.scalar-app .shadow-none{--tw-shadow:0 0 #0000}.scalar-app .outline{outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .outline-offset-2{outline-offset:2px}.scalar-app .outline-b-3{outline-color:var(--scalar-background-3)}.scalar-app .outline-c-danger{outline-color:var(--scalar-color-danger)}.scalar-app .blur{--tw-blur:blur(8px)}.scalar-app .blur,.scalar-app .brightness-90,.scalar-app .brightness-\[\.9\],.scalar-app .brightness-lifted,.scalar-app .filter{filter:var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, )}.scalar-app .brightness-90{--tw-brightness:brightness(90%)}.scalar-app .brightness-\[\.9\]{--tw-brightness:brightness(.9)}.scalar-app .brightness-lifted{--tw-brightness:brightness(var(--scalar-lifted-brightness))}.scalar-app .transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,backdrop-filter,display,content-visibility,overlay,pointer-events}.scalar-app .transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to}.scalar-app .transition-opacity{transition-property:opacity}.scalar-app .transition-transform{transition-property:transform,translate,scale,rotate}.scalar-app .transition-none{transition-property:none}.scalar-app .duration-100{--tw-duration:.1s;transition-duration:.1s}.scalar-app .duration-150{--tw-duration:.15s;transition-duration:.15s}.scalar-app .duration-200{--tw-duration:.2s;transition-duration:.2s}.scalar-app .duration-300{--tw-duration:.3s;transition-duration:.3s}.scalar-app .ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.scalar-app .focus\:outline-none:focus,.scalar-app .outline-none{--tw-outline-style:none;outline-style:none}.scalar-app .select-none{-webkit-user-select:none;user-select:none}.scalar-app .\[--scalar-address-bar-height\:32px\]{--scalar-address-bar-height:32px}.scalar-app .app-drag-region{-webkit-app-region:drag}.scalar-app .app-no-drag-region{-webkit-app-region:no-drag}:is(.scalar-app .\*\:flex>*){display:flex}:is(.scalar-app .\*\:h-8>*){height:32px}:is(.scalar-app .\*\:cursor-pointer>*){cursor:pointer}:is(.scalar-app .\*\:items-center>*){align-items:center}:is(.scalar-app .\*\:rounded-none>*){border-radius:0}:is(.scalar-app .\*\:border-t>*){border-top-style:var(--tw-border-style);border-top-width:var(--scalar-border-width)}:is(.scalar-app .\*\:border-b-0>*){border-bottom-style:var(--tw-border-style);border-bottom-width:0}:is(.scalar-app .\*\:px-1\.5>*){padding-inline:6px}:is(.scalar-app .\*\:pl-4>*){padding-left:16px}.scalar-app .group-first\/row\:border-t-0:is(:where(.group\/row):first-child *){border-top-style:var(--tw-border-style);border-top-width:0}.scalar-app .group-last\:border-b-transparent:is(:where(.group):last-child *){border-bottom-color:#0000}.scalar-app .group-last\/label\:rounded-br-lg:is(:where(.group\/label):last-child *){border-bottom-right-radius:var(--scalar-radius-lg)}.scalar-app .group-focus-within\:flex:is(:where(.group):focus-within *){display:flex}.scalar-app .group-focus-visible\:opacity-100:is(:where(.group):focus-visible *){opacity:1}.scalar-app .group-focus-visible\:outline:is(:where(.group):focus-visible *){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .group-has-focus-visible\:hidden:is(:where(.group):has(:focus-visible) *){display:none}.scalar-app .group-has-\[\.cm-focused\]\:z-1:is(:where(.group):has(.cm-focused) *){z-index:1}.scalar-app .group-has-\[\.cm-focused\]\:flex:is(:where(.group):has(.cm-focused) *){display:flex}.scalar-app .group-has-\[\.cm-focused\]\:pr-6:is(:where(.group):has(.cm-focused) *){padding-right:24px}.scalar-app .group-has-\[\.cm-focused\]\:pr-10:is(:where(.group):has(.cm-focused) *){padding-right:40px}.scalar-app .group-has-\[\:focus-visible\]\:hidden:is(:where(.group):has(:focus-visible) *){display:none}.scalar-app .group-has-\[\:focus-visible\]\:opacity-100:is(:where(.group):has(:focus-visible) *){opacity:1}.scalar-app .group-has-\[\:focus-visible\]\/cell\:border-c-accent:is(:where(.group\/cell):has(:focus-visible) *){border-color:var(--scalar-color-accent)}.scalar-app .group-has-\[\:focus-visible\]\/cell\:opacity-100:is(:where(.group\/cell):has(:focus-visible) *){opacity:1}.scalar-app .group-has-\[\:focus-visible\]\/input\:block:is(:where(.group\/input):has(:focus-visible) *){display:block}.scalar-app .group-has-\[input\]\/label\:mr-0:is(:where(.group\/label):has(:is(input)) *){margin-right:0}.scalar-app .group-aria-expanded\/button\:rotate-180:is(:where(.group\/button)[aria-expanded=true] *),.scalar-app .group-aria-expanded\/combobox-button\:rotate-180:is(:where(.group\/combobox-button)[aria-expanded=true] *){rotate:180deg}.scalar-app .group-\[\.alert\]\:bg-b-alert:is(:where(.group).alert *){background-color:var(--scalar-background-alert)}.scalar-app .group-\[\.alert\]\:bg-transparent:is(:where(.group).alert *){background-color:#0000}.scalar-app .group-\[\.alert\]\:shadow-none:is(:where(.group).alert *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .group-\[\.alert\]\:outline-orange:is(:where(.group).alert *){outline-color:var(--scalar-color-orange)}.scalar-app .group-\[\.error\]\:bg-b-danger:is(:where(.group).error *){background-color:var(--scalar-background-danger)}.scalar-app .group-\[\.error\]\:bg-transparent:is(:where(.group).error *){background-color:#0000}.scalar-app .group-\[\.error\]\:text-red:is(:where(.group).error *){color:var(--scalar-color-red)}.scalar-app .group-\[\.error\]\:shadow-none:is(:where(.group).error *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.scalar-app .group-\[\.error\]\:outline-red:is(:where(.group).error *){outline-color:var(--scalar-color-red)}.scalar-app .peer-checked\:text-c-1:is(:where(.peer):checked~*){color:var(--scalar-color-1)}.scalar-app .peer-has-\[\.cm-focused\]\:opacity-0:is(:where(.peer):has(.cm-focused)~*){opacity:0}.scalar-app .peer-has-\[\.color-selector\]\:hidden:is(:where(.peer):has(.color-selector)~*){display:none}.scalar-app .before\:top-0:before{content:var(--tw-content);top:0}.scalar-app .before\:left-3:before{content:var(--tw-content);left:12px}.scalar-app .before\:left-\[calc\(\.75rem_\+_\.5px\)\]:before{content:var(--tw-content);left:calc(.75rem + .5px)}.scalar-app .before\:z-1:before{content:var(--tw-content);z-index:1}.scalar-app .before\:h-\[calc\(100\%_\+_\.5px\)\]:before{content:var(--tw-content);height:calc(100% + .5px)}.scalar-app .before\:w-\[\.5px\]:before{content:var(--tw-content);width:.5px}.scalar-app .before\:bg-border:before{background-color:var(--scalar-border-color);content:var(--tw-content)}.scalar-app .after\:content-\[\'\:\'\]:after{--tw-content:":";content:var(--tw-content)}:is(.scalar-app .\*\:first\:line-clamp-1>*):first-child{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.scalar-app .\*\:first\:rounded-l>*):first-child{border-bottom-left-radius:var(--scalar-radius);border-top-left-radius:var(--scalar-radius)}:is(.scalar-app .\*\:first\:border-t-0>*):first-child,:is(.scalar-app .first\:\*\:border-t-0:first-child>*){border-top-style:var(--tw-border-style);border-top-width:0}:is(.scalar-app .\*\:first\:text-ellipsis>*):first-child{text-overflow:ellipsis}:is(.scalar-app .\*\:last\:rounded-r>*):last-child{border-bottom-right-radius:var(--scalar-radius);border-top-right-radius:var(--scalar-radius)}.scalar-app .last\:before\:h-full:last-child:before{content:var(--tw-content);height:100%}.scalar-app .last-of-type\:first-of-type\:border-b-0:last-of-type:first-of-type{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.scalar-app .focus-within\:z-20:focus-within{z-index:20}.scalar-app .focus-within\:border-\(--scalar-background-3\):focus-within{border-color:var(--scalar-background-3)}.scalar-app .focus-within\:bg-b-1:focus-within{background-color:var(--scalar-background-1)}.scalar-app .focus-within\:text-c-1:focus-within{color:var(--scalar-color-1)}.scalar-app .focus\:border-b-1:focus{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--scalar-background-1)}.cm-foldPlaceholder:hover,.nav-item[data-v-2e741aab]:hover,.nav-item[data-v-507381a3]:hover,.scalar-app .active\:text-c-1:active,.scalar-app .focus\:text-c-1:focus{color:var(--scalar-color-1)}.scalar-app .focus-visible\:z-10:focus-visible{z-index:10}.scalar-app .has-focus-visible\:bg-b-1:has(:focus-visible){background-color:var(--scalar-background-1)}.scalar-app .has-focus-visible\:outline:has(:focus-visible){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .has-\[\.empty-sidebar-item\]\:border-t:has(.empty-sidebar-item){border-top-style:var(--tw-border-style);border-top-width:var(--scalar-border-width)}.scalar-app .has-\[\:focus-visible\]\:absolute:has(:focus-visible){position:absolute}.scalar-app .has-\[\:focus-visible\]\:z-1:has(:focus-visible){z-index:1}.scalar-app .has-\[\:focus-visible\]\:rounded-\[4px\]:has(:focus-visible){border-radius:4px}.scalar-app .has-\[\:focus-visible\]\:bg-b-1:has(:focus-visible){background-color:var(--scalar-background-1)}.scalar-app .has-\[\:focus-visible\]\:opacity-100:has(:focus-visible){opacity:1}.scalar-app .has-\[\:focus-visible\]\:outline:has(:focus-visible){outline-style:var(--tw-outline-style);outline-width:1px}.scalar-app .dark\:bg-b-2:where(.dark-mode,.dark-mode *){background-color:var(--scalar-background-2)}.scalar-app .ui-open\:rotate-90[data-headlessui-state~=open],:where([data-headlessui-state~=open]) :is(.scalar-app .ui-open\:rotate-90){rotate:90deg}.scalar-app .ui-open\:rotate-180[data-headlessui-state~=open],:where([data-headlessui-state~=open]) :is(.scalar-app .ui-open\:rotate-180){rotate:180deg}.scalar-app .last\:ui-open\:border-b-0:last-child[data-headlessui-state~=open],:where([data-headlessui-state~=open]) .scalar-app .last\:ui-open\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.scalar-app .ui-not-open\:hidden[data-headlessui-state]:not([data-headlessui-state~=open]),:where([data-headlessui-state]:not([data-headlessui-state~=open])) :is(.scalar-app .ui-not-open\:hidden):not([data-headlessui-state]){display:none}.scalar-app .ui-not-open\:rotate-0[data-headlessui-state]:not([data-headlessui-state~=open]),:where([data-headlessui-state]:not([data-headlessui-state~=open])) :is(.scalar-app .ui-not-open\:rotate-0):not([data-headlessui-state]){rotate:none}.scalar-app .ui-checked\:bg-b-3[data-headlessui-state~=checked],:where([data-headlessui-state~=checked]) :is(.scalar-app .ui-checked\:bg-b-3){background-color:var(--scalar-background-3)}.scalar-app .ui-active\:bg-b-2[data-headlessui-state~=active],:is(.scalar-app .ui-active\:\*\:bg-b-2[data-headlessui-state~=active]>*),:is(:where([data-headlessui-state~=active]) :is(.scalar-app .ui-active\:\*\:bg-b-2)>*),:where([data-headlessui-state~=active]) :is(.scalar-app .ui-active\:bg-b-2){background-color:var(--scalar-background-2)}.app-platform-mac :is(.scalar-app .mac\:pt-10){padding-top:40px}.app-platform-mac :is(.scalar-app .mac\:pl-\[72px\]){padding-left:72px}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}.nav-item[data-v-507381a3]{align-items:center;background:var(--scalar-background-3);border:var(--scalar-border-width) solid var(--scalar-background-2);border-radius:var(--scalar-radius-lg);color:var(--scalar-color-3);cursor:pointer;display:flex;flex:1;justify-content:center;min-width:0;overflow:hidden;padding:4.5px;position:relative}.nav-item-icon-copy[data-v-2e741aab],.nav-item-icon-copy[data-v-507381a3]{-webkit-mask-image:linear-gradient(to left,#0000 0,var(--scalar-background-2) 20px);mask-image:linear-gradient(to left,#0000 0,var(--scalar-background-2) 20px);max-width:100%;overflow:hidden;white-space:nowrap}.nav-item:hover .nav-item-icon-copy[data-v-2e741aab],.nav-item:hover .nav-item-icon-copy[data-v-507381a3]{-webkit-mask-image:linear-gradient(to left,#0000 20px,var(--scalar-background-2) 40px);mask-image:linear-gradient(to left,#0000 20px,var(--scalar-background-2) 40px)}.nav-item-copy[data-v-2e741aab],.nav-item-copy[data-v-507381a3]{max-width:calc(100% - 20px)}.nav-item__active[data-v-2e741aab],.nav-item__active[data-v-507381a3]{background-color:var(--scalar-background-1);border-color:var(--scalar-border-color);color:var(--scalar-color-1)}.dark-mode .nav-item__active[data-v-507381a3],.nav-item__active .nav-item-close[data-v-507381a3]:hover{background-color:var(--scalar-background-2)}.nav-item-close[data-v-2e741aab],.nav-item-close[data-v-507381a3]{background:none;border-radius:var(--scalar-radius);color:var(--scalar-color-3);margin-left:-20px;max-width:20px;opacity:0;padding:2px;position:absolute;right:3px;stroke-width:1.5px}.nav-item:hover .nav-item-close[data-v-507381a3],.scalar .scalar-app-exit[data-v-6118685a]:hover:before{opacity:1}.nav-item-close[data-v-2e741aab]:hover,.nav-item-close[data-v-507381a3]:hover{background-color:var(--scalar-background-4)}.download-app-button[data-v-9b609275],.download-app-button[data-v-cb45fa05]{background:linear-gradient(#ffffffbf,#00000009);box-shadow:0 0 0 .5px var(--scalar-border-color)}.dark-mode .download-app-button[data-v-9b609275],.dark-mode .download-app-button[data-v-cb45fa05]{background:linear-gradient(#ffffff1a,#00000026)}.download-app-button[data-v-9b609275]:hover,.download-app-button[data-v-cb45fa05]:hover,.http-bg-gradient[data-v-076b14a1]:hover{background:linear-gradient(#00000009,#ffffffbf)}.dark-mode .download-app-button[data-v-9b609275]:hover,.dark-mode .download-app-button[data-v-cb45fa05]:hover{background:linear-gradient(#00000026,#ffffff1a)}.http-bg-gradient[data-v-076b14a1]{background:linear-gradient(#ffffffbf,#00000009)}.dark-mode .http-bg-gradient[data-v-076b14a1]{background:linear-gradient(#ffffff09,#00000026)}.dark-mode .http-bg-gradient[data-v-076b14a1]:hover{background:linear-gradient(#00000026,#ffffff09)}.scroll-timeline-x[data-v-e0578855],.scroll-timeline-x[data-v-f4568236]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;-ms-overflow-style:none;overflow:auto;scrollbar-width:none}.commandmenu[data-v-2fe57517],.commandmenu[data-v-dd90fe74]{animation:.3s ease-in-out .1s forwards v;background-color:var(--scalar-background-1);border-radius:var(--scalar-radius-lg);box-shadow:var(--scalar-shadow-2);left:50%;margin:12px;max-height:60dvh;max-width:580px;opacity:0;position:fixed;top:150px;transform:translate3d(-50%,10px,0);width:100%}.commandmenu-overlay[data-v-2fe57517],.commandmenu-overlay[data-v-dd90fe74]{animation:.3s ease-in-out forwards u;background:#0003;cursor:pointer;inset:0;position:fixed}.scalar .scalar-app-layout[data-v-6118685a]{background:var(--scalar-background-1);border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:8px;height:calc(100% - 120px);margin:auto;max-width:1390px;overflow:hidden;position:relative;width:100%}.scalar .scalar-app-exit[data-v-6118685a]{background:#00000038;cursor:pointer;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:-1}.scalar .scalar-app-exit[data-v-6118685a]:before{color:#fff;font-family:sans-serif;font-size:30px;font-weight:100;line-height:50px;opacity:.6;position:absolute;right:12px;text-align:center;top:0}.scalar-container[data-v-6118685a]{align-items:center;bottom:0;display:flex;height:100%;justify-content:center;left:0;overflow:hidden;position:fixed;top:0;visibility:visible;width:100%}.scalar .url-form-input[data-v-6118685a]{min-height:auto!important}.scalar .scalar-container[data-v-6118685a]{line-height:normal}.scalar-client-fade-enter-active[data-v-6118685a],.scalar-client-fade-leave-active[data-v-6118685a]{transition:opacity .35s}.filter-hover-item[data-v-f97cc68c],.rabbitjump[data-v-23d35bb5],.rabbitjump[data-v-feb6f541],.scalar-client-fade-enter-from[data-v-6118685a],.scalar-client-fade-leave-to[data-v-6118685a],.v-enter-from[data-v-1f35725e],.v-enter-from[data-v-7ec8af01]{opacity:0}.open-api-client-button[data-v-c7bdd328]{align-items:center;border-radius:var(--scalar-radius);box-shadow:0 0 0 .5px var(--scalar-border-color);color:var(--scalar-sidebar-color-1);cursor:pointer;display:flex;font-size:var(--scalar-small);gap:6px;height:31px;justify-content:center;line-height:1.385;padding:9px 12px;text-align:center;-webkit-text-decoration:none;text-decoration:none;white-space:nowrap;width:100%}.open-api-client-button[data-v-c7bdd328]:hover{background:var(--scalar-sidebar-item-hover-background,var(--scalar-background-2))}[data-v-103d9d56] .cm-editor,[data-v-776dfdde] .cm-editor{background:none;height:100%;outline:0;padding:0}[data-v-103d9d56] .cm-content,[data-v-776dfdde] .cm-content{font-family:var(--scalar-font-code);font-size:var(--scalar-small);max-height:20px;padding:8px 0}[data-v-103d9d56] .cm-tooltip,[data-v-776dfdde] .cm-tooltip{background:none!important;border:none!important;border-radius:var(--scalar-radius);box-shadow:var(--scalar-shadow-2);filter:brightness(var(--scalar-lifted-brightness));outline:0!important;overflow:hidden!important}[data-v-103d9d56] .cm-tooltip-autocomplete ul li,[data-v-776dfdde] .cm-tooltip-autocomplete ul li{padding:3px 6px!important}[data-v-103d9d56] .cm-completionIcon-type:after,[data-v-776dfdde] .cm-completionIcon-type:after{color:var(--scalar-color-3)!important}[data-v-103d9d56] .cm-tooltip-autocomplete ul li[aria-selected],[data-v-776dfdde] .cm-tooltip-autocomplete ul li[aria-selected]{background:var(--scalar-background-2)!important;color:var(--scalar-color-1)!important}[data-v-103d9d56] .cm-tooltip-autocomplete ul,[data-v-776dfdde] .cm-tooltip-autocomplete ul{padding:6px!important;position:relative}[data-v-103d9d56] .cm-tooltip-autocomplete ul li:hover,[data-v-776dfdde] .cm-tooltip-autocomplete ul li:hover{background:var(--scalar-background-3)!important;border-radius:3px;color:var(--scalar-color-1)!important}[data-v-103d9d56] .cm-activeLine,[data-v-103d9d56] .cm-activeLineGutter,[data-v-776dfdde] .cm-activeLine,[data-v-776dfdde] .cm-activeLineGutter{background-color:#0000}[data-v-103d9d56] .cm-matchingBracket,[data-v-103d9d56] .cm-selectionMatch,[data-v-776dfdde] .cm-matchingBracket,[data-v-776dfdde] .cm-selectionMatch{background:var(--scalar-background-4)!important;border-radius:var(--scalar-radius)}[data-v-103d9d56] .cm-css-color-picker-wrapper,[data-v-776dfdde] .cm-css-color-picker-wrapper{border-radius:3px;display:inline-flex;outline:1px solid var(--scalar-background-3);overflow:hidden}[data-v-103d9d56] .cm-gutters,[data-v-776dfdde] .cm-gutters{background-color:#0000;border-radius:0 0 0 3px;border-right:none;color:var(--scalar-color-3);font-size:var(--scalar-small);line-height:22px}[data-v-103d9d56] .cm-gutters:before,[data-v-776dfdde] .cm-gutters:before{background-color:var(--scalar-background-1);border-radius:var(--scalar-radius) 0 0 var(--scalar-radius);content:"";height:calc(100% - 4px);left:2px;position:absolute;top:2px;width:calc(100% - 2px)}[data-v-103d9d56] .cm-gutterElement,[data-v-776dfdde] .cm-gutterElement{align-items:center;display:flex;font-family:var(--scalar-font-code)!important;justify-content:flex-end;padding-left:0!important;padding-right:6px!important;position:relative}.line-wrapping[data-v-103d9d56]:focus-within .cm-content{display:inline-table;min-height:fit-content;padding:3px 6px;white-space:break-spaces;word-break:break-all}.cm-pill{--tw-bg-base:var(--scalar-color-1);background:var(--tw-bg-base)!important;color:var(--tw-bg-base)}.monthHeader_QFki,.updatesPage_z4Av,header.heroBanner_qw_p,main.docs{background:var(--hero-background)}.address-bar-history-button[data-v-a93fa60f]:focus-within{background:var(--scalar-background-2)}.description[data-v-1b7a32a4] .markdown,.description[data-v-92012388] .markdown{color:var(--scalar-color--1);display:block;font-weight:var(--scalar-semibold);padding:0}.description[data-v-1b7a32a4] .markdown>:first-child,.description[data-v-92012388] .markdown>:first-child,.docItemContainer_Djhp article>:first-child,.docItemContainer_Djhp header+*,.start-ul li[data-v-673f10b6]:first-of-type{margin-top:0}[data-v-cb2a35da] .cm-editor,[data-v-f5565642] .cm-editor{height:100%;outline:0;width:100%}[data-v-cb2a35da] .cm-content,[data-v-f5565642] .cm-content{align-items:center;display:flex;font-size:var(--scalar-small);padding:0}.scroll-timeline-x[data-v-cb2a35da],.scroll-timeline-x[data-v-f5565642]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;-ms-overflow-style:none}.scroll-timeline-x-hidden[data-v-cb2a35da] .cm-scroller,.scroll-timeline-x-hidden[data-v-f5565642] .cm-scroller{scrollbar-width:none;-ms-overflow-style:none;overflow:auto;padding-right:20px}.scroll-timeline-x-hidden[data-v-cb2a35da] .cm-scroller::-webkit-scrollbar,.scroll-timeline-x-hidden[data-v-cb2a35da]::-webkit-scrollbar{display:none;height:0;width:0}.scroll-timeline-x-address[data-v-cb2a35da],.scroll-timeline-x-address[data-v-f5565642]{line-height:27px;scrollbar-width:none}.scroll-timeline-x-address[data-v-cb2a35da]:after,.scroll-timeline-x-address[data-v-f5565642]:after{content:"";cursor:text;height:100%;position:absolute;right:0;width:24px}.scroll-timeline-x-address[data-v-cb2a35da]:empty:before,.scroll-timeline-x-address[data-v-f5565642]:empty:before{color:var(--scalar-color-3);content:"Enter URL or cURL request";pointer-events:none}.fade-left[data-v-cb2a35da],.fade-left[data-v-f5565642],.fade-right[data-v-cb2a35da],.fade-right[data-v-f5565642]{animation-direction:reverse;animation-duration:1ms;animation-name:o;animation-timeline:--scroll-timeline;content:"";height:100%;pointer-events:none;position:sticky;z-index:1}.fade-left[data-v-cb2a35da],.fade-left[data-v-f5565642]{background:linear-gradient(-90deg,var(--scalar-address-bar-bg) 0,var(--scalar-address-bar-bg) 30%,var(--scalar-address-bar-bg) 100%);animation-direction:normal;left:-1px;min-width:6px}.fade-right[data-v-cb2a35da],.fade-right[data-v-f5565642]{background:linear-gradient(90deg,var(--scalar-address-bar-bg) 0,var(--scalar-address-bar-bg) 30%,var(--scalar-address-bar-bg) 100%);min-width:24px;right:-1px}.address-bar-bg-states[data-v-cb2a35da],.address-bar-bg-states[data-v-f5565642]{--scalar-address-bar-bg:var(--scalar-background-1);background:var(--scalar-address-bar-bg)}.address-bar-bg-states[data-v-cb2a35da]:has(.cm-focused){--scalar-address-bar-bg:var(--scalar-background-1);border-color:var(--scalar-border-color);outline:1px solid var(--scalar-color-accent)}.address-bar-bg-states:has(.cm-focused) .fade-left[data-v-cb2a35da],.address-bar-bg-states:has(.cm-focused) .fade-right[data-v-cb2a35da]{--scalar-address-bar-bg:var(--scalar-background-1)}.sidebar-height[data-v-7aa06e5a]{min-height:100%}.resizer[data-v-7aa06e5a]{border-right:2px solid #0000;bottom:0;cursor:col-resize;position:absolute;right:0;top:0;transition:border-right-color .3s;width:5px}.dragging .resizer[data-v-7aa06e5a],.resizer:hover,.resizer[data-v-7aa06e5a]:hover,.scalar-dragging .resizer{border-right-color:var(--scalar-background-3)}.dragging[data-v-7aa06e5a],.scalar-dragging{cursor:col-resize}.dragging[data-v-7aa06e5a]:before{content:"";display:block;height:100%;position:absolute;width:100%}[data-v-04661eb4] .cm-content,[data-v-28c8509c] .cm-content,[data-v-3157c3c7] .cm-content,[data-v-36811e28] .cm-content,[data-v-83bfcc8a] .cm-content,[data-v-c1a50a6e] .cm-content,[data-v-ddfccc08] .cm-content{align-items:center;background-color:#0000;display:flex;font-family:var(--scalar-font);font-size:var(--scalar-small);padding:5px 8px;width:100%}[data-v-c1a50a6e] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-04661eb4] .cm-content .cm-pill:not(:last-of-type),[data-v-28c8509c] .cm-content .cm-pill:not(:last-of-type),[data-v-3157c3c7] .cm-content .cm-pill:not(:last-of-type),[data-v-36811e28] .cm-content .cm-pill:not(:last-of-type),[data-v-83bfcc8a] .cm-content .cm-pill:not(:last-of-type),[data-v-c1a50a6e] .cm-content .cm-pill:not(:last-of-type),[data-v-ddfccc08] .cm-content .cm-pill:not(:last-of-type){margin-right:.5px}[data-v-04661eb4] .cm-content .cm-pill:not(:first-of-type),[data-v-28c8509c] .cm-content .cm-pill:not(:first-of-type),[data-v-3157c3c7] .cm-content .cm-pill:not(:first-of-type),[data-v-36811e28] .cm-content .cm-pill:not(:first-of-type),[data-v-83bfcc8a] .cm-content .cm-pill:not(:first-of-type),[data-v-c1a50a6e] .cm-content .cm-pill:not(:first-of-type),[data-v-ddfccc08] .cm-content .cm-pill:not(:first-of-type){margin-left:.5px}[data-v-3157c3c7] .cm-line,[data-v-c1a50a6e] .cm-line{overflow:hidden;padding:0;text-overflow:ellipsis;word-break:break-word}.required[data-v-3157c3c7]:after,.required[data-v-c1a50a6e]:after{content:"Required"}input[data-v-c1a50a6e]::placeholder{color:var(--scalar-color-3)}.scalar-password-input[data-v-3157c3c7],.scalar-password-input[data-v-c1a50a6e]{text-security:disc;-webkit-text-security:disc;-moz-text-security:disc}@keyframes m{0%{opacity:0;transform:scale(.85) translate(calc(50dvw + 80px),10px)}to{opacity:1;transform:scale(.85) translate(calc(50dvw + 80px))}}@keyframes n{0%{opacity:1;transform:scale(.85) translate(calc(50dvw + 80px))}to{opacity:1;transform:scale(1) translate(0)}}.section-flare{position:fixed;right:-50dvw;top:0}#scalar-client{display:flex;flex-direction:column;height:100dvh;width:100dvw;position:relative}.address-bar-history-button[data-v-c15c6573]:focus-within{background:var(--scalar-background-2)}[data-v-776dfdde] .cm-placeholder{color:var(--scalar-color-3)}.line-wrapping[data-v-776dfdde]:focus-within .cm-content{display:inline-table;min-height:fit-content;padding:3px 6px;white-space:break-spaces;word-break:break-all}.cm-pill{border-radius:30px;color:var(--scalar-color-1)!important;display:inline-block;font-size:var(--scalar-small);padding:0 9px}.dark-mode .cm-pill{background:var(--tw-bg-base)!important}.cm-pill:first-of-type{margin-left:0}.cm-editor .cm-widgetBuffer,.cm-foldGutter .cm-gutterElement:first-of-type{display:none}.cm-foldGutter .cm-gutterElement{font-size:var(--scalar-heading-4);padding:2px!important}.cm-foldGutter .cm-gutterElement:hover .cm-foldMarker{background:var(--scalar-background-2);border-radius:var(--scalar-radius);color:var(--scalar-color-1)}.scroll-timeline-x-hidden[data-v-f5565642] .cm-scroller::-webkit-scrollbar,.scroll-timeline-x-hidden[data-v-f5565642]::-webkit-scrollbar{display:none;height:0;width:0}@keyframes o{0%{opacity:0}1%{opacity:1}}.address-bar-bg-states[data-v-f5565642]:has(.cm-focused){--scalar-address-bar-bg:var(--scalar-background-1);border-color:var(--scalar-border-color);outline-style:solid;outline-width:1px}.address-bar-bg-states:has(.cm-focused) .fade-left[data-v-f5565642],.address-bar-bg-states:has(.cm-focused) .fade-right[data-v-f5565642]{--scalar-address-bar-bg:var(--scalar-background-1)}.fade-request-section-content[data-v-f97cc68c]{background:linear-gradient(to left,var(--scalar-background-1) 64%,#0000)}.filter-hover[data-v-f97cc68c]{height:100%;overflow:hidden;padding-left:24px;padding-right:39px;position:absolute;right:0;transition:width 0s ease-in-out .2s}.filter-hover[data-v-f97cc68c]:has(:focus-visible),.filter-hover[data-v-f97cc68c]:hover{width:100%;z-index:10}.filter-hover[data-v-f97cc68c]:before{background-color:var(--scalar-background-1);content:"";height:fit-content;left:0;opacity:0;pointer-events:none;position:absolute;top:0;transition:.3s ease-in-out;width:100%}.filter-hover-item[data-v-f97cc68c]:not(:last-of-type){transform:translate3d(0,3px,0)}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]{transition:opacity .2s ease-in-out,transform .2s ease-in-out}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:last-of-type{transition-delay:50ms}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(2){transition-delay:.1s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(3){transition-delay:.15s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(4){transition-delay:.2s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(5){transition-delay:.25s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(6){transition-delay:.3s}.filter-hover:hover .filter-hover-item[data-v-f97cc68c]:nth-last-of-type(7){transition-delay:.35s}.filter-hover:has(:focus-visible) .filter-hover-item[data-v-f97cc68c],.filter-hover:hover .filter-hover-item[data-v-f97cc68c]{opacity:1;transform:translateZ(0)}.filter-hover[data-v-f97cc68c]:has(:focus-visible):before,.filter-hover[data-v-f97cc68c]:hover:before{backdrop-filter:blur(10px);opacity:.9}.filter-button[data-v-f97cc68c]{top:50%;transform:translateY(-50%)}.context-bar-group:has(:focus-visible) .context-bar-group-hover\:text-c-1[data-v-f97cc68c],.context-bar-group:hover .context-bar-group-hover\:text-c-1[data-v-f97cc68c]{--tw-text-opacity:1;color:rgb(var(--scalar-color-1)/var(--tw-text-opacity))}.context-bar-group:has(:focus-visible) .context-bar-group-hover\:hidden[data-v-f97cc68c],.context-bar-group:hover .context-bar-group-hover\:hidden[data-v-f97cc68c]{display:none}.schema>span[data-v-4df72868],.schema>span[data-v-f2ab7aa3]{display:flex;white-space:nowrap}[data-v-36811e28] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-04661eb4] .cm-line,[data-v-28c8509c] .cm-line,[data-v-36811e28] .cm-line,[data-v-83bfcc8a] .cm-line,[data-v-ddfccc08] .cm-line{overflow:hidden;padding:0;text-overflow:ellipsis}.filemask[data-v-04661eb4],.filemask[data-v-36811e28]{-webkit-mask-image:linear-gradient(to right,#0000 0,var(--scalar-background-2) 20px);mask-image:linear-gradient(to right,#0000 0,var(--scalar-background-2) 20px)}.modal-actions[data-v-43df1726],.modal-actions[data-v-694018d6]{display:flex;gap:1rem;justify-content:flex-end}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}[data-v-3157c3c7] .cm-content:has(.cm-pill){padding:5px 8px}input[data-v-3157c3c7]::placeholder{color:var(--scalar-color-3)}.request-section-content[data-v-22abab53],.request-section-content[data-v-287f5ecf]{--scalar-border-width:.5px}.request-section-content-filter[data-v-22abab53],.request-section-content-filter[data-v-287f5ecf]{box-shadow:0 -10px 0 10px var(--scalar-background-1)}.request-item:focus-within .request-meta-buttons[data-v-22abab53]{opacity:1}.group-hover-input[data-v-22abab53],.group-hover-input[data-v-242ff9a6],.group-hover-input[data-v-287f5ecf],.group-hover-input[data-v-fced736a]{border-color:#0000;border-width:var(--scalar-border-width)}.group:hover .group-hover-input[data-v-22abab53],.group:hover .group-hover-input[data-v-242ff9a6],.group:hover .group-hover-input[data-v-287f5ecf],.group:hover .group-hover-input[data-v-fced736a]{background:var(--scalar-background-1);border-color:var(--scalar-border-color)}.group-hover-input[data-v-22abab53]:focus,.group-hover-input[data-v-242ff9a6]:focus,.group-hover-input[data-v-287f5ecf]:focus,.group-hover-input[data-v-fced736a]:focus{background:none!important;border-color:var(--scalar-border-color)!important}[data-v-1399120c] .cm-editor,[data-v-9c64cd5e] .cm-editor{background-color:#0000;font-size:var(--scalar-small);outline:0}[data-v-1399120c] .cm-gutters,[data-v-9c64cd5e] .cm-gutters{background-color:var(--scalar-background-1);border-radius:var(--scalar-radius) 0 0 var(--scalar-radius)}.body-raw[data-v-1399120c] .cm-scroller,.body-raw[data-v-9c64cd5e] .cm-scroller{min-width:100%;overflow:auto}.ascii-art-animate .ascii-art-line[data-v-69ebd973]{animation:4s step-end 1s both p,.5s step-end infinite q;border-right:1ch solid #0000}@keyframes p{0%{width:0}to{width:100%}}@keyframes q{0%{border-right-color:initial}50%{border-right-color:#0000}}.scalar-version-number[data-v-34b57d9d],.scalar-version-number[data-v-6d2bdb61]{align-items:center;border-radius:9px 9px 16px 12px;box-shadow:inset 2px 0 0 2px var(--scalar-background-2);display:flex;flex-direction:column;font-family:var(--scalar-font-code);font-size:8px;height:76px;justify-content:center;line-height:11px;margin-left:-36px;margin-top:-113px;position:absolute;text-align:center;text-decoration-color:var(--scalar-color-3);text-transform:none;transform:skew(0,13deg);width:76px}.scalar-version-number a[data-v-34b57d9d],.scalar-version-number a[data-v-6d2bdb61]{background:var(--scalar-background-2);border:.5px solid var(--scalar-border-color);border-radius:3px;font-weight:700;padding:2px 4px;-webkit-text-decoration:none;text-decoration:none}.gitbook-show[data-v-34b57d9d]{display:none}.v-enter-active[data-v-1f35725e],.v-enter-active[data-v-7ec8af01]{transition:opacity .5s}.animate-response-heading .response-heading[data-v-6e4eec82],.animate-response-heading .response-heading[data-v-9c1fd1c7]{animation:.2s ease-in-out forwards x;opacity:1}.animate-response-heading .animate-response-children[data-v-6e4eec82],.animate-response-heading .animate-response-children[data-v-9c1fd1c7]{animation:.2s ease-in-out 50ms forwards y;opacity:0}.request-method[data-v-cbc307e3]{font-family:var(--scalar-font-code);margin-right:6px;text-transform:uppercase}.request-card-footer[data-v-cbc307e3]{display:flex;flex-shrink:0;justify-content:flex-end;padding:6px;position:relative}.request-editor-section[data-v-cbc307e3]{display:flex;flex:1}.request-card-simple[data-v-cbc307e3]{align-items:center;display:flex;font-size:var(--scalar-small);justify-content:space-between;padding:8px 8px 8px 12px}.code-snippet[data-v-cbc307e3]{display:flex;flex-direction:column;width:100%}.resizer[data-v-e2c54c18]{border-right:2px solid #0000;bottom:0;cursor:col-resize;position:absolute;right:0;top:0;transition:border-right-color .3s;width:5px;z-index:100}.scalar-dragging:after{content:"";display:block;inset:0;position:absolute}.scroll-timeline-x[data-v-f4568236]::-webkit-scrollbar{display:none}.splash-screen[data-v-af32615f]{animation:.5s ease-in-out forwards r;opacity:0}.logo-icon[data-v-af32615f]{animation:.6s ease-in-out .2s forwards s,2s ease-in-out .8s infinite t;opacity:0}@keyframes r{0%{opacity:0}to{opacity:.9}}@keyframes s{0%{opacity:0;transform:scale(.9)}to{opacity:.8;transform:scale(1)}}@keyframes t{0%,to{opacity:.8}50%{opacity:.6}}@keyframes u{0%{opacity:0}to{opacity:1}}@keyframes v{0%{opacity:0;transform:translate3d(-50%,10px,0)}to{opacity:1;transform:translate3d(-50%,0,0)}}.empty-sidebar-item-content[data-v-23d35bb5],.empty-sidebar-item-content[data-v-feb6f541],.gitbook-show[data-v-6d2bdb61],.gitbook-show[data-v-c8df97c6]{display:none}.empty-sidebar-item .empty-sidebar-item-content[data-v-23d35bb5],.empty-sidebar-item .empty-sidebar-item-content[data-v-feb6f541],.tocCollapsibleContent_vkbj a{display:block}.empty-sidebar-item:hover .rabbitjump[data-v-23d35bb5],.empty-sidebar-item:hover .rabbitjump[data-v-feb6f541]{animation:.5s step-end infinite A;opacity:1}.empty-sidebar-item:hover .rabbitsit[data-v-23d35bb5],.empty-sidebar-item:hover .rabbitsit[data-v-feb6f541]{animation:.5s step-end infinite B;opacity:0}.nav-item:hover .nav-item-close[data-v-2e741aab],header.heroBanner_qw_p img.starButton_Vbnu:hover{opacity:1}.empty-sidebar-item:hover .rabbit-ascii[data-v-23d35bb5],.empty-sidebar-item:hover .rabbit-ascii[data-v-feb6f541]{animation:8s linear infinite z}.nav-single-tab[data-v-2e741aab]{color:var(--scalar-color-1);height:100%;width:100%}.nav-item[data-v-2e741aab],.nav-single-tab[data-v-2e741aab]{align-items:center;display:flex;justify-content:center;overflow:hidden}.nav-item[data-v-2e741aab]{background:var(--scalar-background-3);border:var(--scalar-border-width) solid var(--scalar-background-2);border-radius:var(--scalar-radius-lg);color:var(--scalar-color-3);cursor:pointer;flex:1;min-width:0;padding:4.5px 1rem;position:relative}.dark-mode .nav-item[data-v-2e741aab]{background:var(--scalar-background-2)}#scalar-client,.dark-mode .nav-item__active[data-v-2e741aab],.nav-item__active .nav-item-close[data-v-2e741aab]:hover{background-color:var(--scalar-background-2)}.dark-mode #scalar-client{background-color:var(--scalar-background-1)}.scalar-collection-auth[data-v-60f72663],.scalar-collection-auth[data-v-cc87292e]{border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:var(--scalar-radius-lg);overflow:hidden}[data-v-ddfccc08] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-28c8509c] .cm-content:has(.cm-pill){padding:5px 8px}[data-v-5997a667] .cm-scroller,[data-v-d486ea71] .cm-scroller{max-width:100%;overflow:auto hidden}.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-font-size:var(--scalar-small);--callout-neutral-primary:var(--scalar-color-3);--callout-neutral-secondary:var(--scalar-background-2);--callout-neutral-font-color:var(--scalar-color-1);--callout-success-primary:var(--scalar-color-green);--callout-success-secondary:var(--scalar-color-green);--callout-success-font-color:var(--scalar-color-1);--callout-danger-primary:var(--scalar-color-red);--callout-danger-secondary:var(--scalar-color-red);--callout-danger-font-color:var(--scalar-color-1);--callout-warning-primary:var(--scalar-color-yellow);--callout-warning-secondary:var(--scalar-color-yellow);--callout-warning-font-color:var(--scalar-color-1);--callout-info-primary:var(--scalar-color-blue);--callout-info-secondary:var(--scalar-color-blue);--callout-info-font-color:var(--scalar-color-1);--callout-line-height:22px}.t-editor__callout[data-v-fa58cbab]{border-radius:var(--scalar-radius);margin-top:var(--scalar-block-spacing);--callout-primary:var(--scalar-border-color);--callout-secondary:var(--scalar-background-2);--callout-svg:var(--callout-primary);background:var(--callout-secondary);border:var(--scalar-border-width) solid var(--callout-primary);padding:10px 14px}.t-editor__callout .callout-content__text[data-v-fa58cbab]{flex:1;font-size:var(--callout-font-size);line-height:var(--callout-line-height)}.t-editor__callout .callout-content__icon[data-v-fa58cbab]{align-items:center;border-radius:var(--scalar-radius);color:var(--callout-svg);display:flex;height:fit-content;justify-content:center;position:relative;width:18px}.t-editor__callout .callout-content__icon[data-v-fa58cbab]:before{content:"​";line-height:var(--callout-line-height)}.t-editor__callout.callout__neutral[data-v-fa58cbab]{--callout-primary:var(--callout-neutral-primary);--callout-secondary:var(--callout-neutral-secondary);--callout-font-color:var(--callout-neutral-font-color);--callout-svg:var(--callout-neutral-font-color)}.t-editor__callout.callout__info[data-v-fa58cbab]{--callout-primary:var(--callout-info-primary);--callout-secondary:var(--callout-info-secondary);--callout-font-color:var(--callout-info-font-color);--callout-svg:var(--callout-info-primary)}.t-editor__callout.callout__warning[data-v-fa58cbab]{--callout-primary:var(--callout-warning-primary);--callout-secondary:var(--callout-warning-secondary);--callout-font-color:var(--callout-warning-font-color);--callout-svg:var(--callout-warning-primary)}.t-editor__callout.callout__success[data-v-fa58cbab]{--callout-primary:var(--callout-success-primary);--callout-secondary:var(--callout-success-secondary);--callout-font-color:var(--callout-success-font-color);--callout-svg:var(--callout-success-primary)}.t-editor__callout.callout__danger[data-v-fa58cbab]{--callout-primary:var(--callout-danger-primary);--callout-secondary:var(--callout-danger-secondary);--callout-font-color:var(--callout-danger-font-color);--callout-svg:var(--callout-danger-primary)}[data-v-04661eb4] .cm-content:has(.cm-pill){padding:5px 8px}.auth-combobox-position[data-v-0bb98074],.auth-combobox-position[data-v-3f1067a4]{margin-left:120px}.scroll-timeline-x[data-v-0bb98074],.scroll-timeline-x[data-v-3f1067a4]{scroll-timeline:--scroll-timeline x;scroll-timeline:--scroll-timeline horizontal;scrollbar-width:none;-ms-overflow-style:none;overflow:auto}.fade-left[data-v-0bb98074],.fade-left[data-v-3f1067a4],.fade-right[data-v-0bb98074],.fade-right[data-v-3f1067a4]{animation-direction:reverse;animation-duration:1ms;animation-name:w;animation-timeline:--scroll-timeline;content:"";height:100%;min-height:24px;pointer-events:none;position:sticky}.fade-left[data-v-0bb98074],.fade-left[data-v-3f1067a4]{background:linear-gradient(-90deg,var(--scalar-background-1) 0,var(--scalar-background-1) 60%,var(--scalar-background-1) 100%);animation-direction:normal;left:-1px;min-width:3px}.fade-right[data-v-0bb98074],.fade-right[data-v-3f1067a4]{background:linear-gradient(90deg,var(--scalar-background-1) 0,var(--scalar-background-1) 60%,var(--scalar-background-1) 100%);margin-left:-20px;min-width:24px;right:-1px;top:0}@keyframes w{0%{opacity:0}15%{opacity:1}}.request-item:focus-within .request-meta-buttons[data-v-287f5ecf]{opacity:1}.response-body-virtual[data-headlessui-state=open],.response-body-virtual[data-headlessui-state=open] .diclosure-panel{display:flex;flex-direction:column;flex-grow:1}.keycap-n[data-v-b1211b87]{background:-webkit-linear-gradient(5deg,#0000 30%,var(--scalar-color-3) 50%);-webkit-text-fill-color:#0000;-webkit-background-clip:text;background-clip:text}.keycap-hotkey[data-v-b1211b87]{line-height:26px;position:absolute;top:32px}@keyframes x{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}@keyframes y{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.ellipsis-position[data-v-01a1ab71]{transform:translate3d(calc(-100% - 4.5px),0,0)}.indent-border-line-offset[data-v-b2f06757]:before{left:var(--v2bb71a6d)}.indent-padding-left[data-v-b2f06757]{padding-left:calc(var(--b961af9e) + 6px)}.sidebar-folderitem[data-v-b2f06757] .ellipsis-position{right:6px;transform:none}.search-button-fade[data-v-23d35bb5]{background:linear-gradient(var(--scalar-background-1) 32px,var(--scalar-background-1) 38px,#0000)}@keyframes z{0%,to{transform:translateZ(0)}25%,75%{transform:translate3d(250px,0,0)}25.01%,75.01%{transform:translate3d(-250px,0,0)}}@keyframes A{0%,to{opacity:1}50%{opacity:0}}@keyframes B{0%,to{opacity:0}50%{opacity:1;transform:translate3d(0,-8px,0)}}.request-text-color-text[data-v-02af05d1],.request-text-color-text[data-v-57ae0d10]{background:linear-gradient(var(--scalar-background-1),var(--scalar-background-3));box-shadow:0 0 0 1px var(--scalar-border-color);color:var(--scalar-color-1)}[data-v-83bfcc8a] .cm-content:has(.cm-pill){padding:5px 8px}.dragover-above[data-v-a89d6a6e]:after,.dragover-below[data-v-a89d6a6e]:after{background:var(--scalar-color-blue);content:"";display:block;height:3px;position:absolute;top:-1.5px;width:100%;border-radius:var(--scalar-radius);pointer-events:none}.dragover-below[data-v-a89d6a6e]:after{bottom:-1.5px;top:auto}.dragover-asChild[data-v-a89d6a6e]:after{background:var(--scalar-color-blue);content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%;border-radius:var(--scalar-radius);pointer-events:none}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}.scalar-app .markdown .markdown-alert{background-color:var(--scalar-background-2)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue);border:var(--scalar-border-width) solid var(--scalar-color-blue)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2);border:var(--scalar-border-width) solid var(--scalar-color-2)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange);border:var(--scalar-border-width) solid var(--scalar-color-orange)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red);border:var(--scalar-border-width) solid var(--scalar-color-red)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green);border:var(--scalar-border-width) solid var(--scalar-color-green)}.scalar-app .border-c-accent\/30{border-color:var(--scalar-color-accent)}.scalar-app .bg-c-3\/5{background-color:var(--scalar-color-3)}.scalar-app .bg-c-accent\/10,.scalar-app .bg-c-accent\/5{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:var(--scalar-background-1)}.scalar-app .to-b-1\.5{--tw-gradient-to:var(--scalar-background-1)}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:var(--scalar-tooltip-color)}@supports (color:color-mix(in lab,red,red)){.scalar-app .bg-b-1\.5{background-color:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.scalar-app .to-b-1\.5{--tw-gradient-to:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}:is(.scalar-app .\*\:border-border-tooltip>*){border-color:color-mix(in srgb,var(--scalar-tooltip-color),var(--scalar-tooltip-background))}}.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000)}}.scalar-app .markdown a{-webkit-text-decoration-color:var(--font-color);text-decoration-color:var(--font-color)}.scalar-app .markdown .markdown-alert{background-color:var(--scalar-background-2)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:var(--scalar-color-blue);border:var(--scalar-border-width) solid var(--scalar-color-blue)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:var(--scalar-color-2);border:var(--scalar-border-width) solid var(--scalar-color-2)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:var(--scalar-color-orange);border:var(--scalar-border-width) solid var(--scalar-color-orange)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:var(--scalar-color-red);border:var(--scalar-border-width) solid var(--scalar-color-red)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:var(--scalar-color-green);border:var(--scalar-border-width) solid var(--scalar-color-green)}@supports (color:color-mix(in lab,red,red)){.scalar-app .markdown a{-webkit-text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000);text-decoration-color:color-mix(in srgb,var(--font-color) 30%,#0000)}.scalar-app .markdown .markdown-alert{background-color:color-mix(in srgb,var(--scalar-background-2),#0000)}.scalar-app .markdown .markdown-alert.markdown-alert-note{background-color:color-mix(in srgb,var(--scalar-color-blue),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-blue),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-tip{background-color:color-mix(in srgb,var(--scalar-color-2),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-2),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-important,.scalar-app .markdown .markdown-alert.markdown-alert-warning{background-color:color-mix(in srgb,var(--scalar-color-orange),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-orange),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-caution{background-color:color-mix(in srgb,var(--scalar-color-red),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-red),#0000 50%)}.scalar-app .markdown .markdown-alert.markdown-alert-success{background-color:color-mix(in srgb,var(--scalar-color-green),#0000 97%);border:var(--scalar-border-width) solid color-mix(in srgb,var(--scalar-color-green),#0000 50%)}}.scalar-app .border-c-accent\/30{border-color:color-mix(in oklab,var(--scalar-color-accent) 30%,#0000)}.scalar-app .bg-c-3\/5{background-color:color-mix(in oklab,var(--scalar-color-3) 5%,#0000)}.scalar-app .bg-c-accent\/5{background-color:color-mix(in oklab,var(--scalar-color-accent) 5%,#0000)}.scalar-app .bg-c-accent\/10{background-color:color-mix(in oklab,var(--scalar-color-accent) 10%,#0000)}}.dark-mode .nav-item[data-v-2e741aab],.dark-mode .nav-item[data-v-507381a3]{background:color-mix(in srgb,var(--scalar-background-2),#0000)}.cm-pill{background:color-mix(in srgb,var(--tw-bg-base),#0000 94%)!important}.fade-left[data-v-cb2a35da],.fade-left[data-v-f5565642]{background:linear-gradient(-90deg,color-mix(in srgb,var(--scalar-address-bar-bg),#0000 100%) 0,color-mix(in srgb,var(--scalar-address-bar-bg),#0000 20%) 30%,var(--scalar-address-bar-bg) 100%)}.fade-right[data-v-cb2a35da],.fade-right[data-v-f5565642]{background:linear-gradient(90deg,color-mix(in srgb,var(--scalar-address-bar-bg),#0000 100%) 0,color-mix(in srgb,var(--scalar-address-bar-bg),#0000 20%) 30%,var(--scalar-address-bar-bg) 100%)}.address-bar-bg-states[data-v-cb2a35da],.address-bar-bg-states[data-v-f5565642]{--scalar-address-bar-bg:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.dark-mode .cm-pill{background:color-mix(in srgb,var(--tw-bg-base),#0000 90%)!important}.group:hover .group-hover-input[data-v-22abab53],.group:hover .group-hover-input[data-v-242ff9a6],.group:hover .group-hover-input[data-v-287f5ecf],.group:hover .group-hover-input[data-v-fced736a]{background:color-mix(in srgb,var(--scalar-background-1),var(--scalar-background-2))}.dark-mode #scalar-client{background-color:color-mix(in srgb,var(--scalar-background-1) 65%,#000)}.dark-mode .t-editor__callout[data-v-fa58cbab],.light-mode .t-editor__callout[data-v-fa58cbab]{--callout-neutral-secondary:color-mix(in srgb,var(--scalar-background-2),#0000 50%);--callout-success-secondary:color-mix(in srgb,var(--scalar-color-green),#0000 97%);--callout-danger-secondary:color-mix(in srgb,var(--scalar-color-red),#0000 97%);--callout-warning-secondary:color-mix(in srgb,var(--scalar-color-yellow),#0000 97%);--callout-info-secondary:color-mix(in srgb,var(--scalar-color-blue),#0000 97%)}.t-editor__callout[data-v-fa58cbab]{border:var(--scalar-border-width) solid color-mix(in srgb,var(--callout-primary),#0000 50%)}.fade-left[data-v-0bb98074],.fade-left[data-v-3f1067a4]{background:linear-gradient(-90deg,color-mix(in srgb,var(--scalar-background-1),#0000 100%) 0,color-mix(in srgb,var(--scalar-background-1),#0000 20%) 60%,var(--scalar-background-1) 100%)}.fade-right[data-v-0bb98074],.fade-right[data-v-3f1067a4]{background:linear-gradient(90deg,color-mix(in srgb,var(--scalar-background-1),#0000 100%) 0,color-mix(in srgb,var(--scalar-background-1),#0000 20%) 60%,var(--scalar-background-1) 100%)}.search-button-fade[data-v-23d35bb5]{background:linear-gradient(var(--scalar-background-1) 32px,color-mix(in srgb,var(--scalar-background-1),#0000) 38px,#0000)}.dragover-above[data-v-a89d6a6e]:after,.dragover-asChild[data-v-a89d6a6e]:after,.dragover-below[data-v-a89d6a6e]:after{background:color-mix(in srgb,var(--scalar-color-blue),#0000 85%)}}.empty-variable-name[data-v-298ba76d]:empty:before{color:var(--scalar-color-3);content:"Untitled"}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}.t-doc__sidebar[data-v-94d6fca6]{z-index:10}.references-layout[data-v-94d6fca6]{flex:1;max-width:100%;min-height:100dvh;min-width:100%;--full-height:100dvh;background:var(--scalar-background-1);display:grid;grid-template-areas:"c c" "d g" "h h";grid-template-columns:auto 1fr;grid-template-rows:var(--scalar-header-height,0) repeat(2,auto)}.references-editor[data-v-94d6fca6]{background:var(--scalar-background-1);display:flex;grid-area:i;min-width:0}.references-rendered[data-v-94d6fca6]{background:var(--scalar-background-1);grid-area:g;min-width:0;position:relative}.references-classic .references-rendered[data-v-94d6fca6],.scalar-api-reference.references-classic[data-v-94d6fca6]{height:auto!important;max-height:none!important}@layer scalar-config{.scalar-api-reference[data-v-94d6fca6]{--refs-header-height:calc(var(--scalar-custom-header-height, 0px) + var(--scalar-header-height, 0px));--refs-viewport-offset:calc(var(--refs-header-height, 0px) + var(--refs-content-offset, 0px));--refs-viewport-height:calc(var(--full-height, 100dvh) - var(--refs-viewport-offset, 0px));--refs-sidebar-width:var(--scalar-sidebar-width,0px);--refs-sidebar-height:calc(var(--full-height, 100dvh) - var(--refs-header-height, 0px));--refs-content-max-width:var(--scalar-content-max-width,1540px)}.scalar-api-reference.references-classic[data-v-94d6fca6]{--refs-content-max-width:var(--scalar-content-max-width,1420px);min-height:100dvh;--refs-sidebar-width:0}.references-sidebar[data-v-94d6fca6]{--refs-sidebar-width:var(--scalar-sidebar-width,288px)}}.references-footer[data-v-94d6fca6]{grid-area:h}.darklight-reference[data-v-94d6fca6]{margin-top:auto;width:100%}.start[data-v-673f10b6]{display:flex;flex-flow:wrap;justify-content:space-between;padding:24px;position:relative;z-index:0}.start-h1[data-v-673f10b6]{font-size:var(--scalar-heading-2);margin-bottom:0;position:relative}.start-h1[data-v-673f10b6],.start-h3[data-v-673f10b6]{color:var(--scalar-color-1);font-weight:var(--scalar-bold);line-height:1.45;margin-top:0;width:100%}.start-h3[data-v-673f10b6]{display:block;font-size:var(--scalar-paragraph);margin-bottom:6px}.start-p[data-v-673f10b6]{color:var(--scalar-color-2);width:100%}.start-p[data-v-673f10b6],.start-ul[data-v-673f10b6]{font-size:var(--scalar-paragraph);line-height:1.5;margin-top:12px}.start-ul[data-v-673f10b6]{display:flex;flex-flow:wrap;gap:24px;list-style:initial;padding-left:0}.start-ul li[data-v-673f10b6]{color:var(--scalar-color-2);list-style:none;margin:0;padding:0;width:calc(50% - 24px)}.start-section[data-v-673f10b6]{display:flex;flex-flow:wrap;margin-bottom:12px;width:100%}.start-h2[data-v-673f10b6]{border:1px solid var(--scalar-border-color);border-top-left-radius:var(--scalar-radius-lg);border-top-right-radius:var(--scalar-radius-lg);color:var(--scalar-color-3);width:100%}.start-h2[data-v-673f10b6],.start-item[data-v-673f10b6]{background:var(--scalar-background-2);font-size:var(--scalar-mini);font-weight:var(--scalar-semibold);padding:9px}.start-item[data-v-673f10b6]{align-items:center;border-bottom:1px solid var(--scalar-border-color);border-right:1px solid var(--scalar-border-color);color:var(--scalar-color-1);cursor:pointer;display:flex;flex:1;text-transform:capitalize;-webkit-user-select:none;user-select:none}.start-section-integrations .start-item[data-v-673f10b6]:first-of-type{border-bottom-left-radius:var(--scalar-radius-lg);border-left:1px solid var(--scalar-border-color)}.start-section-integrations .start-item[data-v-673f10b6]:last-of-type{border-bottom-right-radius:var(--scalar-radius-lg)}.start-section-colors .start-item[data-v-673f10b6]{min-width:33.33%}.start-section-colors .start-item[data-v-673f10b6]:nth-child(3n+2){border-left:1px solid var(--scalar-border-color)}.start-item svg[data-v-673f10b6]{height:14px;margin-right:6px;width:14px}.start-item-active[data-v-673f10b6]{color:var(--scalar-color-1);position:relative;z-index:10}.start-item-active[data-v-673f10b6]:before{border:1px solid var(--scalar-color-1);content:"";inset:-1px;pointer-events:none;position:absolute}.start-cta[data-v-673f10b6]{display:flex;gap:12px;margin-top:24px;width:100%}.start-row[data-v-673f10b6]{margin-top:12px;overflow:hidden;width:100%}.start-hero-copy[data-v-673f10b6]{background:var(--scalar-background-2);border-radius:var(--scalar-radius-lg);padding:12px}.start-p-small[data-v-673f10b6]{color:var(--scalar-color-2);font-size:var(--scalar-mini);font-weight:var(--scalar-semibold);line-height:1.4;margin-bottom:12px}.start-cta[data-v-673f10b6]{margin-bottom:12px;white-space:nowrap;width:fit-content}.start-copy[data-v-673f10b6]{align-items:center;display:flex;flex-direction:column;justify-content:center;padding:76px 48px 48px;text-align:center}.start-logo[data-v-673f10b6]{aspect-ratio:1;border-radius:50%;box-shadow:var(--scalar-shadow-2);color:var(--scalar-color-1);margin-bottom:24px;position:relative;width:72px}.start-logo[data-v-673f10b6]:before{aspect-ratio:1;background-image:linear-gradient(to right,var(--scalar-border-color) 1px,#0000 1px),linear-gradient(to bottom,var(--scalar-border-color) 1px,#0000 1px);background-size:24px 24px;border-radius:50%;box-shadow:inset 0 0 50px var(--scalar-background-1),inset 0 0 50px var(--scalar-background-1);content:"";left:-100%;position:absolute;top:-100%;width:300%;z-index:-1}.start-logo svg[data-v-673f10b6]{background:var(--scalar-background-1);border-radius:50%;height:auto;padding:3px;position:relative;width:100%}.updatesPage_z4Av{min-height:80vh;padding:2rem 1rem 4rem}.pageHeader_g4VR{margin-bottom:2.5rem;text-align:center}.pageHeader_g4VR h1{color:var(--text-color);font-family:Racing Sans One,cursive;font-size:2.5rem;margin-bottom:.25rem}.pageHeader_g4VR p{color:var(--mid-grey);font-family:Raleway,sans-serif;font-size:1.1rem;margin:0}.rssLink_aJml{align-items:center;border:1px solid var(--ifm-color-primary,#54bff7);border-radius:4px;color:var(--ifm-color-primary,#54bff7);display:inline-flex;font-family:Raleway,sans-serif;font-size:.95rem;font-weight:600;gap:.4rem;margin-top:.85rem;padding:.3rem .7rem;transition:background .2s,color .2s}.commitRow_RIZe,.releaseCard_aa3v .entryTitle_RUqs,.tagCard_SluW .entryTitle_RUqs{align-items:center;display:flex;color:var(--text-color)}.releaseCard_aa3v,.tagCard_SluW{border-radius:8px;box-shadow:var(--feature-img-shadow)}.badge_qP1g,.count_Ad5j,.monthHeader_QFki,.sha_JsPS,.tagCard_SluW .entryTitle_RUqs,.tagPill_IKwa{font-family:Roboto Mono,monospace}.rssLink_aJml:hover{background:var(--ifm-color-primary,#54bff7);color:#fff}.timeline_WTQQ{margin:0 auto;max-width:1000px;padding-left:2.5rem;position:relative;width:80%}.timeline_WTQQ:before{background:var(--mid-grey);bottom:0;content:"";left:0;opacity:.4;position:absolute;top:0;width:2px}.monthHeader_QFki{color:var(--mid-grey);font-size:1rem;margin:0;padding:.75rem 0 .5rem;position:sticky;top:60px;z-index:1}.entry_mTt2{margin-bottom:1rem;position:relative}.entry_mTt2:before{border-radius:50%;content:"";position:absolute;z-index:1}.releaseEntry_ZG2V:before{background:var(--accent-pink);height:14px;left:-2.5rem;margin-left:-6px;top:1rem;width:14px}.tagEntry_ORGt:before{background:var(--accent-blue);height:10px;left:-2.5rem;margin-left:-4px;top:1rem;width:10px}.commitEntry_JtMn:before{background:var(--mid-grey);height:6px;left:-2.5rem;margin-left:-2px;top:.9rem;width:6px}.commitRow_RIZe:hover,.releaseCard_aa3v,.tagCard_SluW{background:var(--background)}.releaseCard_aa3v{border-left:4px solid var(--accent-pink);padding:1.25rem 1.5rem}.releaseCard_aa3v .entryTitle_RUqs{flex-wrap:wrap;font-family:Roboto Mono,monospace;font-size:1.3rem;gap:.5rem;margin:0 0 .5rem}.tagCard_SluW{border-left:4px solid var(--accent-blue);padding:1rem 1.25rem}.badgeRelease_agcf,.button.color-btn.btn-pink{background:var(--accent-pink)}.tagCard_SluW .entryTitle_RUqs{font-size:1.1rem;gap:.5rem;margin:0 0 .25rem}.commitLink_QhHv,.meta_GplF{align-items:center;gap:.75rem}.commitRow_RIZe{border-bottom:1px solid #80808026;gap:.75rem;padding:.5rem 0;transition:background .15s}.commitLink_QhHv{display:flex;overflow:hidden}.commitLink_QhHv,.commitLink_QhHv:hover,.tile_CRAN,.tile_CRAN:hover{color:var(--text-color);-webkit-text-decoration:none;text-decoration:none}.fetchErrorCard_M8w3 a,.keepReading_G_Ux,.meta_GplF a,main.docs p.see-repo-note a{-webkit-text-decoration:underline;text-decoration:underline}.badge_qP1g{border-radius:4px;color:#fff;display:inline-block;font-size:.7rem;line-height:1.4;padding:.1rem .5rem;white-space:nowrap}.badgeTag_gC_o{background:var(--accent-blue)}.tagPill_IKwa{border:1px solid var(--mid-grey);border-radius:4px;color:var(--mid-grey);font-size:.8rem;padding:.05rem .4rem}.avatarSmall_luV7,.avatar_Vzby,.contributorAvatar_eGpt img{border-radius:50%;vertical-align:middle}.errorMessage_lztz,.fetchErrorCard_M8w3{box-shadow:var(--feature-img-shadow);padding:1.25rem 1.5rem;text-align:center}.bodyPreview_iKaD{color:var(--mid-grey);display:-webkit-box;font-size:.9rem;-webkit-line-clamp:3;margin:.25rem 0 .5rem;-webkit-box-orient:vertical;line-height:1.5;overflow:hidden}.meta_GplF,.sha_JsPS{color:var(--mid-grey);font-size:.85rem}.meta_GplF{display:flex;flex-wrap:wrap}.meta_GplF a{color:var(--mid-grey)}.linksList_CKaa img,.meta_GplF a .providerIcon_NdOm{margin-right:.25rem;vertical-align:middle;width:1.35rem}.avatar_Vzby{height:24px;width:24px}.authorInfo_pi2M{align-items:center;display:flex;gap:.35rem}.sha_JsPS{flex-shrink:0}.commitMsg_XeKn{font-size:.95rem;max-width:500px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.authorLink_YWtN{color:var(--text-color);opacity:.75;transition:.2s}.authorLink_YWtN:hover .avatarSmall_luV7{transform:scale(1.08)}.avatarSmall_luV7{height:18px;margin-right:.2rem;transition:.2s;width:18px}.errorMessage_lztz,.fetchErrorCard_M8w3,.skeleton_NVtD{background:var(--background);border-radius:8px}.commitMeta_teNK{align-items:center;color:var(--mid-grey);display:flex;flex-shrink:0;font-size:.8rem;gap:.3rem;margin-left:auto;white-space:nowrap}.contributors_QyJV{align-items:center;display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.5rem}.contributorAvatar_eGpt{display:block;flex-shrink:0;height:26px;width:26px}.contributorAvatar_eGpt img{height:26px;transition:transform .2s;width:26px}.contributorAvatar_eGpt:hover img{transform:scale(1.15)}.skeleton_NVtD{animation:1.5s ease-in-out infinite C;height:80px;margin-bottom:1rem}.authorSocialIcon_XYv3,.authorSocialLink_owbf,.authorSocials_rSDt{height:var(--docusaurus-blog-social-icon-size)}@keyframes C{0%,to{opacity:.4}50%{opacity:1}}.loadMore_YatW{display:flex;justify-content:center;margin:2rem auto}.fetchErrorCard_M8w3{color:var(--mid-grey);font-size:.95rem;margin:1.5rem auto;max-width:1000px;width:80%}.fetchErrorCard_M8w3 a{color:var(--accent-blue)}.errorMessage_lztz{color:var(--mid-grey);font-size:1.1rem;margin:0 auto;width:fit-content}.authorSocialIcon_XYv3,.authorSocialLink_owbf{width:var(--docusaurus-blog-social-icon-size)}.errorMessage_lztz p{margin:.25rem 0}.sidebar_re4s{overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 2rem)}.sidebarItemTitle_pO2u{font-size:var(--ifm-h3-font-size);font-weight:var(--ifm-font-weight-bold)}.container_mt6G,.sidebarItemList_Yudw{font-size:.9rem}.sidebarItem__DBe{margin-top:.7rem}.sidebarItemLink_mo7H{color:var(--ifm-font-color-base);display:block}.sidebarItemLinkActive_I1ZP{color:var(--ifm-color-primary)!important}.yearGroupHeading_rMGB{margin-bottom:.4rem;margin-top:1.6rem}.yearGroupHeading_QT03{margin:1rem .75rem .5rem}[data-theme=dark] .blueskySvg_AzZw,[data-theme=dark] .githubSvg_Uu4N,[data-theme=dark] .instagramSvg_YC40,[data-theme=dark] .linkedinSvg_FCgI,[data-theme=dark] .threadsSvg_PTXY,[data-theme=dark] .xSvg_y3PF{fill:var(--light)}[data-theme=light] .blueskySvg_AzZw,[data-theme=light] .githubSvg_Uu4N,[data-theme=light] .instagramSvg_YC40,[data-theme=light] .linkedinSvg_FCgI,[data-theme=light] .threadsSvg_PTXY,[data-theme=light] .xSvg_y3PF{fill:var(--dark)}.authorSocials_rSDt{align-items:center;display:flex;flex-wrap:wrap;line-clamp:1;-webkit-line-clamp:1;overflow:hidden;-webkit-box-orient:vertical}.authorSocialLink_owbf,.authorSocials_rSDt{line-height:0}.authorSocialLink_owbf{margin-right:.4rem}.authorImage_XqGP{--ifm-avatar-photo-size:3.6rem}.author-as-h1_n9oJ .authorImage_XqGP{--ifm-avatar-photo-size:7rem}.author-as-h2_gXvM .authorImage_XqGP{--ifm-avatar-photo-size:5.4rem}.authorDetails_lV9A{align-items:flex-start;display:flex;flex-direction:column;justify-content:space-around}.authorName_yefp{display:flex;flex-direction:row;font-size:1.1rem;line-height:1.1rem}.author-as-h1_n9oJ .authorName_yefp{display:inline;font-size:2.4rem;line-height:2.4rem}.author-as-h2_gXvM .authorName_yefp{display:inline;font-size:1.4rem;line-height:1.4rem}.authorTitle_nd0D{display:-webkit-box;font-size:.8rem;line-clamp:1;-webkit-line-clamp:1;line-height:1rem;overflow:hidden;-webkit-box-orient:vertical}.author-as-h1_n9oJ .authorTitle_nd0D{font-size:1.2rem;line-height:1.6rem}.author-as-h2_gXvM .authorTitle_nd0D{font-size:1rem;line-height:1.3rem}.authorBlogPostCount_iiJ5{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.8rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.authorCol_Hf19{max-width:inherit!important}.imageOnlyAuthorRow_pa_O{display:flex;flex-flow:row wrap}.imageOnlyAuthorCol_G86a{margin-left:.3rem;margin-right:.3rem}html[data-theme=light]{--heading-shadow:-2px 3px 2px #a2a1a1;--sub-heading-shadow:-1px 1px 1px #a2a1a1}header.heroBanner_qw_p{min-height:calc(100vh - 4rem);overflow:hidden;padding:4rem 1rem 2rem;position:relative;text-align:center}header.heroBanner_qw_p h1.heroTitle_CteR{font-family:Racing Sans One,mono;font-size:8rem;text-shadow:var(--heading-shadow)}header.heroBanner_qw_p h3.heroSubTitle_p8ER{text-shadow:var(--sub-heading-shadow)}header.heroBanner_qw_p img.starButton_Vbnu{opacity:.85;position:absolute;right:1rem;top:1rem;width:12rem}.buttons_LJn2{align-items:center;display:flex;justify-content:center;margin:1.5rem}.dashyDescription_iLL2{font-size:1.2rem;margin:1rem auto;max-width:680px;text-align:left}.dashyDescription_iLL2>p,.dashyDescription_iLL2>span{margin-left:auto;margin-right:auto;max-width:680px}.keepReading_G_Ux{cursor:pointer;font-size:1.2rem;margin-left:.5rem}.scrollDown_cjGa{bottom:0;cursor:pointer;display:flex;left:42%;margin:2rem auto;position:absolute;width:fit-content}.scrollDownText_HIIX{color:var(--text-color);font-size:1.2rem;font-weight:700;margin:0 1rem}.scrollDownIcon_UZ2V{width:2rem}.scrollDownIcon_UZ2V path{fill:var(--text-color)}.sponsorContainer_zSLg{display:flex;flex-direction:column;gap:1rem;margin:1.5rem auto;max-width:680px}.sponsor_JDQ1{background:var(--background);color:var(--text-color);display:flex;flex-direction:column-reverse;gap:.5rem;justify-content:left;padding:.5rem;width:100%}.sponsor_JDQ1 span{font-size:.9rem;text-align:center}.sponsor_JDQ1 span a{color:var(--text-color);font-size:.9rem}.button{border:none;border-radius:4px;color:var(--black);cursor:pointer;display:inline-block;display:flex;font-size:1.3rem;font-weight:700;margin:.5rem;min-width:12rem;padding:.75rem 1rem;position:relative;transition:none}.button svg{margin-right:.5rem;width:1.8rem}.button.btn-small{font-size:1rem;max-width:12rem;min-width:auto;padding:.5rem .75rem;width:100%}.button.btn-small svg{margin-right:.35rem;width:1.2rem}.button.color-btn:hover{color:rgba(0,0,0,.702);top:2px}.button.color-btn:active{box-shadow:none!important;color:rgba(0,0,0,.502);top:4px}.button.color-btn.btn-pink{box-shadow:0 4px var(--accent-pink-dark)}.button.color-btn.btn-pink:hover{box-shadow:0 2px var(--accent-pink-dark)}.button.color-btn.btn-purple{background:var(--accent-purple);box-shadow:0 4px var(--accent-purple-dark)}.button.color-btn.btn-purple:hover{box-shadow:0 2px var(--accent-purple-dark)}.button.color-btn.btn-blue{background:var(--accent-blue);box-shadow:0 4px var(--accent-blue-dark)}.button.color-btn.btn-blue:hover{box-shadow:0 2px var(--accent-blue-dark)}.button.color-btn.btn-green{background:var(--accent-green);box-shadow:0 4px var(--accent-green-dark)}.button.color-btn.btn-green:hover{box-shadow:0 2px var(--accent-green-dark)}.button.color-btn.btn-yellow{background:var(--accent-yellow);box-shadow:0 4px var(--accent-yellow-dark)}.button.color-btn.btn-yellow:hover{box-shadow:0 2px var(--accent-yellow-dark)}.enjoyingWrap_FXPn .enjoyingWrapPart_oGX0,.home-page-features-wrapper .feature .feature-half.assets,.statItem_at6_,.tile_CRAN:hover,main.docs{box-shadow:var(--feature-img-shadow)}main.docs{margin:1rem auto;max-width:1250px;padding:1rem}main.docs h1.docs-title{font-size:5rem;margin:0;text-align:center}main.docs p.see-repo-note{background:rgba(255,255,170,.839);border-radius:3px;display:none;padding:.25rem 1rem;width:fit-content}main.docs p.see-repo-note a,main.docs p.see-repo-note b,main.docs p.see-repo-note span{color:#000;font-size:1rem;font-style:italic}main.docs p.see-repo-note a{color:#000;font-size:1rem;font-weight:700}main.docs div.row1{display:flex;flex-direction:row}main.docs div.docs-links-section{display:flex;flex-wrap:wrap}main.docs div.docs-contents{color:var(--text-color);display:flex;flex-direction:column;justify-content:space-around}main.docs div.docs-contents h3.section-title{margin:1rem .5rem .5rem}main.docs div.docs-contents a{display:flex;flex-direction:column;margin:.5rem .75rem;min-height:5rem;padding:.25rem .5rem;width:calc(33% - 1.5rem)}main.docs div.docs-contents a p.name{font-size:1.4rem;margin:0;text-align:left}main.docs div.docs-contents a span.description{font-size:1rem;text-align:left;white-space:pre-wrap}.color-pink{--feature-color:var(--accent-pink)}.color-blue{--feature-color:var(--accent-blue)}.color-green{--feature-color:var(--accent-green)}.color-yellow{--feature-color:var(--accent-yellow)}.color-white{--feature-color:#fff}html[data-theme=light] .color-pink{--feature-color:var(--accent-pink-dark)}html[data-theme=light] .color-blue{--feature-color:var(--accent-purple-dark)}html[data-theme=light] .color-green{--feature-color:var(--accent-green-dark)}html[data-theme=light] .color-yellow{--feature-color:var(--accent-yellow-dark)}html[data-theme=light] .color-white{--feature-color:#000}.home-page-features-wrapper{display:flex;flex-direction:column;font-size:1.2rem}.home-page-features-wrapper .feature{align-items:center;display:flex;justify-content:center;margin:0;padding:.5rem}.home-page-features-wrapper .feature.align-left{background:var(--background);box-shadow:0 1px 4px #000;flex-direction:row}.home-page-features-wrapper .feature.align-right{background:var(--hero-background);box-shadow:0 -1px 4px rgba(0,0,0,.702);flex-direction:row-reverse}.home-page-features-wrapper .feature .feature-half{margin:1rem;padding:.25rem;width:40%}.home-page-features-wrapper .feature .feature-half.assets{align-items:center;background:var(--feature-color);border-radius:8px;display:flex;height:fit-content;justify-content:center;margin:.5rem}.home-page-features-wrapper .feature .feature-half.assets img.demo,.home-page-features-wrapper .feature .feature-half.assets span.not-demo{background:var(--kinda-transparent);border-radius:8px;box-shadow:var(--feature-img-shadow);height:100%;max-height:36rem;min-height:12rem;object-fit:contain;width:100%}.home-page-features-wrapper .feature .feature-title{align-items:flex-end;color:var(--feature-color);display:flex;margin:1rem 0}.home-page-features-wrapper .feature .feature-title svg{margin-right:.5rem;width:2rem}.home-page-features-wrapper .feature div.read-the-docs a.button-link-wrapper{margin:0 1rem 1rem}.home-page-features-wrapper .feature div.read-the-docs small{margin:0 .5rem;opacity:.75}.authorsSection_t3c7{background:var(--hero-background);padding:3rem 1rem}.inner_rVjr{margin:.5rem auto;width:80%}.heading_j3J0{color:var(--text-color);font-size:2rem}.enjoyingWrap_FXPn{display:flex;gap:2rem;margin-bottom:2rem}.enjoyingWrap_FXPn .enjoyingWrapPart_oGX0{background:var(--background);border-radius:8px;padding:.5rem 1rem;width:50%}.enjoyingWrap_FXPn .enjoyingWrapPart_oGX0 p{margin:0 0 .5rem}.enjoyingWrap_FXPn .enjoyingWrapPart_oGX0 .authorInnerWrap_y4gi{align-items:center;display:flex;gap:.75rem}.subHeading_D3Yw{color:var(--text-color);font-size:1.2rem;margin:.25rem 0}.builtBy_GoGF,.name_mmMI{color:var(--mid-grey);font-size:.9rem}.grid_qTnk{display:grid;gap:.75rem;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));list-style:none;margin:0 auto 2rem;max-width:1800px;padding:0}.tile_CRAN{align-items:center;background:var(--background);border-radius:8px;display:flex;flex-direction:column;gap:.4rem;padding:.6rem;position:relative;transition:transform .2s,box-shadow .2s}.ctaSection_oQB2,.statItem_at6_{align-items:center;display:flex}.tile_CRAN:hover{transform:translateY(-2px)}.avatar__HA9{border-radius:50%;height:48px;width:48px}.name_mmMI{max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.star_JoTu{position:absolute;right:.5rem;top:.5rem}.fallback_O_bA{margin:0 auto;max-width:100%}.fallback_O_bA img{height:auto;max-width:100%}.ctaSection_oQB2{flex-direction:row;gap:.5rem;justify-content:center;margin-top:.25rem}.builtBy_GoGF{margin-bottom:1rem}.statsSection_Rc3S{background:var(--hero-background);padding:.25rem 1rem 3rem}.inner_YtnN{display:flex;flex-wrap:wrap;gap:1.5rem;justify-content:space-around;margin:0 auto;max-width:1000px;width:80%}.statItem_at6_{background:var(--background);border-radius:8px;flex:1 1 180px;flex-direction:column;max-width:240px;padding:1.5rem 2rem;text-align:center}.icon_vhSc{height:3rem;margin-bottom:.25rem;width:3rem}.count_Ad5j,.icon_vhSc{color:var(--stat-color)}.count_Ad5j{font-size:2.8rem;font-weight:400;line-height:1.1}.label_wElN{color:var(--mid-grey);font-size:1rem;margin-top:.25rem}.buttonGroup_M5ko button,.codeBlockContainer_Ckt0{background:var(--prism-background-color);color:var(--prism-color)}iframe#dashy-survey{border:none;height:92vh;width:100%}.codeBlockContainer_Ckt0{border-radius:var(--ifm-code-border-radius);box-shadow:var(--ifm-global-shadow-lw);margin-bottom:var(--ifm-leading)}.codeBlock_bY9V{--ifm-pre-background:var(--prism-background-color);margin:0;padding:0}.codeBlockLines_e6Vv{float:left;font:inherit;min-width:100%;padding:var(--ifm-pre-padding)}.codeBlockLinesWithNumbering_o6Pm{display:table;padding:var(--ifm-pre-padding) 0}:where(:root){--docusaurus-highlighted-code-line-bg:#484d5b}:where([data-theme=dark]){--docusaurus-highlighted-code-line-bg:#646464}.theme-code-block-highlighted-line{background-color:var(--docusaurus-highlighted-code-line-bg);display:block;margin:0 calc(var(--ifm-pre-padding)*-1);padding:0 var(--ifm-pre-padding)}.codeLine_lJS_{counter-increment:line-count;display:table-row}.codeLineNumber_Tfdd{background:var(--ifm-pre-background);display:table-cell;left:0;overflow-wrap:normal;padding:0 var(--ifm-pre-padding);position:sticky;text-align:right;width:1%}.codeLineNumber_Tfdd:before{content:counter(line-count);opacity:.4}.codeLineContent_feaV{padding-right:var(--ifm-pre-padding)}.theme-code-block:hover .copyButtonCopied_Vdqa{opacity:1!important}.copyButtonIcons_IEyt{height:1.125rem;position:relative;width:1.125rem}.copyButtonIcon_TrPX,.copyButtonSuccessIcon_cVMy{fill:currentColor;height:inherit;left:0;opacity:inherit;position:absolute;top:0;transition:all var(--ifm-transition-fast) ease;width:inherit}.copyButtonSuccessIcon_cVMy{color:#00d600;left:50%;opacity:0;top:50%;transform:translate(-50%,-50%) scale(.33)}.copyButtonCopied_Vdqa .copyButtonIcon_TrPX{opacity:0;transform:scale(.33)}.copyButtonCopied_Vdqa .copyButtonSuccessIcon_cVMy{opacity:1;transform:translate(-50%,-50%) scale(1);transition-delay:75ms}.wordWrapButtonIcon_b1P5{height:1.2rem;width:1.2rem}.buttonGroup_M5ko{column-gap:.2rem;display:flex;position:absolute;right:calc(var(--ifm-pre-padding)/2);top:calc(var(--ifm-pre-padding)/2)}.buttonGroup_M5ko button{align-items:center;border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-global-radius);display:flex;line-height:0;opacity:0;padding:.4rem;transition:opacity var(--ifm-transition-fast) ease-in-out}.buttonGroup_M5ko button:focus-visible,.buttonGroup_M5ko button:hover{opacity:1!important}.codeBlockContent_QJqH{border-radius:inherit;direction:ltr;position:relative}.codeBlockTitle_OeMC{border-bottom:1px solid var(--ifm-color-emphasis-300);border-top-left-radius:inherit;border-top-right-radius:inherit;font-size:var(--ifm-code-font-size);font-weight:500;padding:.75rem var(--ifm-pre-padding)}.tag_zVej{border:1px solid var(--docusaurus-tag-list-border);transition:border var(--ifm-transition-fast)}.tag_zVej:hover{--docusaurus-tag-list-border:var(--ifm-link-color);-webkit-text-decoration:none;text-decoration:none}.tagRegular_sFm0{border-radius:var(--ifm-global-radius);font-size:90%;padding:.2rem .5rem .3rem}.tagWithCount_h2kH{align-items:center;border-left:0;display:flex;padding:0 .5rem 0 1rem;position:relative}.tagWithCount_h2kH:after,.tagWithCount_h2kH:before{border:1px solid var(--docusaurus-tag-list-border);content:"";position:absolute;top:50%;transition:inherit}.tagWithCount_h2kH:before{border-bottom:0;border-right:0;height:1.18rem;right:100%;transform:translate(50%,-50%) rotate(-45deg);width:1.18rem}.tagWithCount_h2kH:after{border-radius:50%;height:.5rem;left:0;transform:translateY(-50%);width:.5rem}.tagWithCount_h2kH span{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.7rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.details_lb9f{--docusaurus-details-summary-arrow-size:0.38rem;--docusaurus-details-transition:transform 200ms ease;--docusaurus-details-decoration-color:grey}.details_lb9f>summary{cursor:pointer;list-style:none;padding-left:1rem;position:relative}.details_lb9f>summary::-webkit-details-marker{display:none}.details_lb9f>summary:before{border-color:#0000 #0000 #0000 var(--docusaurus-details-decoration-color);border-style:solid;border-width:var(--docusaurus-details-summary-arrow-size);content:"";left:0;position:absolute;top:.45rem;transform:rotate(0);transform-origin:calc(var(--docusaurus-details-summary-arrow-size)/2) 50%;transition:var(--docusaurus-details-transition)}.collapsibleContent_i85q{border-top:1px solid var(--docusaurus-details-decoration-color);margin-top:1rem;padding-top:1rem}.tag_QGVx{display:inline-block;margin:0 .4rem .5rem 0}.iconEdit_Z9Sw{margin-right:.3em;vertical-align:sub}.details_b_Ee{--docusaurus-details-decoration-color:var(--ifm-alert-border-color);--docusaurus-details-transition:transform var(--ifm-transition-fast) ease;border:1px solid var(--ifm-alert-border-color);margin:0 0 var(--ifm-spacing-vertical)}.lastUpdated_JAkA{font-size:smaller;font-style:italic;margin-top:.2rem}.tocCollapsibleButton_TO0P{align-items:center;display:flex;font-size:inherit;justify-content:space-between;padding:.4rem .8rem;width:100%}.tocCollapsibleButton_TO0P:after{background:var(--ifm-menu-link-sublist-icon) 50% 50%/2rem 2rem no-repeat;content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast);width:1.25rem}.tocCollapsibleButtonExpanded_MG3E:after,.tocCollapsibleExpanded_sAul{transform:none}.tocCollapsible_ETCw{background-color:var(--ifm-menu-color-background-active);border-radius:var(--ifm-global-radius);margin:1rem 0}.tocCollapsibleContent_vkbj>ul{border-left:none;border-top:1px solid var(--ifm-color-emphasis-300);font-size:15px;padding:.2rem 0}.tocCollapsibleContent_vkbj ul li{margin:.4rem .8rem}.containsTaskList_mC6p{list-style:none}.admonition_xJq3{margin-bottom:1em}.admonitionHeading_Gvgb{font:var(--ifm-heading-font-weight) var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);text-transform:uppercase}.admonitionHeading_Gvgb:not(:last-child){margin-bottom:.3rem}.admonitionHeading_Gvgb code{text-transform:none}.admonitionIcon_Rf37{display:inline-block;margin-right:.4em;vertical-align:middle}.admonitionIcon_Rf37 svg{display:inline-block;fill:var(--ifm-alert-foreground-color);height:1.6em;width:1.6em}.tableOfContents_bqdL{overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 1rem)}.breadcrumbHomeIcon_YNFT{height:1.1rem;position:relative;top:1px;vertical-align:top;width:1.1rem}.breadcrumbsContainer_Z_bl{--ifm-breadcrumb-size-multiplier:0.8;margin-bottom:.8rem}@media (min-width:400px){.scalar-app .container{max-width:400px}}@media (min-width:600px){.introduction-card-row[data-v-5764c94a]{flex-flow:row wrap}.introduction-card-row[data-v-5764c94a]>*{min-width:min-content}.scalar-app .container{max-width:600px}.scalar-app .sm\:not-sr-only{clip-path:none;height:auto;margin:0;overflow:visible;padding:0;position:static;white-space:normal;width:auto}.scalar-app .sm\:order-none{order:0}.scalar-app .sm\:mr-1\.5{margin-right:6px}.scalar-app .sm\:mb-1\.5{margin-bottom:6px}.scalar-app .sm\:ml-1\.5{margin-left:6px}.scalar-app .sm\:flex{display:flex}.scalar-app .sm\:hidden{display:none}.scalar-app .sm\:max-w-max{max-width:max-content}.scalar-app .sm\:min-w-max{min-width:max-content}.scalar-app .sm\:flex-col{flex-direction:column}.scalar-app .sm\:flex-row{flex-direction:row}.scalar-app .sm\:justify-between{justify-content:space-between}.scalar-app .sm\:gap-px{gap:1px}.scalar-app .sm\:rounded{border-radius:var(--scalar-radius)}.scalar-app .sm\:rounded-lg{border-radius:var(--scalar-radius-lg)}.scalar-app .sm\:px-2{padding-inline:8px}.scalar-app .sm\:px-3{padding-inline:12px}.scalar-app .sm\:py-1\.5{padding-block:6px}:is(.scalar-app .sm\:\*\:rounded-lg>*){border-radius:var(--scalar-radius-lg)}}@media (min-width:800px){.scalar-app .md\:w-\[calc\(100vw-16px\)\]{width:calc(100vw - 16px)}.full-size-styles[data-v-c36b47da]{width:50dvw!important}.scalar-app .container{max-width:800px}.scalar-app .md\:right-10{right:40px}.scalar-app .md\:bottom-10{bottom:40px}.scalar-app .md\:mx-auto{margin-inline:auto}.scalar-app .md\:-ml-1\.25{margin-left:-5px}.scalar-app .md\:ml-1\.5{margin-left:6px}.scalar-app .md\:block{display:block}.scalar-app .md\:flex{display:flex}.scalar-app .md\:grid{display:grid}.scalar-app .md\:w-full{width:100%}.scalar-app .md\:max-w-180,.scalar-app .md\:max-w-\[720px\]{max-width:720px}.scalar-app .md\:min-w-fit{min-width:fit-content}.scalar-app .md\:flex-none{flex:none}.scalar-app .md\:translate-x-0{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.scalar-app .md\:translate-y-0{--tw-translate-y:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.scalar-app .md\:grid-cols-\[1fr_720px_1fr\]{grid-template-columns:1fr 720px 1fr}.scalar-app .md\:flex-row{flex-direction:row}.scalar-app .md\:border-r{border-right-style:var(--tw-border-style);border-right-width:var(--scalar-border-width)}.scalar-app .md\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.scalar-app .md\:p-1\.5{padding:6px}.scalar-app .md\:px-0{padding-inline:0}.scalar-app .md\:px-1\.5{padding-inline:6px}.scalar-app .md\:px-2{padding-inline:8px}.scalar-app .md\:px-2\.5{padding-inline:10px}.scalar-app .md\:px-4{padding-inline:16px}.scalar-app .md\:px-\[18px\]{padding-inline:18px}.scalar-app .md\:py-2\.5{padding-block:10px}.scalar-app .md\:pb-2\.5{padding-bottom:10px}.scalar-app .md\:pb-\[37px\]{padding-bottom:37px}.scalar-app .md\:pl-0{padding-left:0}:is(.scalar-app .md\:\*\:border-t-0>*){border-top-style:var(--tw-border-style);border-top-width:0}.sidebar-mask[data-v-7aa06e5a]{-webkit-mask-image:linear-gradient(0,#0000 0,#0000 0,var(--scalar-background-2) 30px);mask-image:linear-gradient(0,#0000 0,#0000 0,var(--scalar-background-2) 30px)}.has-import-url,.has-no-import-url{contain:paint;max-width:100dvw;overflow-x:hidden}.has-no-import-url .scalar-client>main{animation:.3s ease-in-out forwards n;background:var(--scalar-background-1);opacity:1}.has-import-url .scalar-client>main{animation:.3s ease-in-out forwards m;border:var(--scalar-border-width) solid var(--scalar-border-color);border-radius:12px;opacity:0;overflow:hidden;transform:scale(.85) translate(calc(50dvw + 80px));z-index:10000}.has-import-url .scalar-client .sidenav{display:none}.has-import-url .scalar-app,.has-no-import-url .scalar-app{background:var(--scalar-background-1)!important}}@media not all and (min-width:800px){.scalar-app .max-md\:absolute\!{position:absolute!important}.scalar-app .max-md\:w-full\!{width:100%!important}}@media (min-width:997px){.collapseSidebarButton_PEFL,.expandButton_TmdG{background-color:var(--docusaurus-collapse-button-bg)}:root{--docusaurus-announcement-bar-height:30px}.announcementBarClose_gvF7,.announcementBarPlaceholder_vyr4{flex-basis:50px}.collapseSidebarButton_PEFL{border:1px solid var(--ifm-toc-border-color);border-radius:0;bottom:0;display:block!important;height:40px;position:sticky}.collapseSidebarButtonIcon_kv0_{margin-top:4px;transform:rotate(180deg)}.expandButtonIcon_i1dp,[dir=rtl] .collapseSidebarButtonIcon_kv0_{transform:rotate(0)}.collapseSidebarButton_PEFL:focus,.collapseSidebarButton_PEFL:hover,.expandButton_TmdG:focus,.expandButton_TmdG:hover{background-color:var(--docusaurus-collapse-button-bg-hover)}.menuHtmlItem_M9Kj{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu_SIkG{flex-grow:1;padding:.5rem}@supports (scrollbar-gutter:stable){.menu_SIkG{padding:.5rem 0 .5rem .5rem;scrollbar-gutter:stable}}.menuWithAnnouncementBar_GW3s{margin-bottom:var(--docusaurus-announcement-bar-height)}.sidebar_njMd{display:flex;flex-direction:column;height:100%;padding-top:var(--ifm-navbar-height);width:var(--doc-sidebar-width)}.sidebarWithHideableNavbar_wUlq{padding-top:0}.sidebarHidden_VK0M{opacity:0;visibility:hidden}.sidebarLogo_isFc{align-items:center;color:inherit!important;display:flex!important;margin:0 var(--ifm-navbar-padding-horizontal);max-height:var(--ifm-navbar-height);min-height:var(--ifm-navbar-height);-webkit-text-decoration:none!important;text-decoration:none!important}.sidebarLogo_isFc img{height:2rem;margin-right:.5rem}.expandButton_TmdG{align-items:center;display:flex;height:100%;justify-content:center;position:absolute;right:0;top:0;transition:background-color var(--ifm-transition-fast) ease;width:100%}[dir=rtl] .expandButtonIcon_i1dp{transform:rotate(180deg)}.docSidebarContainer_YfHR{border-right:1px solid var(--ifm-toc-border-color);clip-path:inset(0);display:block;margin-top:calc(var(--ifm-navbar-height)*-1);transition:width var(--ifm-transition-fast) ease;width:var(--doc-sidebar-width);will-change:width}.docSidebarContainerHidden_DPk8{cursor:pointer;width:var(--doc-sidebar-hidden-width)}.sidebarViewport_aRkj{height:100%;max-height:100vh;position:sticky;top:0}.docMainContainer_TBSr{flex-grow:1;max-width:calc(100% - var(--doc-sidebar-width))}.docMainContainerEnhanced_lQrH{max-width:calc(100% - var(--doc-sidebar-hidden-width))}.docItemWrapperEnhanced_JWYK{max-width:calc(var(--ifm-container-width) + var(--doc-sidebar-width))!important}.navbarSearchContainer_Bca1{padding:0 var(--ifm-navbar-item-padding-horizontal)}.lastUpdated_JAkA{text-align:right}.tocMobile_ITEo{display:none}.docItemCol_VOVn{max-width:75%!important}}@media (min-width:997px)and (max-width:1200px){nav.navbar .navbar__item.navbar__link{font-size:1rem}nav.navbar .navbar__item.navbar__link svg{display:none}}@media (min-width:1000px){.endpoint-content[data-v-55addca4]{grid-auto-flow:column}.scalar-app .lg\:w-\[calc\(100vw-32px\)\]{width:calc(100vw - 32px)}.scalar-app .lg\:w-full{width:100%}.scalar-app .container{max-width:1000px}.scalar-app .lg\:order-0,.scalar-app .lg\:order-none{order:0}.scalar-app .lg\:-mr-1{margin-right:-4px}.scalar-app .lg\:mb-0{margin-bottom:0}.scalar-app .lg\:flex{display:flex}.scalar-app .lg\:min-h-header{min-height:48px}.scalar-app .lg\:w-auto{width:auto}.scalar-app .lg\:max-w-\[580px\]{max-width:580px}.scalar-app .lg\:min-w-\[580px\]{min-width:580px}.scalar-app .lg\:flex-1{flex:1}.scalar-app .lg\:p-0{padding:0}.scalar-app .lg\:p-1{padding:4px}.scalar-app .lg\:px-1{padding-inline:4px}.scalar-app .lg\:px-2\.5{padding-inline:10px}.scalar-app .lg\:pt-1{padding-top:4px}.scalar-app .lg\:pr-24{padding-right:96px}}@media (min-width:1200px){.section-header-wrapper[data-v-465a7a78]{grid-template-columns:repeat(2,1fr)}.scalar-app .xl\:mb-1\.5{margin-bottom:6px}.scalar-app .xl\:gap-12{gap:48px}.scalar-app .xl\:border-r{border-right-style:var(--tw-border-style);border-right-width:var(--scalar-border-width)}.scalar-app .xl\:border-none{--tw-border-style:none;border-style:none}.scalar-app .xl\:first\:ml-auto:first-child{margin-left:auto}.scalar-app .container{max-width:1200px}.scalar-app .xl\:\!flex{display:flex!important}.scalar-app .xl\:flex{display:flex}.scalar-app .xl\:hidden{display:none}.scalar-app .xl\:h-fit{height:fit-content}.scalar-app .xl\:h-full{height:100%}.scalar-app .xl\:min-h-header{min-height:48px}.scalar-app .xl\:max-w-\[720px\]{max-width:720px}.scalar-app .xl\:min-w-0{min-width:0}.scalar-app .xl\:min-w-\[720px\]{min-width:720px}.scalar-app .xl\:flex-row{flex-direction:row}.scalar-app .xl\:overflow-auto{overflow:auto}.scalar-app .xl\:overflow-hidden{overflow:hidden}.scalar-app .xl\:rounded-none{border-radius:0}.scalar-app .xl\:pr-0\.5{padding-right:2px}.scalar-app .xl\:pl-2{padding-left:8px}:is(.scalar-app .\*\:xl\:border-t-0>*){border-top-style:var(--tw-border-style);border-top-width:0}:is(.scalar-app .\*\:xl\:border-l>*){border-left-style:var(--tw-border-style);border-left-width:var(--scalar-border-width)}:is(.scalar-app .\*\:first\:xl\:border-l-0>*):first-child{border-left-style:var(--tw-border-style);border-left-width:0}}@media screen and (min-width:1200px){.sponsorContainer_zSLg{flex-direction:row;justify-content:center;max-width:1000px}.sponsor_JDQ1{flex:1 1 0;min-height:100%}}@media (min-width:1440px){.container{max-width:var(--ifm-container-width-xl)}}@media (min-width:96rem){.scalar-app .container{max-width:96rem}}@media (max-width:1000px){.agent-scalar-container[data-v-b45a83b1]{width:100%}.agent-scalar[data-v-b45a83b1]{border-radius:var(--scalar-radius-lg) var(--scalar-radius-lg) 0 0;bottom:0;height:calc(100dvh - 50px);top:auto;width:100%;z-index:12}.scalar-app-exit[data-v-b45a83b1]{z-index:11}.references-layout[data-v-94d6fca6]{--refs-sidebar-height:calc(var(--full-height, 100dvh) - var(--scalar-custom-header-height, 0px));grid-template-areas:"c" "d" "g" "h";grid-template-columns:100%;grid-template-rows:var(--scalar-header-height,0) 0 auto auto}.references-editable[data-v-94d6fca6]{grid-template-areas:"c" "d" "i"}.references-rendered[data-v-94d6fca6]{position:static}.scalar-api-references-standalone-mobile[data-v-94d6fca6]:not(.references-classic){--scalar-header-height:50px}}@media screen and (max-width:1000px){.start[data-v-673f10b6]{overflow:auto;padding:0}}@media (max-width:996px){.col{--ifm-col-width:100%;flex-basis:var(--ifm-col-width);margin-left:0}.footer{--ifm-footer-padding-horizontal:0}.colorModeToggle_DEke,.footer__link-separator,.navbar__item,.sidebar_re4s,.tableOfContents_bqdL{display:none}.footer__col{margin-bottom:calc(var(--ifm-spacing-vertical)*3)}.footer__link-item{display:block;width:max-content}.hero{padding-left:0;padding-right:0}.navbar>.container,.navbar>.container-fluid{padding:0}.navbar__toggle{display:inherit}.navbar__search-input{width:9rem}.pills--block,.tabs--block{flex-direction:column}.navbarSearchContainer_Bca1{position:absolute;right:var(--ifm-navbar-padding-horizontal)}.docItemContainer_F8PC{padding:0 .3rem}}@media not (max-width:996px){.searchBar_RVTs.searchBarLeft_MXDe .dropdownMenu_qbY6{left:0!important;right:auto!important}}@media only screen and (max-width:996px){.searchQueryColumn_q7nx{max-width:60%!important}.searchContextColumn_oWAF{max-width:40%!important}}@media (max-width:966px){.timeline_WTQQ{padding-left:2rem;width:100%}.commitEntry_JtMn:before,.releaseEntry_ZG2V:before,.tagEntry_ORGt:before{left:-2rem}.commitRow_RIZe{flex-wrap:wrap}.commitMsg_XeKn{max-width:100%}.commitMeta_teNK{margin-left:0}.bodyPreview_iKaD{-webkit-line-clamp:2}header.heroBanner_qw_p h1.heroTitle_CteR{font-size:6rem}header.heroBanner_qw_p h3.heroSubTitle_p8ER{font-size:1.5rem}.scrollDown_cjGa{display:none}.button{width:80%}.ctaSection_oQB2,.enjoyingWrap_FXPn,.home-page-features-wrapper .feature.align-left,.home-page-features-wrapper .feature.align-right{flex-direction:column}.home-page-features-wrapper .feature.align-left .feature-half,.home-page-features-wrapper .feature.align-right .feature-half{width:95%}.enjoyingWrap_FXPn .enjoyingWrapPart_oGX0,.inner_rVjr{width:100%}.grid_qTnk{grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}.inner_YtnN{display:grid;gap:1rem;grid-template-columns:1fr 1fr;width:100%}.statItem_at6_{max-width:none;padding:1.25rem 1rem}.count_Ad5j{font-size:2.2rem}}@media screen and (max-width:966px){header.heroBanner_qw_p{padding:2rem}.buttons_LJn2{flex-direction:column}}@media screen and (max-width:800px){.sidebar-active-hide-layout[data-v-02af05d1],.sidebar-active-hide-layout[data-v-57ae0d10]{display:none}.sidebar-active-width[data-v-02af05d1],.sidebar-active-width[data-v-57ae0d10]{width:100%}}@media (max-width:768px){.glitchCode_Pt6F{font-size:6rem}}@media (max-width:720px){:root{--scalar-heading-1:24px;--scalar-page-description:20px}}@media (max-width:720px)and (max-height:480px){.scalar-app .zoomed\:\!whitespace-normal{white-space:normal!important}.scalar-modal-layout .scalar-modal[data-v-c36b47da]{margin-top:5svh;max-height:90svh}.scalar-app .zoomed\:static{position:static}.scalar-app .zoomed\:p-1{padding:4px}.scalar .scalar-app-layout[data-v-6118685a]{height:100%;max-height:90svh}}@media (max-width:600px){.banner_woPo{font-size:.85rem;padding:.4rem .75rem}.closeBtn_fC0A{font-size:1.1rem;right:.5rem}.operation-example-card[data-v-55addca4]{max-height:unset;position:static}.examples[data-v-3c3c5405]>*{max-height:unset}.introduction-card-row[data-v-5764c94a]>*{max-width:100%}}@media screen and (max-width:600px){.references-classic .client-libraries[data-v-6a49c111]{flex-direction:column}.start-item[data-v-673f10b6],.start-section-colors .start-item[data-v-673f10b6]{border-radius:0;border-right:none;border-top:1px solid var(--scalar-border-color);width:100%}.start-item[data-v-673f10b6]:empty{display:none}.start-h2[data-v-673f10b6]{border-bottom:none}.start li[data-v-673f10b6]{width:100%}.start-copy[data-v-673f10b6]{padding:48px 0 24px}}@media (max-width:576px){.markdown h1:first-child{--ifm-h1-font-size:2rem}.markdown>h2{--ifm-h2-font-size:1.5rem}.markdown>h3{--ifm-h3-font-size:1.25rem}.navbar__search-input:not(:focus){width:2rem}.searchBar_RVTs .dropdownMenu_qbY6{max-width:calc(100vw - var(--ifm-navbar-padding-horizontal)*2);width:var(--search-local-modal-width-sm,340px)}.searchBarContainer_NW3z:not(.focused_OWtg) .searchClearButton_qk4g,.searchHintContainer_Pkmr{display:none}.title_f1Hy{font-size:2rem}}@media screen and (max-width:576px){.searchQueryColumn_q7nx{max-width:100%!important}.searchContextColumn_oWAF{max-width:100%!important;padding-left:var(--ifm-spacing-horizontal)!important}}@media (max-width:500px){main.docs div.docs-contents a{width:100%}}@media (max-width:460px){:root{--scalar-font-size-1:22px;--scalar-font-size-2:14px;--scalar-font-size-3:12px}}@media screen and (max-width:450px){.client-libraries[data-v-6a49c111]:nth-of-type(4),.client-libraries[data-v-6a49c111]:nth-of-type(5){display:none}}@media (color-gamut:p3){.light-mode{--scalar-color-accent:color(display-p3 0 .6 1);--scalar-color-green:color(display-p3 .023529 .564706 .380392);--scalar-color-red:color(display-p3 .937255 0 .023529);--scalar-color-yellow:color(display-p3 .929412 .745098 .12549);--scalar-color-blue:color(display-p3 0 .509804 .815686);--scalar-color-orange:color(display-p3 1 .4 .02);--scalar-color-purple:color(display-p3 .321569 .011765 .819608)}.dark-mode{--scalar-color-accent:color(display-p3 .07 .67 1);--scalar-color-green:color(display-p3 0 .713725 .282353);--scalar-color-red:color(display-p3 .862745 .105882 .098039);--scalar-color-yellow:color(display-p3 1 .788235 .05098);--scalar-color-blue:color(display-p3 .305882 .701961 .92549);--scalar-color-orange:color(display-p3 1 .552941 .301961);--scalar-color-purple:color(display-p3 .694118 .568627 .976471)}}@media (hover:hover){.backToTopButton_sjWU:hover{background-color:var(--ifm-color-emphasis-300)}.scalar-app .hover\:brightness-90:hover{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);--tw-brightness:brightness(90%)}.scalar-app .hover\:brightness-75:hover,.scalar-app .hover\:brightness-90:hover{filter:var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, )}.scalar-app .group-hover\/heading\:opacity-100:is(:where(.group\/heading):hover *),.scalar-app .group-hover\/parameter-item\:opacity-100:is(:where(.group\/parameter-item):hover *),.scalar-app .group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.scalar-app .hover\:opacity-100:hover,.scalar-app .peer-hover\/button\:opacity-100:is(:where(.peer\/button):hover~*){opacity:1}.scalar-app .group-hover\/button\:opacity-0:is(:where(.group\/button):hover *){opacity:0}.scalar-app .peer-hover\/button\:opacity-100:is(:where(.peer\/button):hover~*){opacity:1}.scalar-app .hover\:opacity-100:hover{opacity:1}.scalar-app .group-hover\/button\:bg-sidebar-indent-border-hover:is(:where(.group\/button):hover *){background-color:var(--scalar-sidebar-indent-border-hover,var(--scalar-border-color))}.scalar-app .group-hover\/button\:text-c-1:is(:where(.group\/button):hover *){color:var(--scalar-color-1)}.scalar-app .hover\:bg-h-btn:hover{background-color:var(--scalar-button-1-hover)}.scalar-app .hover\:bg-sidebar-b-1:hover{background-color:var(--scalar-sidebar-background-1,var(--scalar-background-1))}.scalar-app .hover\:bg-sidebar-b-hover:hover{background-color:var(--scalar-sidebar-item-hover-background,var(--scalar-background-2))}.scalar-app .hover\:bg-linear-to-b:hover{--tw-gradient-position:to bottom;background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .hover\:bg-linear-to-t:hover{--tw-gradient-position:to top;background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .hover\:text-sidebar-c-1:hover{color:var(--scalar-sidebar-color-1,var(--scalar-color-1))}.scalar-app .hover\:text-sidebar-c-hover:hover{color:var(--scalar-sidebar-item-hover-color,var(--scalar-sidebar-color-2))}.scalar-app .dark\:hover\:bg-b-3:where(.dark-mode,.dark-mode *):hover{background-color:var(--scalar-background-3)}.scalar-app .dark\:hover\:bg-linear-to-b:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to bottom;background-image:linear-gradient(var(--tw-gradient-stops))}.scalar-app .dark\:hover\:bg-linear-to-t:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to top;background-image:linear-gradient(var(--tw-gradient-stops))}@supports (background-image:linear-gradient(in lab,red,red)){.scalar-app .hover\:bg-linear-to-b:hover{--tw-gradient-position:to bottom in oklab}.scalar-app .hover\:bg-linear-to-t:hover{--tw-gradient-position:to top in oklab}.scalar-app .dark\:hover\:bg-linear-to-b:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to bottom in oklab}.scalar-app .dark\:hover\:bg-linear-to-t:where(.dark-mode,.dark-mode *):hover{--tw-gradient-position:to top in oklab}}.scalar-app .group-hover\:block:is(:where(.group):hover *){display:block}.scalar-app .group-hover\:flex:is(:where(.group):hover *){display:flex}.scalar-app .group-hover\:hidden:is(:where(.group):hover *){display:none}.scalar-app .group-hover\:inline:is(:where(.group):hover *){display:inline}.scalar-app .group-hover\:pr-5:is(:where(.group):hover *){padding-right:20px}.scalar-app .group-hover\:pr-6:is(:where(.group):hover *){padding-right:24px}.scalar-app .group-hover\:pr-10:is(:where(.group):hover *){padding-right:40px}.scalar-app .group-hover\:text-c-1:is(:where(.group):hover *){color:var(--scalar-color-1)}.scalar-app .group-hover\:opacity-80:is(:where(.group):hover *){opacity:.8}.scalar-app .group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.scalar-app .group-hover\/auth\:absolute:is(:where(.group\/auth):hover *){position:absolute}.scalar-app .group-hover\/auth\:h-auto:is(:where(.group\/auth):hover *){height:auto}.scalar-app .group-hover\/auth\:border-b:is(:where(.group\/auth):hover *){border-bottom-style:var(--tw-border-style);border-bottom-width:var(--scalar-border-width)}.scalar-app .group-hover\/cell\:opacity-100:is(:where(.group\/cell):hover *){opacity:1}.scalar-app .group-hover\/item\:flex:is(:where(.group\/item):hover *){display:flex}.scalar-app .group-hover\/item\:opacity-100:is(:where(.group\/item):hover *),.scalar-app .group-hover\/params\:opacity-100:is(:where(.group\/params):hover *){opacity:1}.scalar-app .group-hover\/row\:flex:is(:where(.group\/row):hover *){display:flex}.scalar-app .group-hover\/scopes-accordion\:text-c-2:is(:where(.group\/scopes-accordion):hover *){color:var(--scalar-color-2)}.scalar-app .group-hover\/upload\:block:is(:where(.group\/upload):hover *){display:block}:is(.scalar-app .group-hover\/auth\:\*\:first\:line-clamp-none:is(:where(.group\/auth):hover *)>*):first-child{-webkit-line-clamp:unset;-webkit-box-orient:horizontal;display:block;overflow:visible}.scalar-app .hover\:cursor-default:hover{cursor:default}.scalar-app .hover\:border-\(--scalar-background-3\):hover{border-color:var(--scalar-background-3)}.scalar-app .hover\:border-inherit:hover{border-color:inherit}.scalar-app .hover\:bg-b-2:hover{background-color:var(--scalar-background-2)}.scalar-app .hover\:bg-b-3:hover{background-color:var(--scalar-background-3)}.scalar-app .hover\:bg-c-accent\/20:hover{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .hover\:bg-c-accent\/20:hover{background-color:var(--scalar-color-accent)}@supports (color:color-mix(in lab,red,red)){.scalar-app .hover\:bg-c-accent\/20:hover{background-color:color-mix(in oklab,var(--scalar-color-accent) 20%,#0000)}}}.scalar-app .hover\:bg-inherit:hover{background-color:inherit}.scalar-app .hover\:bg-sidebar-b-active:hover{background-color:var(--scalar-sidebar-item-active-background,var(--scalar-background-2))}.scalar-app .hover\:whitespace-normal:hover{white-space:normal}.scalar-app .hover\:text-c-1:hover{color:var(--scalar-color-1)}.scalar-app .hover\:text-c-2:hover{color:var(--scalar-color-2)}.scalar-app .hover\:underline:hover{text-decoration-line:underline}.scalar-app .hover\:brightness-75:hover{--tw-brightness:brightness(75%)}.scalar-app .hover\:dark\:bg-b-2:hover:where(.dark-mode,.dark-mode *){background-color:var(--scalar-background-2)}}@media (pointer:fine){.thin-scrollbar{scrollbar-width:thin}.thin-scrollbar::-webkit-scrollbar{height:var(--ifm-scrollbar-size);width:var(--ifm-scrollbar-size)}.thin-scrollbar::-webkit-scrollbar-track{background:var(--ifm-scrollbar-track-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb{background:var(--ifm-scrollbar-thumb-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb:hover{background:var(--ifm-scrollbar-thumb-hover-background-color)}}@media (pointer:coarse){.scalar-app .cm-scroller,.scalar-app .custom-scroll{padding-right:12px}}@media (prefers-color-scheme:dark){#carbonads{--carbon-bg-primary:#1f1f1f;--carbon-bg-secondary:#262626;--carbon-text-color:#e6e6e6}}@media (prefers-reduced-motion:reduce){:root{--ifm-transition-fast:0ms;--ifm-transition-slow:0ms}.skeleton_NVtD{animation:none;opacity:.6}}@media print{.announcementBar_mb4j,.footer,.menu,.navbar,.noPrint_WFHX,.pagination-nav,.table-of-contents,.tocMobile_ITEo{display:none}.tabs{page-break-inside:avoid}.codeBlockLines_e6Vv{white-space:pre-wrap}} \ No newline at end of file diff --git a/assets/js/044d5aa6.1c171b49.js b/assets/js/044d5aa6.5a03dbc5.js similarity index 99% rename from assets/js/044d5aa6.1c171b49.js rename to assets/js/044d5aa6.5a03dbc5.js index 92c3757f..8d13ec88 100644 --- a/assets/js/044d5aa6.1c171b49.js +++ b/assets/js/044d5aa6.5a03dbc5.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[5332],{67670(e,n,s){s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>r,default:()=>d,frontMatter:()=>t,metadata:()=>i,toc:()=>l});const i=JSON.parse('{"id":"icons","title":"Icons","description":"Both sections and items can have an icon, which is specified using the icon attribute. Using icons improves the aesthetics of your UI and makes the app more intuitive to use. Dashy supports multiple different icon providers, usage instructions for which are explained here.","source":"@site/docs/icons.md","sourceDirName":".","slug":"/icons","permalink":"/docs/icons","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/icons.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Troubleshooting","permalink":"/docs/troubleshooting"},"next":{"title":"Widgets","permalink":"/docs/widgets"}}');var o=s(74848),c=s(28453);const t={},r="Icons",a={},l=[{value:"Favicons",id:"favicons",level:2},{value:"Font Awesome",id:"font-awesome",level:2},{value:"Simple Icons",id:"simple-icons",level:2},{value:"Generative Icons",id:"generative-icons",level:2},{value:"Emoji Icons",id:"emoji-icons",level:2},{value:"selfh.st Icons",id:"selfhst-icons",level:2},{value:"Home-Lab Icons",id:"home-lab-icons",level:2},{value:"Material Design Icons",id:"material-design-icons",level:2},{value:"Icons by URL",id:"icons-by-url",level:2},{value:"Local Icons",id:"local-icons",level:2},{value:"Default Icon",id:"default-icon",level:2},{value:"No Icon",id:"no-icon",level:2},{value:"Icon Collections and Resources",id:"icon-collections-and-resources",level:2},{value:"Notes",id:"notes",level:2}];function h(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,c.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"icons",children:"Icons"})}),"\n",(0,o.jsxs)(n.p,{children:["Both sections and items can have an icon, which is specified using the ",(0,o.jsx)(n.code,{children:"icon"})," attribute. Using icons improves the aesthetics of your UI and makes the app more intuitive to use. Dashy supports multiple different icon providers, usage instructions for which are explained here."]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#favicons",children:"Auto-Fetched Favicons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#font-awesome",children:"Font Awesome Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#simple-icons",children:"Simple Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#generative-icons",children:"Generative Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#emoji-icons",children:"Emoji Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#selfhst-icons",children:"selfh.st Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#home-lab-icons",children:"Home-Lab Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#material-design-icons",children:"Material Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#icons-by-url",children:"Icons by URL"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#local-icons",children:"Local Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#default-icon",children:"Using a Default Icon"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#no-icon",children:"No Icon"})}),"\n",(0,o.jsx)(n.img,{width:"500",src:"https://i.ibb.co/GTVmZnc/dashy-example-icons.png"}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"favicons",children:"Favicons"}),"\n",(0,o.jsxs)(n.p,{children:["Dashy can auto-fetch an icon for a given service, using it's favicon. Just set ",(0,o.jsx)(n.code,{children:"icon: favicon"})," to use this feature."]}),"\n ",(0,o.jsx)(n.img,{width:"580",src:"https://i.ibb.co/k6wyhnB/favicon-icons.png"}),"\n",(0,o.jsx)(n.p,{children:"Since different websites host their favicons at different paths, for the best results Dashy can use an API to resolve a websites icon."}),"\n",(0,o.jsxs)(n.p,{children:["The default favicon API is ",(0,o.jsx)(n.a,{href:"https://favicon.allesedv.com/",children:"allesedv.com"}),", but you can change this under ",(0,o.jsx)(n.code,{children:"appConfig.faviconApi"}),". If you'd prefer not to use an API, just set this value to ",(0,o.jsx)(n.code,{children:"local"}),". You can also use different APIs for individual items, by setting ",(0,o.jsx)(n.code,{children:"icon: favicon-[api]"}),", e.g. ",(0,o.jsx)(n.code,{children:"favicon-iconhorse"}),"."]}),"\n",(0,o.jsx)(n.p,{children:"The following favicon APIs are supported:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"allesedv"})," - ",(0,o.jsx)(n.a,{href:"https://favicon.allesedv.com/",children:"allesedv.com"})," is a highly efficient IPv6-enabled service"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"iconhorse"})," - ",(0,o.jsx)(n.a,{href:"https://icon.horse/",children:"Icon.Horse"})," returns quality icons for any site, with caching for speed and fallbacks for sites without an icon"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"faviconkit"})," - ",(0,o.jsx)(n.a,{href:"https://faviconkit.com/",children:"faviconkit.com"})," good quality icons and most sites supported (Note: down as of Nov '21)"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"besticon"})," - ",(0,o.jsx)(n.a,{href:"https://github.com/mat/besticon",children:"BestIcon"})," fetches websites icons from manifest"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"mcapi"})," - ",(0,o.jsx)(n.a,{href:"https://eu.mc-api.net/",children:"MC-API"})," fetches default website favicon, originally a Minecraft util"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"duckduckgo"})," - Returns decent quality website icons, from DuckDuckGo search"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"google"})," - Official Google favicon API service, good support for all sites, but poor quality"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"yandex"})," - Lower quality icons, but useful in some regions where other services are blocked"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"webmasterapi"})," - ",(0,o.jsx)(n.a,{href:"https://www.webmasterapi.com/",children:"WebMasterAPI"})," fetches website favicons via their API"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"local"})," - Set to local to fetch the default icon at /favicon.ico instead of using an API"]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["If for a given service none of the APIs work in your situation, and nor does local, then the best option is to find the path of the services logo or favicon, and set the icon to the URL of the raw image. For example, ",(0,o.jsx)(n.code,{children:"icon: https://monitoring.local/faviconx128.png"}),"- you can find this path using the browser dev tools."]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"font-awesome",children:"Font Awesome"}),"\n",(0,o.jsxs)(n.p,{children:["You can use any ",(0,o.jsx)(n.a,{href:"https://fontawesome.com/icons",children:"Font Awesome Icon"})," simply by specifying it's identifier. This is in the format of ",(0,o.jsx)(n.code,{children:"[category] [name]"})," and can be found on the page for that icon on the Font Awesome site. For example: ",(0,o.jsx)(n.code,{children:"fas fa-rocket"}),", ",(0,o.jsx)(n.code,{children:"fab fa-monero"})," or ",(0,o.jsx)(n.code,{children:"fas fa-unicorn"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["Font-Awesome has a wide variety of free icons, but you can also use their pro icons if you have a membership. To do so, you need to specify your license key under: ",(0,o.jsx)(n.code,{children:"appConfig.fontAwesomeKey"}),". This is usually a 10-digit string, for example ",(0,o.jsx)(n.code,{children:"13014ae648"}),"."]}),"\n ",(0,o.jsx)(n.img,{width:"500",src:"https://i.ibb.co/tMtwNYZ/fontawesome-icons3.png"}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"simple-icons",children:"Simple Icons"}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.a,{href:"https://simpleicons.org/",children:"SimpleIcons.org"})," is a collection of 2000+ high quality, free and open source brand and logo SVG icons. Usage of which is very similar to font-awesome icons. First find the glyph you want to use on the ",(0,o.jsx)(n.a,{href:"https://simpleicons.org/",children:"website"}),", then just set your icon to the simple icon slug, prefixed with ",(0,o.jsx)(n.code,{children:"si-"}),"."]}),"\n ",(0,o.jsx)(n.img,{width:"580",src:"https://i.ibb.co/MVhkXfC/simple-icons-example.png"}),"\n",(0,o.jsx)(n.p,{children:"For example:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-yaml",children:"sections:\n- name: Simple Icons Example\n items:\n - title: Portainer\n icon: si-portainer\n - title: FreeNAS\n icon: si-freenas\n - title: NextCloud\n icon: si-nextcloud\n - title: Home Assistant\n icon: si-homeassistant\n"})}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"generative-icons",children:"Generative Icons"}),"\n",(0,o.jsxs)(n.p,{children:["To uses a unique and programmatically generated icon for a given service just set ",(0,o.jsx)(n.code,{children:"icon: generative"}),". This is particularly useful when you have a lot of similar services with a different IP or port, and no specific icon. These icons are generated with ",(0,o.jsx)(n.a,{href:"https://api.dicebear.com/",children:"DiceBear"})," (or ",(0,o.jsx)(n.a,{href:"https://evatar.io/",children:"Evatar"})," for fallback), and use a hash of the services domain/ ip for entropy, so each domain will have a unique icon."]}),"\n ",(0,o.jsx)(n.img,{width:"500",src:"https://i.ibb.co/b2pC2CL/generative-icons-2.png"}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"emoji-icons",children:"Emoji Icons"}),"\n",(0,o.jsxs)(n.p,{children:["You can use almost any emoji as an icon for items or sections. You can specify the emoji either by pasting it directly, using it's unicode ( e.g. ",(0,o.jsx)(n.code,{children:"'U+1F680'"}),") or shortcode (e.g. ",(0,o.jsx)(n.code,{children:"':rocket:'"}),"). You can find these codes for any emoji using ",(0,o.jsx)(n.a,{href:"https://emojipedia.org/",children:"Emojipedia"})," (near the bottom of emoji each page), or for a quick reference to emoji shortcodes, check out ",(0,o.jsx)(n.a,{href:"https://emojis.ninja/",children:"emojis.ninja"})," by @nomanoff."]}),"\n ",(0,o.jsx)(n.img,{width:"580",src:"https://i.ibb.co/YLwgTf9/emoji-icons-1.png"}),"\n",(0,o.jsxs)(n.p,{children:["For example, these will all render the same rocket (\ud83d\ude80) emoji: ",(0,o.jsx)(n.code,{children:"icon: ':rocket:'"})," or ",(0,o.jsx)(n.code,{children:"icon: 'U+1F680'"})," or ",(0,o.jsx)(n.code,{children:"icon: \ud83d\ude80"})]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"selfhst-icons",children:"selfh.st Icons"}),"\n",(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.a,{href:"https://selfh.st/",children:"selfh.st"})," project provides a set of icons, originally for self-hosted services, but now expanded to include a wide variety of services. These icons can be used by specifying the icon name (without extension and with all spaces replaced with -) preceded by ",(0,o.jsx)(n.code,{children:"sh-"}),". See ",(0,o.jsx)(n.a,{href:"https://selfh.st/icons/",children:"https://selfh.st/icons/"})," for a full list of all available icons. For example, the Home Assistant icon is ",(0,o.jsx)(n.code,{children:"sh-home-assistant"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["Note: These icons are fetched from the jsdelivr CDN, so if you require offline access, the ",(0,o.jsx)(n.a,{href:"#local-icons",children:"Local Icons"})," method may be a better option for you."]}),"\n ",(0,o.jsx)(n.img,{width:"580",src:"https://i.ibb.co/pfy09LH/Screenshot-from-2025-01-08-22-04-21.png"}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"home-lab-icons",children:"Home-Lab Icons"}),"\n",(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.a,{href:"https://github.com/homarr-labs/dashboard-icons",children:"dashboard-icons"})," repo by ",(0,o.jsx)(n.a,{href:"https://github.com/homarr-labs",children:"Homarr Labs"})," provides a comprehensive collection of high-quality icons for commonly self-hosted services. Dashy natively supports these icons, and you can use them just by specifying the icon name (without extension) preceded by ",(0,o.jsx)(n.code,{children:"hl-"}),". SVG is preferred where available, with an automatic fallback to PNG for icons that aren't published as SVG. See ",(0,o.jsx)(n.a,{href:"https://github.com/homarr-labs/dashboard-icons/tree/main/svg",children:"here"})," for a full list of all available icons. Note that these are fetched and cached straight from jsDelivr, so if you require offline access, the ",(0,o.jsx)(n.a,{href:"#local-icons",children:"Local Icons"})," method may be a better option for you."]}),"\n",(0,o.jsx)(n.p,{children:"For example:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-yaml",children:"sections:\n- name: Home Lab Icons Example\n items:\n - title: AdGuard Home\n icon: hl-adguardhome\n - title: Long Horn\n icon: hl-longhorn\n - title: Nagios\n icon: hl-nagios\n - title: Whoogle Search\n icon: hl-whooglesearch\n"})}),"\n ",(0,o.jsx)(n.img,{width:"580",src:"https://i.ibb.co/PQzYHmD/homelab-icons-2.png"}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"material-design-icons",children:"Material Design Icons"}),"\n",(0,o.jsxs)(n.p,{children:["Dashy also supports 5000+ ",(0,o.jsx)(n.a,{href:"https://github.com/Templarian/MaterialDesign",children:"material-design-icons"}),". To use these, first find the name/ slug for your icon ",(0,o.jsx)(n.a,{href:"https://dev.materialdesignicons.com/icons",children:"here"}),", and then prefix is with ",(0,o.jsx)(n.code,{children:"mdi-"}),"."]}),"\n",(0,o.jsx)(n.p,{children:"For example:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-yaml",children:"sections:\n- name: Material Design Icons Example\n items:\n - title: Alien Icon\n icon: mdi-alien\n - title: Fire Icon\n icon: mdi-fire\n - title: Dino Icon\n icon: mdi-google-downasaur\n"})}),"\n ",(0,o.jsx)(n.img,{width:"500",src:"https://i.ibb.co/fC9B4mq/icons-mdi-example.png"}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"icons-by-url",children:"Icons by URL"}),"\n",(0,o.jsxs)(n.p,{children:["You can also set an icon by passing in a valid URL pointing to the icons location. For example ",(0,o.jsx)(n.code,{children:"icon: https://i.ibb.co/710B3Yc/space-invader-x256.png"}),", this can be in .png, .jpg or .svg format, and hosted anywhere (local or remote) - so long as it's accessible from where you are hosting Dashy. The icon will be automatically scaled to fit, however loading in a lot of large icons may have a negative impact on performance, especially if you visit Dashy from new devices often."]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"local-icons",children:"Local Icons"}),"\n",(0,o.jsxs)(n.p,{children:["You may also want to store your icons locally, bundled within Dashy so that there is no reliance on outside services. This can be done by putting the icons within Dashy's ",(0,o.jsx)(n.code,{children:"./user-data/item-icons/"})," directory. If you are using Docker, then the easiest option is to map a volume from your host system, for example: ",(0,o.jsx)(n.code,{children:"-v /local/image/directory:/app/user-data/item-icons/"}),". To reference an icon stored locally, just specify it's name and extension. For example, if my icon was stored in ",(0,o.jsx)(n.code,{children:"/app/user-data/item-icons/maltrail.png"}),", then I would just set ",(0,o.jsx)(n.code,{children:"icon: maltrail.png"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["You can also use sub-folders within the ",(0,o.jsx)(n.code,{children:"item-icons"})," directory to keep things organized. You would then specify an icon with it's folder name slash image name. For example: ",(0,o.jsx)(n.code,{children:"networking/monit.png"})]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"default-icon",children:"Default Icon"}),"\n",(0,o.jsxs)(n.p,{children:["If you'd like to set a default icon, to be applied to any items which don't have an icon already set, then this can be done under ",(0,o.jsx)(n.code,{children:"appConfig.defaultIcon"}),"."]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"no-icon",children:"No Icon"}),"\n",(0,o.jsxs)(n.p,{children:["If you don't wish for a given item or section to have an icon, just leave out the ",(0,o.jsx)(n.code,{children:"icon"})," attribute."]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"icon-collections-and-resources",children:"Icon Collections and Resources"}),"\n",(0,o.jsxs)(n.p,{children:["The following websites provide good-quality, free icon sets. To use any of these icons, either copy the link to the raw icon (it should end in ",(0,o.jsx)(n.code,{children:".svg"})," or ",(0,o.jsx)(n.code,{children:".png"}),") and paste it as your ",(0,o.jsx)(n.code,{children:"icon"}),", or download and save the icons in ",(0,o.jsx)(n.code,{children:"/user-data/item-icons"})," / mapped Docker volume. Full credit to the authors, please see the licenses for each service for usage and copyright information."]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://thehomelab.wiki/books/helpful-tools-resources/page/icons-for-self-hosted-dashboards",children:"Icons for Self-Hosted Apps"})," - 350+ high-quality icons for commonly self-hosted services"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://svgbox.net/iconsets/",children:"SVG Box"})," - Cryptocurrency, social media apps and flag icons"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://simpleicons.org/",children:"Simple Icons"})," - Free SVG brand icons, with easy API access"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://github.com/google/material-design-icons/",children:"Material Design Icons"})," - Hundreds of Open source PNG + SVG icons by Google"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://icons8.com/icons",children:"Icons8"})," - Thousands of icons, all with free versions at 64x64"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://www.flaticon.com/",children:"Flat Icon"})," - Wide variety of icon sets, most of which are free to use"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://www.svgrepo.com/",children:"SVG Repo"})," - 300,000+ Vector Icons"]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["If you are a student, then you can get free access to premium icons on ",(0,o.jsx)(n.a,{href:"https://education.github.com/pack/redeem/iconscout-student",children:"Icon Scout"})," or ",(0,o.jsx)(n.a,{href:"https://icons8.com/github-students",children:"Icons8"})," using the ",(0,o.jsx)(n.a,{href:"https://education.github.com/pack",children:"GitHub Student Pack"}),"."]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"notes",children:"Notes"}),"\n",(0,o.jsx)(n.p,{children:"If you are using icons from an external source, these will be fetched on initial page load automatically, if and when needed. But combining icons from multiple services may have a negative impact on performance."}),"\n",(0,o.jsx)(n.p,{children:"You can improve load speeds, by downloading your required icons, and serving them locally. Scaling icons to the minimum required dimensions (e.g. 128x128 or 64x64) will also greatly improve application load times."}),"\n",(0,o.jsx)(n.p,{children:"For icons from external sources, please see the Privacy Policies and Licenses for that provider."})]})}function d(e={}){const{wrapper:n}={...(0,c.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>r});var i=s(96540);const o={},c=i.createContext(o);function t(e){const n=i.useContext(c);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:t(e.components),i.createElement(c.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[5332],{67670(e,n,s){s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>r,default:()=>d,frontMatter:()=>t,metadata:()=>i,toc:()=>l});const i=JSON.parse('{"id":"icons","title":"Icons","description":"Both sections and items can have an icon, which is specified using the icon attribute. Using icons improves the aesthetics of your UI and makes the app more intuitive to use. Dashy supports multiple different icon providers, usage instructions for which are explained here.","source":"@site/docs/icons.md","sourceDirName":".","slug":"/icons","permalink":"/docs/icons","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/icons.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Troubleshooting","permalink":"/docs/troubleshooting"},"next":{"title":"Widgets","permalink":"/docs/widgets"}}');var o=s(74848),c=s(28453);const t={},r="Icons",a={},l=[{value:"Favicons",id:"favicons",level:2},{value:"Font Awesome",id:"font-awesome",level:2},{value:"Simple Icons",id:"simple-icons",level:2},{value:"Generative Icons",id:"generative-icons",level:2},{value:"Emoji Icons",id:"emoji-icons",level:2},{value:"selfh.st Icons",id:"selfhst-icons",level:2},{value:"Home-Lab Icons",id:"home-lab-icons",level:2},{value:"Material Design Icons",id:"material-design-icons",level:2},{value:"Icons by URL",id:"icons-by-url",level:2},{value:"Local Icons",id:"local-icons",level:2},{value:"Default Icon",id:"default-icon",level:2},{value:"No Icon",id:"no-icon",level:2},{value:"Icon Collections and Resources",id:"icon-collections-and-resources",level:2},{value:"Notes",id:"notes",level:2}];function h(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,c.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"icons",children:"Icons"})}),"\n",(0,o.jsxs)(n.p,{children:["Both sections and items can have an icon, which is specified using the ",(0,o.jsx)(n.code,{children:"icon"})," attribute. Using icons improves the aesthetics of your UI and makes the app more intuitive to use. Dashy supports multiple different icon providers, usage instructions for which are explained here."]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#favicons",children:"Auto-Fetched Favicons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#font-awesome",children:"Font Awesome Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#simple-icons",children:"Simple Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#generative-icons",children:"Generative Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#emoji-icons",children:"Emoji Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#selfhst-icons",children:"selfh.st Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#home-lab-icons",children:"Home-Lab Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#material-design-icons",children:"Material Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#icons-by-url",children:"Icons by URL"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#local-icons",children:"Local Icons"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#default-icon",children:"Using a Default Icon"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.a,{href:"#no-icon",children:"No Icon"})}),"\n",(0,o.jsx)(n.img,{width:"500",src:"https://i.ibb.co/GTVmZnc/dashy-example-icons.png"}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"favicons",children:"Favicons"}),"\n",(0,o.jsxs)(n.p,{children:["Dashy can auto-fetch an icon for a given service, using it's favicon. Just set ",(0,o.jsx)(n.code,{children:"icon: favicon"})," to use this feature."]}),"\n ",(0,o.jsx)(n.img,{width:"580",src:"https://i.ibb.co/k6wyhnB/favicon-icons.png"}),"\n",(0,o.jsx)(n.p,{children:"Since different websites host their favicons at different paths, for the best results Dashy can use an API to resolve a websites icon."}),"\n",(0,o.jsxs)(n.p,{children:["The default favicon API is ",(0,o.jsx)(n.a,{href:"https://favicon.allesedv.com/",children:"allesedv.com"}),", but you can change this under ",(0,o.jsx)(n.code,{children:"appConfig.faviconApi"}),". If you'd prefer not to use an API, just set this value to ",(0,o.jsx)(n.code,{children:"local"}),". You can also use different APIs for individual items, by setting ",(0,o.jsx)(n.code,{children:"icon: favicon-[api]"}),", e.g. ",(0,o.jsx)(n.code,{children:"favicon-iconhorse"}),"."]}),"\n",(0,o.jsx)(n.p,{children:"The following favicon APIs are supported:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"allesedv"})," - ",(0,o.jsx)(n.a,{href:"https://favicon.allesedv.com/",children:"allesedv.com"})," is a highly efficient IPv6-enabled service"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"iconhorse"})," - ",(0,o.jsx)(n.a,{href:"https://icon.horse/",children:"Icon.Horse"})," returns quality icons for any site, with caching for speed and fallbacks for sites without an icon"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"faviconkit"})," - ",(0,o.jsx)(n.a,{href:"https://faviconkit.com/",children:"faviconkit.com"})," good quality icons and most sites supported (Note: down as of Nov '21)"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"besticon"})," - ",(0,o.jsx)(n.a,{href:"https://github.com/mat/besticon",children:"BestIcon"})," fetches websites icons from manifest"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"mcapi"})," - ",(0,o.jsx)(n.a,{href:"https://eu.mc-api.net/",children:"MC-API"})," fetches default website favicon, originally a Minecraft util"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"duckduckgo"})," - Returns decent quality website icons, from DuckDuckGo search"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"google"})," - Official Google favicon API service, good support for all sites, but poor quality"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"yandex"})," - Lower quality icons, but useful in some regions where other services are blocked"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"webmasterapi"})," - ",(0,o.jsx)(n.a,{href:"https://www.webmasterapi.com/",children:"WebMasterAPI"})," fetches website favicons via their API"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"local"})," - Set to local to fetch the default icon at /favicon.ico instead of using an API"]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["If for a given service none of the APIs work in your situation, and nor does local, then the best option is to find the path of the services logo or favicon, and set the icon to the URL of the raw image. For example, ",(0,o.jsx)(n.code,{children:"icon: https://monitoring.local/faviconx128.png"}),"- you can find this path using the browser dev tools."]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"font-awesome",children:"Font Awesome"}),"\n",(0,o.jsxs)(n.p,{children:["You can use any ",(0,o.jsx)(n.a,{href:"https://fontawesome.com/icons",children:"Font Awesome Icon"})," simply by specifying it's identifier. This is in the format of ",(0,o.jsx)(n.code,{children:"[category] [name]"})," and can be found on the page for that icon on the Font Awesome site. For example: ",(0,o.jsx)(n.code,{children:"fas fa-rocket"}),", ",(0,o.jsx)(n.code,{children:"fab fa-monero"})," or ",(0,o.jsx)(n.code,{children:"fas fa-unicorn"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["Font-Awesome has a wide variety of free icons, but you can also use their pro icons if you have a membership. To do so, you need to specify your license key under: ",(0,o.jsx)(n.code,{children:"appConfig.fontAwesomeKey"}),". This is usually a 10-digit string, for example ",(0,o.jsx)(n.code,{children:"13014ae648"}),"."]}),"\n ",(0,o.jsx)(n.img,{width:"500",src:"https://i.ibb.co/tMtwNYZ/fontawesome-icons3.png"}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"simple-icons",children:"Simple Icons"}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.a,{href:"https://simpleicons.org/",children:"SimpleIcons.org"})," is a collection of 2000+ high quality, free and open source brand and logo SVG icons. Usage of which is very similar to font-awesome icons. First find the glyph you want to use on the ",(0,o.jsx)(n.a,{href:"https://simpleicons.org/",children:"website"}),", then just set your icon to the simple icon slug, prefixed with ",(0,o.jsx)(n.code,{children:"si-"}),"."]}),"\n ",(0,o.jsx)(n.img,{width:"580",src:"https://i.ibb.co/MVhkXfC/simple-icons-example.png"}),"\n",(0,o.jsx)(n.p,{children:"For example:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-yaml",children:"sections:\n- name: Simple Icons Example\n items:\n - title: Portainer\n icon: si-portainer\n - title: FreeNAS\n icon: si-freenas\n - title: NextCloud\n icon: si-nextcloud\n - title: Home Assistant\n icon: si-homeassistant\n"})}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"generative-icons",children:"Generative Icons"}),"\n",(0,o.jsxs)(n.p,{children:["To uses a unique and programmatically generated icon for a given service just set ",(0,o.jsx)(n.code,{children:"icon: generative"}),". This is particularly useful when you have a lot of similar services with a different IP or port, and no specific icon. These icons are generated with ",(0,o.jsx)(n.a,{href:"https://api.dicebear.com/",children:"DiceBear"})," (or ",(0,o.jsx)(n.a,{href:"https://evatar.io/",children:"Evatar"})," for fallback), and use a hash of the services domain/ ip for entropy, so each domain will have a unique icon."]}),"\n ",(0,o.jsx)(n.img,{width:"500",src:"https://i.ibb.co/b2pC2CL/generative-icons-2.png"}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"emoji-icons",children:"Emoji Icons"}),"\n",(0,o.jsxs)(n.p,{children:["You can use almost any emoji as an icon for items or sections. You can specify the emoji either by pasting it directly, using it's unicode ( e.g. ",(0,o.jsx)(n.code,{children:"'U+1F680'"}),") or shortcode (e.g. ",(0,o.jsx)(n.code,{children:"':rocket:'"}),"). You can find these codes for any emoji using ",(0,o.jsx)(n.a,{href:"https://emojipedia.org/",children:"Emojipedia"})," (near the bottom of emoji each page), or for a quick reference to emoji shortcodes, check out ",(0,o.jsx)(n.a,{href:"https://emojis.ninja/",children:"emojis.ninja"})," by @nomanoff."]}),"\n ",(0,o.jsx)(n.img,{width:"580",src:"https://i.ibb.co/YLwgTf9/emoji-icons-1.png"}),"\n",(0,o.jsxs)(n.p,{children:["For example, these will all render the same rocket (\ud83d\ude80) emoji: ",(0,o.jsx)(n.code,{children:"icon: ':rocket:'"})," or ",(0,o.jsx)(n.code,{children:"icon: 'U+1F680'"})," or ",(0,o.jsx)(n.code,{children:"icon: \ud83d\ude80"})]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"selfhst-icons",children:"selfh.st Icons"}),"\n",(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.a,{href:"https://selfh.st/",children:"selfh.st"})," project provides a set of icons, originally for self-hosted services, but now expanded to include a wide variety of services. These icons can be used by specifying the icon name (without extension and with all spaces replaced with -) preceded by ",(0,o.jsx)(n.code,{children:"sh-"}),". See ",(0,o.jsx)(n.a,{href:"https://selfh.st/icons/",children:"https://selfh.st/icons/"})," for a full list of all available icons. For example, the Home Assistant icon is ",(0,o.jsx)(n.code,{children:"sh-home-assistant"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["Note: These icons are fetched from the jsdelivr CDN, so if you require offline access, the ",(0,o.jsx)(n.a,{href:"#local-icons",children:"Local Icons"})," method may be a better option for you."]}),"\n ",(0,o.jsx)(n.img,{width:"580",src:"https://i.ibb.co/pfy09LH/Screenshot-from-2025-01-08-22-04-21.png"}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"home-lab-icons",children:"Home-Lab Icons"}),"\n",(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.a,{href:"https://github.com/homarr-labs/dashboard-icons",children:"dashboard-icons"})," repo by ",(0,o.jsx)(n.a,{href:"https://github.com/homarr-labs",children:"Homarr Labs"})," provides a comprehensive collection of high-quality icons for commonly self-hosted services. Dashy natively supports these icons, and you can use them just by specifying the icon name (without extension) preceded by ",(0,o.jsx)(n.code,{children:"hl-"}),". SVG is preferred where available, with an automatic fallback to PNG for icons that aren't published as SVG. See ",(0,o.jsx)(n.a,{href:"https://github.com/homarr-labs/dashboard-icons/tree/main/svg",children:"here"})," for a full list of all available icons. Note that these are fetched and cached straight from jsDelivr, so if you require offline access, the ",(0,o.jsx)(n.a,{href:"#local-icons",children:"Local Icons"})," method may be a better option for you."]}),"\n",(0,o.jsx)(n.p,{children:"For example:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-yaml",children:"sections:\n- name: Home Lab Icons Example\n items:\n - title: AdGuard Home\n icon: hl-adguardhome\n - title: Long Horn\n icon: hl-longhorn\n - title: Nagios\n icon: hl-nagios\n - title: Whoogle Search\n icon: hl-whooglesearch\n"})}),"\n ",(0,o.jsx)(n.img,{width:"580",src:"https://i.ibb.co/PQzYHmD/homelab-icons-2.png"}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"material-design-icons",children:"Material Design Icons"}),"\n",(0,o.jsxs)(n.p,{children:["Dashy also supports 5000+ ",(0,o.jsx)(n.a,{href:"https://github.com/Templarian/MaterialDesign",children:"material-design-icons"}),". To use these, first find the name/ slug for your icon ",(0,o.jsx)(n.a,{href:"https://dev.materialdesignicons.com/icons",children:"here"}),", and then prefix is with ",(0,o.jsx)(n.code,{children:"mdi-"}),"."]}),"\n",(0,o.jsx)(n.p,{children:"For example:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-yaml",children:"sections:\n- name: Material Design Icons Example\n items:\n - title: Alien Icon\n icon: mdi-alien\n - title: Fire Icon\n icon: mdi-fire\n - title: Dino Icon\n icon: mdi-google-downasaur\n"})}),"\n ",(0,o.jsx)(n.img,{width:"500",src:"https://i.ibb.co/fC9B4mq/icons-mdi-example.png"}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"icons-by-url",children:"Icons by URL"}),"\n",(0,o.jsxs)(n.p,{children:["You can also set an icon by passing in a valid URL pointing to the icons location. For example ",(0,o.jsx)(n.code,{children:"icon: https://i.ibb.co/710B3Yc/space-invader-x256.png"}),", this can be in .png, .jpg or .svg format, and hosted anywhere (local or remote) - so long as it's accessible from where you are hosting Dashy. The icon will be automatically scaled to fit, however loading in a lot of large icons may have a negative impact on performance, especially if you visit Dashy from new devices often."]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"local-icons",children:"Local Icons"}),"\n",(0,o.jsxs)(n.p,{children:["You may also want to store your icons locally, bundled within Dashy so that there is no reliance on outside services. This can be done by putting the icons within Dashy's ",(0,o.jsx)(n.code,{children:"./user-data/item-icons/"})," directory. If you are using Docker, then the easiest option is to map a volume from your host system, for example: ",(0,o.jsx)(n.code,{children:"-v /local/image/directory:/app/user-data/item-icons/"}),". To reference an icon stored locally, just specify it's name and extension. For example, if my icon was stored in ",(0,o.jsx)(n.code,{children:"/app/user-data/item-icons/maltrail.png"}),", then I would just set ",(0,o.jsx)(n.code,{children:"icon: maltrail.png"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["You can also use sub-folders within the ",(0,o.jsx)(n.code,{children:"item-icons"})," directory to keep things organized. You would then specify an icon with it's folder name slash image name. For example: ",(0,o.jsx)(n.code,{children:"networking/monit.png"})]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"default-icon",children:"Default Icon"}),"\n",(0,o.jsxs)(n.p,{children:["If you'd like to set a default icon, to be applied to any items which don't have an icon already set, then this can be done under ",(0,o.jsx)(n.code,{children:"appConfig.defaultIcon"}),"."]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"no-icon",children:"No Icon"}),"\n",(0,o.jsxs)(n.p,{children:["If you don't wish for a given item or section to have an icon, just leave out the ",(0,o.jsx)(n.code,{children:"icon"})," attribute."]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"icon-collections-and-resources",children:"Icon Collections and Resources"}),"\n",(0,o.jsxs)(n.p,{children:["The following websites provide good-quality, free icon sets. To use any of these icons, either copy the link to the raw icon (it should end in ",(0,o.jsx)(n.code,{children:".svg"})," or ",(0,o.jsx)(n.code,{children:".png"}),") and paste it as your ",(0,o.jsx)(n.code,{children:"icon"}),", or download and save the icons in ",(0,o.jsx)(n.code,{children:"/user-data/item-icons"})," / mapped Docker volume. Full credit to the authors, please see the licenses for each service for usage and copyright information."]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://thehomelab.wiki/books/helpful-tools-resources/page/icons-for-self-hosted-dashboards",children:"Icons for Self-Hosted Apps"})," - 350+ high-quality icons for commonly self-hosted services"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://svgbox.net/iconsets/",children:"SVG Box"})," - Cryptocurrency, social media apps and flag icons"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://simpleicons.org/",children:"Simple Icons"})," - Free SVG brand icons, with easy API access"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://github.com/google/material-design-icons/",children:"Material Design Icons"})," - Hundreds of Open source PNG + SVG icons by Google"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://icons8.com/icons",children:"Icons8"})," - Thousands of icons, all with free versions at 64x64"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://www.flaticon.com/",children:"Flat Icon"})," - Wide variety of icon sets, most of which are free to use"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"https://www.svgrepo.com/",children:"SVG Repo"})," - 300,000+ Vector Icons"]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["If you are a student, then you can get free access to premium icons on ",(0,o.jsx)(n.a,{href:"https://education.github.com/pack/redeem/iconscout-student",children:"Icon Scout"})," or ",(0,o.jsx)(n.a,{href:"https://icons8.com/github-students",children:"Icons8"})," using the ",(0,o.jsx)(n.a,{href:"https://education.github.com/pack",children:"GitHub Student Pack"}),"."]}),"\n",(0,o.jsx)(n.hr,{}),"\n",(0,o.jsx)(n.h2,{id:"notes",children:"Notes"}),"\n",(0,o.jsx)(n.p,{children:"If you are using icons from an external source, these will be fetched on initial page load automatically, if and when needed. But combining icons from multiple services may have a negative impact on performance."}),"\n",(0,o.jsx)(n.p,{children:"You can improve load speeds, by downloading your required icons, and serving them locally. Scaling icons to the minimum required dimensions (e.g. 128x128 or 64x64) will also greatly improve application load times."}),"\n",(0,o.jsx)(n.p,{children:"For icons from external sources, please see the Privacy Policies and Licenses for that provider."})]})}function d(e={}){const{wrapper:n}={...(0,c.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>r});var i=s(96540);const o={},c=i.createContext(o);function t(e){const n=i.useContext(c);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:t(e.components),i.createElement(c.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/052b4c8f.6ce48445.js b/assets/js/052b4c8f.92ca9ad0.js similarity index 99% rename from assets/js/052b4c8f.6ce48445.js rename to assets/js/052b4c8f.92ca9ad0.js index bdbd60d3..88fd4357 100644 --- a/assets/js/052b4c8f.6ce48445.js +++ b/assets/js/052b4c8f.92ca9ad0.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[4319],{63425(e,t,n){n.r(t),n.d(t,{assets:()=>a,contentTitle:()=>h,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>d});const s=JSON.parse('{"id":"authentication/header-auth","title":"Header Authentication","description":"Header authentication lets Dashy trust a reverse proxy in front of it to handle login. The proxy authenticates the user, then passes their username to Dashy in an HTTP header. Dashy reads that header and signs them in automatically, so there\'s no separate Dashy login page.","source":"@site/docs/authentication/header-auth.md","sourceDirName":"authentication","slug":"/authentication/header-auth","permalink":"/docs/authentication/header-auth","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/header-auth.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Cloudflare Tunnel","permalink":"/docs/authentication/cloudflare-tunnel"},"next":{"title":"Keycloak","permalink":"/docs/authentication/keycloak"}}');var r=n(74848),i=n(28453);const o={},h="Header Authentication",a={},d=[{value:"Contents",id:"contents",level:3},{value:"Configure Dashy",id:"configure-dashy",level:2},{value:"Configure your proxy",id:"configure-your-proxy",level:2},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"401 "Unauthorized - not from trusted proxy"",id:"401-unauthorized---not-from-trusted-proxy",level:4},{value:"401 "Unauthorized - missing user header"",id:"401-unauthorized---missing-user-header",level:4},{value:""User '...' from upstream proxy was not found in conf.yml"",id:"user--from-upstream-proxy-was-not-found-in-confyml",level:4},{value:"Logged in but can't save the config",id:"logged-in-but-cant-save-the-config",level:4},{value:"Logging out just logs me back in",id:"logging-out-just-logs-me-back-in",level:4},{value:"Security notes",id:"security-notes",level:2},{value:"How it works",id:"how-it-works",level:2}];function l(e){const t={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.header,{children:(0,r.jsx)(t.h1,{id:"header-authentication",children:"Header Authentication"})}),"\n",(0,r.jsx)(t.p,{children:"Header authentication lets Dashy trust a reverse proxy in front of it to handle login. The proxy authenticates the user, then passes their username to Dashy in an HTTP header. Dashy reads that header and signs them in automatically, so there's no separate Dashy login page."}),"\n",(0,r.jsxs)(t.p,{children:["Use this when you already run something like ",(0,r.jsx)(t.a,{href:"https://www.authelia.com/",children:"Authelia"}),", ",(0,r.jsx)(t.a,{href:"https://goauthentik.io/",children:"Authentik"}),", Traefik's ",(0,r.jsx)(t.code,{children:"forwardAuth"}),", Caddy's ",(0,r.jsx)(t.code,{children:"forward_auth"}),", or nginx's ",(0,r.jsx)(t.code,{children:"auth_request"})," in front of your services, and you want Dashy to pick up the same session."]}),"\n",(0,r.jsx)(t.h3,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:(0,r.jsx)(t.a,{href:"#configure-dashy",children:"Configure Dashy"})}),"\n",(0,r.jsx)(t.li,{children:(0,r.jsx)(t.a,{href:"#configure-your-proxy",children:"Configure your proxy"})}),"\n",(0,r.jsx)(t.li,{children:(0,r.jsx)(t.a,{href:"#troubleshooting",children:"Troubleshooting"})}),"\n",(0,r.jsx)(t.li,{children:(0,r.jsx)(t.a,{href:"#security-notes",children:"Security notes"})}),"\n",(0,r.jsx)(t.li,{children:(0,r.jsx)(t.a,{href:"#how-it-works",children:"How it works"})}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"configure-dashy",children:"Configure Dashy"}),"\n",(0,r.jsxs)(t.p,{children:["In ",(0,r.jsx)(t.code,{children:"/user-data/conf.yml"}),", set the ",(0,r.jsx)(t.code,{children:"auth"})," block under ",(0,r.jsx)(t.code,{children:"appConfig"}),":"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-yaml",children:"appConfig:\n auth:\n enableHeaderAuth: true\n users:\n - user: alice\n hash: 0a7b1d4c2e... # SHA-256 hash, see below\n type: admin\n - user: bob\n hash: 3f8e2b1a9d...\n type: normal\n headerAuth:\n userHeader: Remote-User\n proxyWhitelist:\n - 172.18.0.2\n"})}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"enableHeaderAuth"})," - turns the mode on"]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"userHeader"})," - the header holding the username. Defaults to ",(0,r.jsx)(t.code,{children:"Remote-User"}),". Authelia sends ",(0,r.jsx)(t.code,{children:"Remote-User"}),", Authentik sends ",(0,r.jsx)(t.code,{children:"X-authentik-username"}),"; use whatever yours forwards. The name is case-insensitive"]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"proxyWhitelist"})," - the IP(s) Dashy will accept the header from. Anything not on this list is rejected, which is what stops a client from setting the header itself. Required in practice: if it's empty, nothing gets through"]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"users"})," - the people allowed in. The forwarded username is matched (case-insensitive) against this list to find their ",(0,r.jsx)(t.code,{children:"type"})," (",(0,r.jsx)(t.code,{children:"admin"})," or ",(0,r.jsx)(t.code,{children:"normal"}),"). They still need an entry here even though the proxy did the actual auth"]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"hash"})," is a SHA-256 of any string. Nobody types it, since the proxy already logged them in, but Dashy uses it to derive the session token it keeps in the browser, so each user needs one. Generate it the same way as for ",(0,r.jsx)(t.a,{href:"/docs/built-in#generating-a-password-hash",children:"built-in auth"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"configure-your-proxy",children:"Configure your proxy"}),"\n",(0,r.jsx)(t.p,{children:"Two things the proxy has to do:"}),"\n",(0,r.jsxs)(t.ol,{children:["\n",(0,r.jsxs)(t.li,{children:["Forward the username header on every request to Dashy (e.g. ",(0,r.jsx)(t.code,{children:"Remote-User: alice"}),"). On most forward-auth setups this is a one-line setting"]}),"\n",(0,r.jsxs)(t.li,{children:["Connect to Dashy from an IP that's in ",(0,r.jsx)(t.code,{children:"proxyWhitelist"})]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["The whitelist is the security boundary, so Dashy must only be reachable through the proxy. If someone can hit Dashy directly and their IP happens to be whitelisted, they can forge the header. See ",(0,r.jsx)(t.a,{href:"#security-notes",children:"Security notes"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsx)(t.h4,{id:"401-unauthorized---not-from-trusted-proxy",children:'401 "Unauthorized - not from trusted proxy"'}),"\n",(0,r.jsxs)(t.p,{children:["The request reached Dashy from an IP that isn't in ",(0,r.jsx)(t.code,{children:"proxyWhitelist"}),". The check is on the direct connection IP, so with Docker that's the proxy container's address on the shared network, not the host or the client's IP. Find it with ",(0,r.jsx)(t.code,{children:"docker inspect "})," (or read the rejected IP from Dashy's logs) and add it."]}),"\n",(0,r.jsx)(t.h4,{id:"401-unauthorized---missing-user-header",children:'401 "Unauthorized - missing user header"'}),"\n",(0,r.jsxs)(t.p,{children:["The request came from a trusted IP but had no username header. Either the proxy isn't forwarding it, or ",(0,r.jsx)(t.code,{children:"userHeader"})," doesn't match the header name the proxy actually sends. Check the exact header in your proxy config."]}),"\n",(0,r.jsx)(t.h4,{id:"user--from-upstream-proxy-was-not-found-in-confyml",children:"\"User '...' from upstream proxy was not found in conf.yml\""}),"\n",(0,r.jsxs)(t.p,{children:["The proxy sent a username with no matching entry in ",(0,r.jsx)(t.code,{children:"auth.users"}),". Add a user whose ",(0,r.jsx)(t.code,{children:"user"})," matches it (case doesn't matter)."]}),"\n",(0,r.jsx)(t.h4,{id:"logged-in-but-cant-save-the-config",children:"Logged in but can't save the config"}),"\n",(0,r.jsxs)(t.p,{children:["That user's ",(0,r.jsx)(t.code,{children:"type"})," isn't ",(0,r.jsx)(t.code,{children:"admin"}),". Saving is admin-only, so set ",(0,r.jsx)(t.code,{children:"type: admin"})," on their entry."]}),"\n",(0,r.jsx)(t.h4,{id:"logging-out-just-logs-me-back-in",children:"Logging out just logs me back in"}),"\n",(0,r.jsx)(t.p,{children:"Expected. Logout clears Dashy's cookie, but you're still signed in at the proxy, so the next page load re-authenticates from the header. To fully sign out, log out at the proxy."}),"\n",(0,r.jsx)(t.h2,{id:"security-notes",children:"Security notes"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"Header auth is only as strong as the proxy in front of it. Dashy trusts any whitelisted IP that sends the header, so the whole model depends on Dashy being unreachable except through the proxy. Lock that down at the network or firewall level"}),"\n",(0,r.jsxs)(t.li,{children:["Keep ",(0,r.jsx)(t.code,{children:"proxyWhitelist"})," tight: just the proxy's real connecting IP(s), nothing broader"]}),"\n",(0,r.jsxs)(t.li,{children:["The username and role come from ",(0,r.jsx)(t.code,{children:"conf.yml"}),". A user the proxy authenticates but that you haven't listed is refused, so removing someone from ",(0,r.jsx)(t.code,{children:"auth.users"})," blocks them even if the proxy still lets them through"]}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"how-it-works",children:"How it works"}),"\n",(0,r.jsxs)(t.ol,{children:["\n",(0,r.jsx)(t.li,{children:"The user hits Dashy through the proxy. The proxy authenticates them and adds the username header"}),"\n",(0,r.jsxs)(t.li,{children:["On load, Dashy's frontend calls ",(0,r.jsx)(t.code,{children:"/get-user"}),". The server checks the request IP against ",(0,r.jsx)(t.code,{children:"proxyWhitelist"}),", and if it's trusted, reads the username from ",(0,r.jsx)(t.code,{children:"userHeader"})," and returns it"]}),"\n",(0,r.jsxs)(t.li,{children:["The frontend looks that username up in ",(0,r.jsx)(t.code,{children:"auth.users"}),", derives a session token from the user and hash, sets the auth cookie, and stores the username"]}),"\n",(0,r.jsxs)(t.li,{children:["From there it's normal Dashy auth: ",(0,r.jsx)(t.code,{children:"isLoggedIn"}),", the admin check, and the per-page, section and item visibility rules all work as usual"]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["Server-side, the same proxy-whitelist middleware guards the config and API routes, so a request from an untrusted IP is rejected before it reaches anything. Writes (saving config) additionally require the matched user to be ",(0,r.jsx)(t.code,{children:"type: admin"}),"."]}),"\n",(0,r.jsxs)(t.p,{children:["The relevant code is ",(0,r.jsx)(t.code,{children:"src/utils/auth/HeaderAuth.js"})," on the frontend, and ",(0,r.jsx)(t.code,{children:"services/app.js"})," with ",(0,r.jsx)(t.code,{children:"services/get-user.js"})," on the server."]})]})}function c(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},28453(e,t,n){n.d(t,{R:()=>o,x:()=>h});var s=n(96540);const r={},i=s.createContext(r);function o(e){const t=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function h(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),s.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[4319],{63425(e,t,n){n.r(t),n.d(t,{assets:()=>a,contentTitle:()=>h,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>d});const s=JSON.parse('{"id":"authentication/header-auth","title":"Header Authentication","description":"Header authentication lets Dashy trust a reverse proxy in front of it to handle login. The proxy authenticates the user, then passes their username to Dashy in an HTTP header. Dashy reads that header and signs them in automatically, so there\'s no separate Dashy login page.","source":"@site/docs/authentication/header-auth.md","sourceDirName":"authentication","slug":"/authentication/header-auth","permalink":"/docs/authentication/header-auth","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/header-auth.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Cloudflare Tunnel","permalink":"/docs/authentication/cloudflare-tunnel"},"next":{"title":"Keycloak","permalink":"/docs/authentication/keycloak"}}');var r=n(74848),i=n(28453);const o={},h="Header Authentication",a={},d=[{value:"Contents",id:"contents",level:3},{value:"Configure Dashy",id:"configure-dashy",level:2},{value:"Configure your proxy",id:"configure-your-proxy",level:2},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"401 "Unauthorized - not from trusted proxy"",id:"401-unauthorized---not-from-trusted-proxy",level:4},{value:"401 "Unauthorized - missing user header"",id:"401-unauthorized---missing-user-header",level:4},{value:""User '...' from upstream proxy was not found in conf.yml"",id:"user--from-upstream-proxy-was-not-found-in-confyml",level:4},{value:"Logged in but can't save the config",id:"logged-in-but-cant-save-the-config",level:4},{value:"Logging out just logs me back in",id:"logging-out-just-logs-me-back-in",level:4},{value:"Security notes",id:"security-notes",level:2},{value:"How it works",id:"how-it-works",level:2}];function l(e){const t={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.header,{children:(0,r.jsx)(t.h1,{id:"header-authentication",children:"Header Authentication"})}),"\n",(0,r.jsx)(t.p,{children:"Header authentication lets Dashy trust a reverse proxy in front of it to handle login. The proxy authenticates the user, then passes their username to Dashy in an HTTP header. Dashy reads that header and signs them in automatically, so there's no separate Dashy login page."}),"\n",(0,r.jsxs)(t.p,{children:["Use this when you already run something like ",(0,r.jsx)(t.a,{href:"https://www.authelia.com/",children:"Authelia"}),", ",(0,r.jsx)(t.a,{href:"https://goauthentik.io/",children:"Authentik"}),", Traefik's ",(0,r.jsx)(t.code,{children:"forwardAuth"}),", Caddy's ",(0,r.jsx)(t.code,{children:"forward_auth"}),", or nginx's ",(0,r.jsx)(t.code,{children:"auth_request"})," in front of your services, and you want Dashy to pick up the same session."]}),"\n",(0,r.jsx)(t.h3,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:(0,r.jsx)(t.a,{href:"#configure-dashy",children:"Configure Dashy"})}),"\n",(0,r.jsx)(t.li,{children:(0,r.jsx)(t.a,{href:"#configure-your-proxy",children:"Configure your proxy"})}),"\n",(0,r.jsx)(t.li,{children:(0,r.jsx)(t.a,{href:"#troubleshooting",children:"Troubleshooting"})}),"\n",(0,r.jsx)(t.li,{children:(0,r.jsx)(t.a,{href:"#security-notes",children:"Security notes"})}),"\n",(0,r.jsx)(t.li,{children:(0,r.jsx)(t.a,{href:"#how-it-works",children:"How it works"})}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"configure-dashy",children:"Configure Dashy"}),"\n",(0,r.jsxs)(t.p,{children:["In ",(0,r.jsx)(t.code,{children:"/user-data/conf.yml"}),", set the ",(0,r.jsx)(t.code,{children:"auth"})," block under ",(0,r.jsx)(t.code,{children:"appConfig"}),":"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-yaml",children:"appConfig:\n auth:\n enableHeaderAuth: true\n users:\n - user: alice\n hash: 0a7b1d4c2e... # SHA-256 hash, see below\n type: admin\n - user: bob\n hash: 3f8e2b1a9d...\n type: normal\n headerAuth:\n userHeader: Remote-User\n proxyWhitelist:\n - 172.18.0.2\n"})}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"enableHeaderAuth"})," - turns the mode on"]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"userHeader"})," - the header holding the username. Defaults to ",(0,r.jsx)(t.code,{children:"Remote-User"}),". Authelia sends ",(0,r.jsx)(t.code,{children:"Remote-User"}),", Authentik sends ",(0,r.jsx)(t.code,{children:"X-authentik-username"}),"; use whatever yours forwards. The name is case-insensitive"]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"proxyWhitelist"})," - the IP(s) Dashy will accept the header from. Anything not on this list is rejected, which is what stops a client from setting the header itself. Required in practice: if it's empty, nothing gets through"]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"users"})," - the people allowed in. The forwarded username is matched (case-insensitive) against this list to find their ",(0,r.jsx)(t.code,{children:"type"})," (",(0,r.jsx)(t.code,{children:"admin"})," or ",(0,r.jsx)(t.code,{children:"normal"}),"). They still need an entry here even though the proxy did the actual auth"]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"hash"})," is a SHA-256 of any string. Nobody types it, since the proxy already logged them in, but Dashy uses it to derive the session token it keeps in the browser, so each user needs one. Generate it the same way as for ",(0,r.jsx)(t.a,{href:"/docs/built-in#generating-a-password-hash",children:"built-in auth"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"configure-your-proxy",children:"Configure your proxy"}),"\n",(0,r.jsx)(t.p,{children:"Two things the proxy has to do:"}),"\n",(0,r.jsxs)(t.ol,{children:["\n",(0,r.jsxs)(t.li,{children:["Forward the username header on every request to Dashy (e.g. ",(0,r.jsx)(t.code,{children:"Remote-User: alice"}),"). On most forward-auth setups this is a one-line setting"]}),"\n",(0,r.jsxs)(t.li,{children:["Connect to Dashy from an IP that's in ",(0,r.jsx)(t.code,{children:"proxyWhitelist"})]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["The whitelist is the security boundary, so Dashy must only be reachable through the proxy. If someone can hit Dashy directly and their IP happens to be whitelisted, they can forge the header. See ",(0,r.jsx)(t.a,{href:"#security-notes",children:"Security notes"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsx)(t.h4,{id:"401-unauthorized---not-from-trusted-proxy",children:'401 "Unauthorized - not from trusted proxy"'}),"\n",(0,r.jsxs)(t.p,{children:["The request reached Dashy from an IP that isn't in ",(0,r.jsx)(t.code,{children:"proxyWhitelist"}),". The check is on the direct connection IP, so with Docker that's the proxy container's address on the shared network, not the host or the client's IP. Find it with ",(0,r.jsx)(t.code,{children:"docker inspect "})," (or read the rejected IP from Dashy's logs) and add it."]}),"\n",(0,r.jsx)(t.h4,{id:"401-unauthorized---missing-user-header",children:'401 "Unauthorized - missing user header"'}),"\n",(0,r.jsxs)(t.p,{children:["The request came from a trusted IP but had no username header. Either the proxy isn't forwarding it, or ",(0,r.jsx)(t.code,{children:"userHeader"})," doesn't match the header name the proxy actually sends. Check the exact header in your proxy config."]}),"\n",(0,r.jsx)(t.h4,{id:"user--from-upstream-proxy-was-not-found-in-confyml",children:"\"User '...' from upstream proxy was not found in conf.yml\""}),"\n",(0,r.jsxs)(t.p,{children:["The proxy sent a username with no matching entry in ",(0,r.jsx)(t.code,{children:"auth.users"}),". Add a user whose ",(0,r.jsx)(t.code,{children:"user"})," matches it (case doesn't matter)."]}),"\n",(0,r.jsx)(t.h4,{id:"logged-in-but-cant-save-the-config",children:"Logged in but can't save the config"}),"\n",(0,r.jsxs)(t.p,{children:["That user's ",(0,r.jsx)(t.code,{children:"type"})," isn't ",(0,r.jsx)(t.code,{children:"admin"}),". Saving is admin-only, so set ",(0,r.jsx)(t.code,{children:"type: admin"})," on their entry."]}),"\n",(0,r.jsx)(t.h4,{id:"logging-out-just-logs-me-back-in",children:"Logging out just logs me back in"}),"\n",(0,r.jsx)(t.p,{children:"Expected. Logout clears Dashy's cookie, but you're still signed in at the proxy, so the next page load re-authenticates from the header. To fully sign out, log out at the proxy."}),"\n",(0,r.jsx)(t.h2,{id:"security-notes",children:"Security notes"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"Header auth is only as strong as the proxy in front of it. Dashy trusts any whitelisted IP that sends the header, so the whole model depends on Dashy being unreachable except through the proxy. Lock that down at the network or firewall level"}),"\n",(0,r.jsxs)(t.li,{children:["Keep ",(0,r.jsx)(t.code,{children:"proxyWhitelist"})," tight: just the proxy's real connecting IP(s), nothing broader"]}),"\n",(0,r.jsxs)(t.li,{children:["The username and role come from ",(0,r.jsx)(t.code,{children:"conf.yml"}),". A user the proxy authenticates but that you haven't listed is refused, so removing someone from ",(0,r.jsx)(t.code,{children:"auth.users"})," blocks them even if the proxy still lets them through"]}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"how-it-works",children:"How it works"}),"\n",(0,r.jsxs)(t.ol,{children:["\n",(0,r.jsx)(t.li,{children:"The user hits Dashy through the proxy. The proxy authenticates them and adds the username header"}),"\n",(0,r.jsxs)(t.li,{children:["On load, Dashy's frontend calls ",(0,r.jsx)(t.code,{children:"/get-user"}),". The server checks the request IP against ",(0,r.jsx)(t.code,{children:"proxyWhitelist"}),", and if it's trusted, reads the username from ",(0,r.jsx)(t.code,{children:"userHeader"})," and returns it"]}),"\n",(0,r.jsxs)(t.li,{children:["The frontend looks that username up in ",(0,r.jsx)(t.code,{children:"auth.users"}),", derives a session token from the user and hash, sets the auth cookie, and stores the username"]}),"\n",(0,r.jsxs)(t.li,{children:["From there it's normal Dashy auth: ",(0,r.jsx)(t.code,{children:"isLoggedIn"}),", the admin check, and the per-page, section and item visibility rules all work as usual"]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["Server-side, the same proxy-whitelist middleware guards the config and API routes, so a request from an untrusted IP is rejected before it reaches anything. Writes (saving config) additionally require the matched user to be ",(0,r.jsx)(t.code,{children:"type: admin"}),"."]}),"\n",(0,r.jsxs)(t.p,{children:["The relevant code is ",(0,r.jsx)(t.code,{children:"src/utils/auth/HeaderAuth.js"})," on the frontend, and ",(0,r.jsx)(t.code,{children:"services/app.js"})," with ",(0,r.jsx)(t.code,{children:"services/get-user.js"})," on the server."]})]})}function c(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},28453(e,t,n){n.d(t,{R:()=>o,x:()=>h});var s=n(96540);const r={},i=s.createContext(r);function o(e){const t=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function h(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),s.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/0a44bce0.ba1aa4e6.js b/assets/js/0a44bce0.e0a74859.js similarity index 98% rename from assets/js/0a44bce0.ba1aa4e6.js rename to assets/js/0a44bce0.e0a74859.js index cd85cecd..ef9bb54a 100644 --- a/assets/js/0a44bce0.ba1aa4e6.js +++ b/assets/js/0a44bce0.e0a74859.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[9372],{68810(e,n,s){s.r(n),s.d(n,{assets:()=>d,contentTitle:()=>l,default:()=>h,frontMatter:()=>i,metadata:()=>t,toc:()=>c});const t=JSON.parse('{"id":"deployment/bare-metal","title":"Bare Metal","description":"Build from Source","source":"@site/docs/deployment/bare-metal.md","sourceDirName":"deployment","slug":"/deployment/bare-metal","permalink":"/docs/deployment/bare-metal","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/deployment/bare-metal.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{}}');var a=s(74848),r=s(28453);const i={},l="Bare Metal",d={},c=[{value:"Build from Source",id:"build-from-source",level:2},{value:"Pre-Built Release",id:"pre-built-release",level:2},{value:"Systemd Service",id:"systemd-service",level:2},{value:"Checksum Verification",id:"checksum-verification",level:2},{value:"Provenance Attestation",id:"provenance-attestation",level:2},{value:"Install Script",id:"install-script",level:2}];function o(e){const n={a:"a",br:"br",code:"code",details:"details",h1:"h1",h2:"h2",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",summary:"summary",ul:"ul",...(0,r.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(n.header,{children:(0,a.jsx)(n.h1,{id:"bare-metal",children:"Bare Metal"})}),"\n",(0,a.jsx)(n.h2,{id:"build-from-source",children:"Build from Source"}),"\n",(0,a.jsxs)(n.p,{children:["Dashy can be compiled and run directly on your machine.",(0,a.jsx)(n.br,{}),"\nFor this, you will need both ",(0,a.jsx)(n.a,{href:"https://git-scm.com/downloads",children:"git"})," and ",(0,a.jsx)(n.a,{href:"https://nodejs.org/",children:"Node.js"})," (v20 or newer) installed."]}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["Get Code: ",(0,a.jsx)(n.code,{children:"git clone https://github.com/Lissy93/dashy.git"})," and ",(0,a.jsx)(n.code,{children:"cd dashy"})]}),"\n",(0,a.jsxs)(n.li,{children:["Configuration: Fill in your settings in ",(0,a.jsx)(n.code,{children:"./user-data/conf.yml"})]}),"\n",(0,a.jsxs)(n.li,{children:["Install dependencies: ",(0,a.jsx)(n.code,{children:"yarn"})]}),"\n",(0,a.jsxs)(n.li,{children:["Build: ",(0,a.jsx)(n.code,{children:"yarn build"})]}),"\n",(0,a.jsxs)(n.li,{children:["Run: ",(0,a.jsx)(n.code,{children:"yarn start"})]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"For example, the following steps can be used for Ubuntu/Debian based distros:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"# Install prerequisites\nsudo apt update\nsudo apt install -y curl git\n\n# Install Node.js (v24 LTS) from NodeSource, and enable yarn\ncurl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -\nsudo apt install -y nodejs\nsudo corepack enable\n\n# Get the code\ngit clone https://github.com/Lissy93/dashy.git\ncd dashy\n\n# Install dependencies and build\nyarn\nyarn build\n\n# Run it (serves on http://localhost:4000 by default, change with PORT env var)\nyarn start\n\n# Put all your assets (icons, styles, scripts, fonts, pages, etc) and configs in ./user-data\n# Then, get started with editing the main config\nnano user-data/conf.yml\n"})}),"\n",(0,a.jsx)(n.p,{children:"Notes:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["If you're making changes to Dashy's codebase, you can run ",(0,a.jsx)(n.code,{children:"yarn dev"})," to start the dev server with hot reloading. See the ",(0,a.jsx)(n.a,{href:"/docs/developing",children:"Developing"})," docs for more info."]}),"\n",(0,a.jsxs)(n.li,{children:["To keep Dashy running in the background, either use ",(0,a.jsx)(n.code,{children:"yarn pm2-start"})," (instead of yarn start). Or, with systemd, see the ",(0,a.jsx)(n.a,{href:"#systemd-service",children:"systemd service"})," example below"]}),"\n"]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"pre-built-release",children:"Pre-Built Release"}),"\n",(0,a.jsxs)(n.p,{children:["We publish GitHub releases for every major and minor (not patch) version of Dashy.",(0,a.jsx)(n.br,{}),"\nEach release includes the pre-compiled app as ",(0,a.jsx)(n.code,{children:"dashy-[version].tar.gz"})," (plus a SHA256 checksum and SLSA build-provenance attestation)."]}),"\n",(0,a.jsx)(n.p,{children:"This is useful if you want to run Dashy on bare metal, without the need to compile from source."}),"\n",(0,a.jsxs)(n.p,{children:["You'll need ",(0,a.jsx)(n.a,{href:"https://nodejs.org/",children:"Node.js"})," (v20 or newer), ",(0,a.jsx)(n.code,{children:"curl"}),", ",(0,a.jsx)(n.code,{children:"tar"}),", and ",(0,a.jsx)(n.code,{children:"yarn"})," enabled through Corepack."]}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["Pick a release from ",(0,a.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/releases",children:"GitHub Releases"})]}),"\n",(0,a.jsxs)(n.li,{children:["Download ",(0,a.jsx)(n.code,{children:"dashy-[version].tar.gz"})]}),"\n",(0,a.jsxs)(n.li,{children:["Extract it somewhere permanent, such as ",(0,a.jsx)(n.code,{children:"/opt/dashy"})]}),"\n",(0,a.jsxs)(n.li,{children:["Run ",(0,a.jsx)(n.code,{children:"yarn install --production --frozen-lockfile"})]}),"\n",(0,a.jsxs)(n.li,{children:["Edit ",(0,a.jsx)(n.code,{children:"user-data/conf.yml"})]}),"\n",(0,a.jsxs)(n.li,{children:["Start it with ",(0,a.jsx)(n.code,{children:"node server"})]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"For example (replace 4.3.0 with whichever version you want):"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"curl -LO https://github.com/Lissy93/dashy/releases/download/4.3.0/dashy-4.3.0.tar.gz\nmkdir -p dashy\ntar -xzf dashy-4.3.0.tar.gz -C dashy\ncd dashy\ncorepack enable\nyarn install --production --frozen-lockfile\nnode server\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Dashy will run on ",(0,a.jsx)(n.code,{children:"http://localhost:4000"})," by default. Change this with the ",(0,a.jsx)(n.code,{children:"PORT"})," env var."]}),"\n",(0,a.jsxs)(n.p,{children:["You can now start configuring by editing ",(0,a.jsx)(n.code,{children:"user-data/conf.yml"})," and put any assets you want within user-data."]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"systemd-service",children:"Systemd Service"}),"\n",(0,a.jsx)(n.p,{children:"If you're running Dashy on a Linux server, you can use systemd to start it at boot and restart it if it crashes."}),"\n",(0,a.jsxs)(n.p,{children:["This example assumes Dashy is installed in ",(0,a.jsx)(n.code,{children:"/opt/dashy"}),", and runs it as a dedicated ",(0,a.jsx)(n.code,{children:"dashy"})," user, so adjust it for your usecase."]}),"\n",(0,a.jsx)(n.p,{children:"Run these commands as root:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"useradd --system --home /opt/dashy --shell /usr/sbin/nologin dashy\nchown -R dashy:dashy /opt/dashy\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Create ",(0,a.jsx)(n.code,{children:"/etc/systemd/system/dashy.service"}),":"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-ini",children:"[Unit]\nDescription=Dashy\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=dashy\nWorkingDirectory=/opt/dashy\nEnvironment=NODE_ENV=production\nEnvironment=PORT=4000\nExecStart=/usr/bin/env node server\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\n"})}),"\n",(0,a.jsx)(n.p,{children:"Then enable and start it:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"systemctl daemon-reload\nsystemctl enable --now dashy\nsystemctl status dashy\n"})}),"\n",(0,a.jsxs)(n.p,{children:["If you installed Dashy somewhere else, update ",(0,a.jsx)(n.code,{children:"WorkingDirectory"}),"."]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"checksum-verification",children:"Checksum Verification"}),"\n",(0,a.jsx)(n.p,{children:"Each pre-built release includes a SHA256 checksum. This is optional, but it is a quick way to check the file downloaded correctly."}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"curl -LO https://github.com/Lissy93/dashy/releases/download/4.3.0/dashy-4.3.0.tar.gz\ncurl -LO https://github.com/Lissy93/dashy/releases/download/4.3.0/dashy-4.3.0.tar.gz.sha256\nsha256sum -c dashy-4.3.0.tar.gz.sha256\n"})}),"\n",(0,a.jsxs)(n.p,{children:["You should see ",(0,a.jsx)(n.code,{children:"dashy-4.3.0.tar.gz: OK"}),"."]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"provenance-attestation",children:"Provenance Attestation"}),"\n",(0,a.jsx)(n.p,{children:"GitHub releases also include a SLSA build-provenance attestation. This lets you verify that the release asset was built by Dashy's GitHub Actions release workflow."}),"\n",(0,a.jsxs)(n.p,{children:["Install a recent ",(0,a.jsx)(n.a,{href:"https://cli.github.com/",children:"GitHub CLI"}),", then run:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"gh attestation verify dashy-4.3.0.tar.gz --repo Lissy93/dashy\n"})}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"install-script",children:"Install Script"}),"\n",(0,a.jsxs)(n.p,{children:["The following is an example script to install or update the latest pre-built release on a Linux server with systemd.\nIt creates a ",(0,a.jsx)(n.code,{children:"dashy"})," user, installs into ",(0,a.jsx)(n.code,{children:"/opt/dashy"}),", verifies the SHA256 checksum, installs production dependencies, and starts Dashy as a service."]}),"\n",(0,a.jsxs)(n.details,{children:["\n",(0,a.jsx)(n.summary,{children:"install-dashy.sh"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:'#!/usr/bin/env bash\nset -euo pipefail\n\nREPO="Lissy93/dashy"\nINSTALL_DIR="${INSTALL_DIR:-/opt/dashy}"\nRUN_USER="${RUN_USER:-dashy}"\nPORT="${PORT:-4000}"\nVERSION="${DASHY_VERSION:-latest}"\n\nif [ "$(id -u)" -ne 0 ]; then\n echo "Please run this script as root"\n exit 1\nfi\n\nneed_cmd() {\n command -v "$1" >/dev/null 2>&1 || {\n echo "Missing required command: $1"\n exit 1\n }\n}\n\ninstall_packages() {\n if command -v apt-get >/dev/null 2>&1; then\n apt-get update\n apt-get install -y curl ca-certificates tar gzip coreutils\n elif command -v dnf >/dev/null 2>&1; then\n dnf install -y curl ca-certificates tar gzip coreutils nodejs\n elif command -v yum >/dev/null 2>&1; then\n yum install -y curl ca-certificates tar gzip coreutils nodejs\n else\n echo "Please install curl, tar, sha256sum and Node.js v20 or newer, then re-run this script."\n exit 1\n fi\n}\n\ninstall_packages\nneed_cmd curl\nneed_cmd tar\nneed_cmd sha256sum\nneed_cmd runuser\n\ninstall_node_apt() {\n curl -fsSL https://deb.nodesource.com/setup_24.x | bash -\n apt-get install -y nodejs\n}\n\nif ! command -v node >/dev/null 2>&1; then\n if command -v apt-get >/dev/null 2>&1; then\n install_node_apt\n else\n echo "Please install Node.js v20 or newer, then re-run this script."\n exit 1\n fi\nfi\n\nNODE_MAJOR="$(node -p "process.versions.node.split(\'.\')[0]")"\nif [ "$NODE_MAJOR" -lt 20 ]; then\n if command -v apt-get >/dev/null 2>&1; then\n install_node_apt\n NODE_MAJOR="$(node -p "process.versions.node.split(\'.\')[0]")"\n fi\n\n if [ "$NODE_MAJOR" -lt 20 ]; then\n echo "Node.js v20 or newer is required. Found: $(node -v)"\n exit 1\n fi\nfi\n\ncorepack enable\nneed_cmd yarn\n\ncase "$INSTALL_DIR" in\n /*) ;;\n *) echo "INSTALL_DIR must be an absolute path"; exit 1 ;;\nesac\n\nif [ "$INSTALL_DIR" = "/" ]; then\n echo "INSTALL_DIR cannot be /"\n exit 1\nfi\n\nif [ "$VERSION" = "latest" ]; then\n RELEASE_URL="$(curl -fsSLI -o /dev/null -w \'%{url_effective}\' "https://github.com/${REPO}/releases/latest")"\n VERSION="${RELEASE_URL##*/}"\nfi\nVERSION="${VERSION#v}"\n\nASSET="dashy-${VERSION}.tar.gz"\nBASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"\nTMP_DIR="$(mktemp -d)"\ntrap \'rm -rf "$TMP_DIR"\' EXIT\n\necho "Downloading Dashy ${VERSION}..."\ncurl -fL "${BASE_URL}/${ASSET}" -o "${TMP_DIR}/${ASSET}"\ncurl -fL "${BASE_URL}/${ASSET}.sha256" -o "${TMP_DIR}/${ASSET}.sha256"\n\n(\n cd "$TMP_DIR"\n sha256sum -c "${ASSET}.sha256"\n)\n\nif ! id "$RUN_USER" >/dev/null 2>&1; then\n useradd --system --home "$INSTALL_DIR" --shell /usr/sbin/nologin "$RUN_USER"\nfi\n\nif [ -d "$INSTALL_DIR/user-data" ]; then\n BACKUP_DIR="${INSTALL_DIR}.user-data.$(date +%Y%m%d%H%M%S)"\n cp -a "$INSTALL_DIR/user-data" "$BACKUP_DIR"\n echo "Backed up user-data to ${BACKUP_DIR}"\nfi\n\nNEW_DIR="${INSTALL_DIR}.new"\nOLD_DIR="${INSTALL_DIR}.old"\n\nrm -rf "$NEW_DIR" "$OLD_DIR"\nmkdir -p "$NEW_DIR"\ntar -xzf "${TMP_DIR}/${ASSET}" -C "$NEW_DIR"\n\nif [ -d "$INSTALL_DIR/user-data" ]; then\n rm -rf "${NEW_DIR}/user-data"\n cp -a "$INSTALL_DIR/user-data" "${NEW_DIR}/user-data"\nfi\n\nchown -R "$RUN_USER:$RUN_USER" "$NEW_DIR"\n\nrunuser -u "$RUN_USER" -- bash -lc "cd \'$NEW_DIR\' && yarn install --production --frozen-lockfile"\n\nif [ -d "$INSTALL_DIR" ]; then\n mv "$INSTALL_DIR" "$OLD_DIR"\nfi\n\nmv "$NEW_DIR" "$INSTALL_DIR"\nrm -rf "$OLD_DIR"\n\ncat >/etc/systemd/system/dashy.service <i,x:()=>l});var t=s(96540);const a={},r=t.createContext(a);function i(e){const n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:i(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[9372],{68810(e,n,s){s.r(n),s.d(n,{assets:()=>d,contentTitle:()=>l,default:()=>h,frontMatter:()=>i,metadata:()=>t,toc:()=>c});const t=JSON.parse('{"id":"deployment/bare-metal","title":"Bare Metal","description":"Build from Source","source":"@site/docs/deployment/bare-metal.md","sourceDirName":"deployment","slug":"/deployment/bare-metal","permalink":"/docs/deployment/bare-metal","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/deployment/bare-metal.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{}}');var a=s(74848),r=s(28453);const i={},l="Bare Metal",d={},c=[{value:"Build from Source",id:"build-from-source",level:2},{value:"Pre-Built Release",id:"pre-built-release",level:2},{value:"Systemd Service",id:"systemd-service",level:2},{value:"Checksum Verification",id:"checksum-verification",level:2},{value:"Provenance Attestation",id:"provenance-attestation",level:2},{value:"Install Script",id:"install-script",level:2}];function o(e){const n={a:"a",br:"br",code:"code",details:"details",h1:"h1",h2:"h2",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",summary:"summary",ul:"ul",...(0,r.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(n.header,{children:(0,a.jsx)(n.h1,{id:"bare-metal",children:"Bare Metal"})}),"\n",(0,a.jsx)(n.h2,{id:"build-from-source",children:"Build from Source"}),"\n",(0,a.jsxs)(n.p,{children:["Dashy can be compiled and run directly on your machine.",(0,a.jsx)(n.br,{}),"\nFor this, you will need both ",(0,a.jsx)(n.a,{href:"https://git-scm.com/downloads",children:"git"})," and ",(0,a.jsx)(n.a,{href:"https://nodejs.org/",children:"Node.js"})," (v20 or newer) installed."]}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["Get Code: ",(0,a.jsx)(n.code,{children:"git clone https://github.com/Lissy93/dashy.git"})," and ",(0,a.jsx)(n.code,{children:"cd dashy"})]}),"\n",(0,a.jsxs)(n.li,{children:["Configuration: Fill in your settings in ",(0,a.jsx)(n.code,{children:"./user-data/conf.yml"})]}),"\n",(0,a.jsxs)(n.li,{children:["Install dependencies: ",(0,a.jsx)(n.code,{children:"yarn"})]}),"\n",(0,a.jsxs)(n.li,{children:["Build: ",(0,a.jsx)(n.code,{children:"yarn build"})]}),"\n",(0,a.jsxs)(n.li,{children:["Run: ",(0,a.jsx)(n.code,{children:"yarn start"})]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"For example, the following steps can be used for Ubuntu/Debian based distros:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"# Install prerequisites\nsudo apt update\nsudo apt install -y curl git\n\n# Install Node.js (v24 LTS) from NodeSource, and enable yarn\ncurl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -\nsudo apt install -y nodejs\nsudo corepack enable\n\n# Get the code\ngit clone https://github.com/Lissy93/dashy.git\ncd dashy\n\n# Install dependencies and build\nyarn\nyarn build\n\n# Run it (serves on http://localhost:4000 by default, change with PORT env var)\nyarn start\n\n# Put all your assets (icons, styles, scripts, fonts, pages, etc) and configs in ./user-data\n# Then, get started with editing the main config\nnano user-data/conf.yml\n"})}),"\n",(0,a.jsx)(n.p,{children:"Notes:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["If you're making changes to Dashy's codebase, you can run ",(0,a.jsx)(n.code,{children:"yarn dev"})," to start the dev server with hot reloading. See the ",(0,a.jsx)(n.a,{href:"/docs/developing",children:"Developing"})," docs for more info."]}),"\n",(0,a.jsxs)(n.li,{children:["To keep Dashy running in the background, either use ",(0,a.jsx)(n.code,{children:"yarn pm2-start"})," (instead of yarn start). Or, with systemd, see the ",(0,a.jsx)(n.a,{href:"#systemd-service",children:"systemd service"})," example below"]}),"\n"]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"pre-built-release",children:"Pre-Built Release"}),"\n",(0,a.jsxs)(n.p,{children:["We publish GitHub releases for every major and minor (not patch) version of Dashy.",(0,a.jsx)(n.br,{}),"\nEach release includes the pre-compiled app as ",(0,a.jsx)(n.code,{children:"dashy-[version].tar.gz"})," (plus a SHA256 checksum and SLSA build-provenance attestation)."]}),"\n",(0,a.jsx)(n.p,{children:"This is useful if you want to run Dashy on bare metal, without the need to compile from source."}),"\n",(0,a.jsxs)(n.p,{children:["You'll need ",(0,a.jsx)(n.a,{href:"https://nodejs.org/",children:"Node.js"})," (v20 or newer), ",(0,a.jsx)(n.code,{children:"curl"}),", ",(0,a.jsx)(n.code,{children:"tar"}),", and ",(0,a.jsx)(n.code,{children:"yarn"})," enabled through Corepack."]}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["Pick a release from ",(0,a.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/releases",children:"GitHub Releases"})]}),"\n",(0,a.jsxs)(n.li,{children:["Download ",(0,a.jsx)(n.code,{children:"dashy-[version].tar.gz"})]}),"\n",(0,a.jsxs)(n.li,{children:["Extract it somewhere permanent, such as ",(0,a.jsx)(n.code,{children:"/opt/dashy"})]}),"\n",(0,a.jsxs)(n.li,{children:["Run ",(0,a.jsx)(n.code,{children:"yarn install --production --frozen-lockfile"})]}),"\n",(0,a.jsxs)(n.li,{children:["Edit ",(0,a.jsx)(n.code,{children:"user-data/conf.yml"})]}),"\n",(0,a.jsxs)(n.li,{children:["Start it with ",(0,a.jsx)(n.code,{children:"node server"})]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"For example (replace 4.3.0 with whichever version you want):"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"curl -LO https://github.com/Lissy93/dashy/releases/download/4.3.0/dashy-4.3.0.tar.gz\nmkdir -p dashy\ntar -xzf dashy-4.3.0.tar.gz -C dashy\ncd dashy\ncorepack enable\nyarn install --production --frozen-lockfile\nnode server\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Dashy will run on ",(0,a.jsx)(n.code,{children:"http://localhost:4000"})," by default. Change this with the ",(0,a.jsx)(n.code,{children:"PORT"})," env var."]}),"\n",(0,a.jsxs)(n.p,{children:["You can now start configuring by editing ",(0,a.jsx)(n.code,{children:"user-data/conf.yml"})," and put any assets you want within user-data."]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"systemd-service",children:"Systemd Service"}),"\n",(0,a.jsx)(n.p,{children:"If you're running Dashy on a Linux server, you can use systemd to start it at boot and restart it if it crashes."}),"\n",(0,a.jsxs)(n.p,{children:["This example assumes Dashy is installed in ",(0,a.jsx)(n.code,{children:"/opt/dashy"}),", and runs it as a dedicated ",(0,a.jsx)(n.code,{children:"dashy"})," user, so adjust it for your usecase."]}),"\n",(0,a.jsx)(n.p,{children:"Run these commands as root:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"useradd --system --home /opt/dashy --shell /usr/sbin/nologin dashy\nchown -R dashy:dashy /opt/dashy\n"})}),"\n",(0,a.jsxs)(n.p,{children:["Create ",(0,a.jsx)(n.code,{children:"/etc/systemd/system/dashy.service"}),":"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-ini",children:"[Unit]\nDescription=Dashy\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=dashy\nWorkingDirectory=/opt/dashy\nEnvironment=NODE_ENV=production\nEnvironment=PORT=4000\nExecStart=/usr/bin/env node server\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\n"})}),"\n",(0,a.jsx)(n.p,{children:"Then enable and start it:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"systemctl daemon-reload\nsystemctl enable --now dashy\nsystemctl status dashy\n"})}),"\n",(0,a.jsxs)(n.p,{children:["If you installed Dashy somewhere else, update ",(0,a.jsx)(n.code,{children:"WorkingDirectory"}),"."]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"checksum-verification",children:"Checksum Verification"}),"\n",(0,a.jsx)(n.p,{children:"Each pre-built release includes a SHA256 checksum. This is optional, but it is a quick way to check the file downloaded correctly."}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"curl -LO https://github.com/Lissy93/dashy/releases/download/4.3.0/dashy-4.3.0.tar.gz\ncurl -LO https://github.com/Lissy93/dashy/releases/download/4.3.0/dashy-4.3.0.tar.gz.sha256\nsha256sum -c dashy-4.3.0.tar.gz.sha256\n"})}),"\n",(0,a.jsxs)(n.p,{children:["You should see ",(0,a.jsx)(n.code,{children:"dashy-4.3.0.tar.gz: OK"}),"."]}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"provenance-attestation",children:"Provenance Attestation"}),"\n",(0,a.jsx)(n.p,{children:"GitHub releases also include a SLSA build-provenance attestation. This lets you verify that the release asset was built by Dashy's GitHub Actions release workflow."}),"\n",(0,a.jsxs)(n.p,{children:["Install a recent ",(0,a.jsx)(n.a,{href:"https://cli.github.com/",children:"GitHub CLI"}),", then run:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"gh attestation verify dashy-4.3.0.tar.gz --repo Lissy93/dashy\n"})}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsx)(n.h2,{id:"install-script",children:"Install Script"}),"\n",(0,a.jsxs)(n.p,{children:["The following is an example script to install or update the latest pre-built release on a Linux server with systemd.\nIt creates a ",(0,a.jsx)(n.code,{children:"dashy"})," user, installs into ",(0,a.jsx)(n.code,{children:"/opt/dashy"}),", verifies the SHA256 checksum, installs production dependencies, and starts Dashy as a service."]}),"\n",(0,a.jsxs)(n.details,{children:["\n",(0,a.jsx)(n.summary,{children:"install-dashy.sh"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:'#!/usr/bin/env bash\nset -euo pipefail\n\nREPO="Lissy93/dashy"\nINSTALL_DIR="${INSTALL_DIR:-/opt/dashy}"\nRUN_USER="${RUN_USER:-dashy}"\nPORT="${PORT:-4000}"\nVERSION="${DASHY_VERSION:-latest}"\n\nif [ "$(id -u)" -ne 0 ]; then\n echo "Please run this script as root"\n exit 1\nfi\n\nneed_cmd() {\n command -v "$1" >/dev/null 2>&1 || {\n echo "Missing required command: $1"\n exit 1\n }\n}\n\ninstall_packages() {\n if command -v apt-get >/dev/null 2>&1; then\n apt-get update\n apt-get install -y curl ca-certificates tar gzip coreutils\n elif command -v dnf >/dev/null 2>&1; then\n dnf install -y curl ca-certificates tar gzip coreutils nodejs\n elif command -v yum >/dev/null 2>&1; then\n yum install -y curl ca-certificates tar gzip coreutils nodejs\n else\n echo "Please install curl, tar, sha256sum and Node.js v20 or newer, then re-run this script."\n exit 1\n fi\n}\n\ninstall_packages\nneed_cmd curl\nneed_cmd tar\nneed_cmd sha256sum\nneed_cmd runuser\n\ninstall_node_apt() {\n curl -fsSL https://deb.nodesource.com/setup_24.x | bash -\n apt-get install -y nodejs\n}\n\nif ! command -v node >/dev/null 2>&1; then\n if command -v apt-get >/dev/null 2>&1; then\n install_node_apt\n else\n echo "Please install Node.js v20 or newer, then re-run this script."\n exit 1\n fi\nfi\n\nNODE_MAJOR="$(node -p "process.versions.node.split(\'.\')[0]")"\nif [ "$NODE_MAJOR" -lt 20 ]; then\n if command -v apt-get >/dev/null 2>&1; then\n install_node_apt\n NODE_MAJOR="$(node -p "process.versions.node.split(\'.\')[0]")"\n fi\n\n if [ "$NODE_MAJOR" -lt 20 ]; then\n echo "Node.js v20 or newer is required. Found: $(node -v)"\n exit 1\n fi\nfi\n\ncorepack enable\nneed_cmd yarn\n\ncase "$INSTALL_DIR" in\n /*) ;;\n *) echo "INSTALL_DIR must be an absolute path"; exit 1 ;;\nesac\n\nif [ "$INSTALL_DIR" = "/" ]; then\n echo "INSTALL_DIR cannot be /"\n exit 1\nfi\n\nif [ "$VERSION" = "latest" ]; then\n RELEASE_URL="$(curl -fsSLI -o /dev/null -w \'%{url_effective}\' "https://github.com/${REPO}/releases/latest")"\n VERSION="${RELEASE_URL##*/}"\nfi\nVERSION="${VERSION#v}"\n\nASSET="dashy-${VERSION}.tar.gz"\nBASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"\nTMP_DIR="$(mktemp -d)"\ntrap \'rm -rf "$TMP_DIR"\' EXIT\n\necho "Downloading Dashy ${VERSION}..."\ncurl -fL "${BASE_URL}/${ASSET}" -o "${TMP_DIR}/${ASSET}"\ncurl -fL "${BASE_URL}/${ASSET}.sha256" -o "${TMP_DIR}/${ASSET}.sha256"\n\n(\n cd "$TMP_DIR"\n sha256sum -c "${ASSET}.sha256"\n)\n\nif ! id "$RUN_USER" >/dev/null 2>&1; then\n useradd --system --home "$INSTALL_DIR" --shell /usr/sbin/nologin "$RUN_USER"\nfi\n\nif [ -d "$INSTALL_DIR/user-data" ]; then\n BACKUP_DIR="${INSTALL_DIR}.user-data.$(date +%Y%m%d%H%M%S)"\n cp -a "$INSTALL_DIR/user-data" "$BACKUP_DIR"\n echo "Backed up user-data to ${BACKUP_DIR}"\nfi\n\nNEW_DIR="${INSTALL_DIR}.new"\nOLD_DIR="${INSTALL_DIR}.old"\n\nrm -rf "$NEW_DIR" "$OLD_DIR"\nmkdir -p "$NEW_DIR"\ntar -xzf "${TMP_DIR}/${ASSET}" -C "$NEW_DIR"\n\nif [ -d "$INSTALL_DIR/user-data" ]; then\n rm -rf "${NEW_DIR}/user-data"\n cp -a "$INSTALL_DIR/user-data" "${NEW_DIR}/user-data"\nfi\n\nchown -R "$RUN_USER:$RUN_USER" "$NEW_DIR"\n\nrunuser -u "$RUN_USER" -- bash -lc "cd \'$NEW_DIR\' && yarn install --production --frozen-lockfile"\n\nif [ -d "$INSTALL_DIR" ]; then\n mv "$INSTALL_DIR" "$OLD_DIR"\nfi\n\nmv "$NEW_DIR" "$INSTALL_DIR"\nrm -rf "$OLD_DIR"\n\ncat >/etc/systemd/system/dashy.service <i,x:()=>l});var t=s(96540);const a={},r=t.createContext(a);function i(e){const n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:i(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/0bfe2fe2.c3bef218.js b/assets/js/0bfe2fe2.03e2b087.js similarity index 99% rename from assets/js/0bfe2fe2.c3bef218.js rename to assets/js/0bfe2fe2.03e2b087.js index d7e7cc8f..6a7c1bac 100644 --- a/assets/js/0bfe2fe2.c3bef218.js +++ b/assets/js/0bfe2fe2.03e2b087.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[7151],{92849(e,n,s){s.r(n),s.d(n,{assets:()=>h,contentTitle:()=>c,default:()=>d,frontMatter:()=>t,metadata:()=>i,toc:()=>o});const i=JSON.parse('{"id":"searching","title":"Search & Shortcuts","description":"Searching","source":"@site/docs/searching.md","sourceDirName":".","slug":"/searching","permalink":"/docs/searching","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/searching.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Zitadel OIDC","permalink":"/docs/authentication/zitadel"},"next":{"title":"Alternate Views & Opening Methods","permalink":"/docs/alternate-views"}}');var r=s(74848),a=s(28453);const t={},c="Search & Shortcuts",h={},o=[{value:"Searching",id:"searching",level:2},{value:"Navigating",id:"navigating",level:2},{value:"Launching Apps",id:"launching-apps",level:2},{value:"Tags",id:"tags",level:2},{value:"Hiding Items From The Homepage",id:"hiding-items-from-the-homepage",level:2},{value:"Custom Hotkeys",id:"custom-hotkeys",level:2},{value:"Web Search",id:"web-search",level:2},{value:"Setting Search Engine",id:"setting-search-engine",level:3},{value:"Using Custom Search Engine",id:"using-custom-search-engine",level:3},{value:"Setting Opening Method",id:"setting-opening-method",level:3},{value:"Using Bangs",id:"using-bangs",level:3},{value:"Disabling Web Search",id:"disabling-web-search",level:3},{value:"Opening URLs Directly",id:"opening-urls-directly",level:3},{value:"Clearing Search",id:"clearing-search",level:2}];function l(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",kbd:"kbd",li:"li",p:"p",pre:"pre",ul:"ul",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"search--shortcuts",children:"Search & Shortcuts"})}),"\n",(0,r.jsx)(n.h2,{id:"searching",children:"Searching"}),"\n",(0,r.jsx)(n.p,{children:"One of the primary purposes of Dashy is to allow you to quickly find and launch a given app. To make this as quick as possible, there is no need to touch the mouse, or press a certain key to begin searching - just start typing. Results will be filtered in real-time. No need to worry about case, special characters or small typos, these are taken care of, and your results should appear."}),"\n",(0,r.jsx)(n.h2,{id:"navigating",children:"Navigating"}),"\n",(0,r.jsxs)(n.p,{children:["You can navigate through your items or search results using the keyboard. You can use ",(0,r.jsx)(n.kbd,{children:"Tab"})," to cycle through results, and ",(0,r.jsx)(n.kbd,{children:"Shift"})," + ",(0,r.jsx)(n.kbd,{children:"Tab"})," to go backwards. Or use the arrow keys, ",(0,r.jsx)(n.kbd,{children:"\u2191"}),", ",(0,r.jsx)(n.kbd,{children:"\u2192"}),", ",(0,r.jsx)(n.kbd,{children:"\u2193"})," and ",(0,r.jsx)(n.kbd,{children:"\u2190"}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"launching-apps",children:"Launching Apps"}),"\n",(0,r.jsxs)(n.p,{children:["You can launch a elected app by hitting ",(0,r.jsx)(n.kbd,{children:"Enter"}),". This will open the app using your default opening method, specified in ",(0,r.jsx)(n.code,{children:"target"})," (one of ",(0,r.jsx)(n.code,{children:"newtab"}),", ",(0,r.jsx)(n.code,{children:"sametab"}),", ",(0,r.jsx)(n.code,{children:"parent"}),", ",(0,r.jsx)(n.code,{children:"top"}),", ",(0,r.jsx)(n.code,{children:"modal"}),", ",(0,r.jsx)(n.code,{children:"workspace"}),", ",(0,r.jsx)(n.code,{children:"clipboard"})," or ",(0,r.jsx)(n.code,{children:"newwindow"}),"). You can also use ",(0,r.jsx)(n.kbd,{children:"Alt"})," + ",(0,r.jsx)(n.kbd,{children:"Enter"})," to open the app in a pop-up modal, or ",(0,r.jsx)(n.kbd,{children:"Ctrl"})," + ",(0,r.jsx)(n.kbd,{children:"Enter"})," to open it in a new tab. For all available opening methods, just right-click on an item, to bring up the context menu."]}),"\n",(0,r.jsx)(n.h2,{id:"tags",children:"Tags"}),"\n",(0,r.jsxs)(n.p,{children:["By default, when searching items are filtered by the ",(0,r.jsx)(n.code,{children:"title"}),", (as well as the ",(0,r.jsx)(n.code,{children:"url"}),", ",(0,r.jsx)(n.code,{children:"provider"})," and ",(0,r.jsx)(n.code,{children:"description"}),"). If you need to find results based on text which isn't included in these attributes, then you can add ",(0,r.jsx)(n.code,{children:"tags"})," to a given item."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" items:\n - title: Plex\n description: Media library\n icon: favicon\n url: https://plex.lab.local\n tags: [ movies, videos, music ]\n - title: FreshRSS\n description: RSS Reader\n icon: favicon\n url: https://freshrss.lab.local\n tags: [ news, updates, blogs ]\n\n"})}),"\n",(0,r.jsx)(n.p,{children:"In the above example, Plex will be visible when searching for 'movies', and FreshRSS with 'news'"}),"\n",(0,r.jsx)(n.h2,{id:"hiding-items-from-the-homepage",children:"Hiding Items From The Homepage"}),"\n",(0,r.jsxs)(n.p,{children:["You can hide a rarely-used item from the main view but still have it surface when you search for it. Set ",(0,r.jsx)(n.code,{children:"displayData.hideFromHomepage: true"})," on the item."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- title: MXToolbox\n url: https://mxtoolbox.com\n tags: [dns, mail, debug]\n displayData:\n hideFromHomepage: true\n"})}),"\n",(0,r.jsx)(n.p,{children:"The item still shows up in search results, and stays visible in the workspace view, in edit mode, and when you navigate into its parent section directly."}),"\n",(0,r.jsx)(n.h2,{id:"custom-hotkeys",children:"Custom Hotkeys"}),"\n",(0,r.jsxs)(n.p,{children:["For apps that you use regularly, you can set a custom keybinding. Use the ",(0,r.jsx)(n.code,{children:"hotkey"})," parameter on a certain item to specify a numeric key, between ",(0,r.jsx)(n.code,{children:"0 - 9"}),". You can then launch that app, by just pressing that key, which is much quicker than searching for it, if it's an app you use frequently."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- title: Bookstack\n icon: far fa-books\n url: https://bookstack.lab.local/\n hotkey: 2\n- title: Git Tea\n icon: fab fa-git\n url: https://git.lab.local/\n target: workspace\n hotkey: 3\n"})}),"\n",(0,r.jsxs)(n.p,{children:["In the above example, pressing ",(0,r.jsx)(n.kbd,{children:"2"})," will launch Bookstack. Or hitting ",(0,r.jsx)(n.kbd,{children:"3"})," will open Git in the workspace view."]}),"\n",(0,r.jsx)(n.h2,{id:"web-search",children:"Web Search"}),"\n",(0,r.jsxs)(n.p,{children:["It's possible to launch a web search directly from Dashy, which might be useful if you're using Dashy as your start page. This can be done by typing your query as normal, and then pressing ",(0,r.jsx)(n.kbd,{children:"\u23ce"}),"/Enter. Web search options are configured under ",(0,r.jsx)(n.code,{children:"appConfig.webSearch"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"setting-search-engine",children:"Setting Search Engine"}),"\n",(0,r.jsxs)(n.p,{children:["Set your default search engine using the ",(0,r.jsx)(n.code,{children:"webSearch.searchEngine"})," property. This defaults to DuckDuckGo. Search engine must be referenced by their key, the following providers are supported:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://duckduckgo.com",children:(0,r.jsx)(n.code,{children:"duckduckgo"})}),", ",(0,r.jsx)(n.a,{href:"https://google.com",children:(0,r.jsx)(n.code,{children:"google"})}),", ",(0,r.jsx)(n.a,{href:"https://search.brave.com",children:(0,r.jsx)(n.code,{children:"brave"})}),", ",(0,r.jsx)(n.a,{href:"https://kagi.com",children:(0,r.jsx)(n.code,{children:"kagi"})}),", ",(0,r.jsx)(n.a,{href:"https://www.qwant.com",children:(0,r.jsx)(n.code,{children:"qwant"})}),", ",(0,r.jsx)(n.a,{href:"https://www.startpage.com",children:(0,r.jsx)(n.code,{children:"startpage"})})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://www.perplexity.ai",children:(0,r.jsx)(n.code,{children:"perplexity"})}),", ",(0,r.jsx)(n.a,{href:"https://uruky.com",children:(0,r.jsx)(n.code,{children:"uruky"})}),", ",(0,r.jsx)(n.a,{href:"https://searx.tiekoetter.com",children:(0,r.jsx)(n.code,{children:"searx-tiekoetter"})}),", ",(0,r.jsx)(n.a,{href:"https://www.ecosia.org",children:(0,r.jsx)(n.code,{children:"ecosia"})}),", ",(0,r.jsx)(n.a,{href:"https://metager.org/meta",children:(0,r.jsx)(n.code,{children:"metager"})}),", ",(0,r.jsx)(n.a,{href:"https://swisscows.com",children:(0,r.jsx)(n.code,{children:"swisscows"})}),", ",(0,r.jsx)(n.a,{href:"https://www.mojeek.com",children:(0,r.jsx)(n.code,{children:"mojeek"})}),", ",(0,r.jsx)(n.a,{href:"https://peekier.com",children:(0,r.jsx)(n.code,{children:"peekier"})})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://en.wikipedia.org",children:(0,r.jsx)(n.code,{children:"wikipedia"})}),", ",(0,r.jsx)(n.a,{href:"https://www.wolframalpha.com",children:(0,r.jsx)(n.code,{children:"wolframalpha"})}),", ",(0,r.jsx)(n.a,{href:"https://stackoverflow.com",children:(0,r.jsx)(n.code,{children:"stackoverflow"})}),", ",(0,r.jsx)(n.a,{href:"https://github.com",children:(0,r.jsx)(n.code,{children:"github"})}),", ",(0,r.jsx)(n.a,{href:"https://www.reddit.com",children:(0,r.jsx)(n.code,{children:"reddit"})}),", ",(0,r.jsx)(n.a,{href:"https://youtube.com",children:(0,r.jsx)(n.code,{children:"youtube"})}),", ",(0,r.jsx)(n.a,{href:"https://www.bbc.co.uk",children:(0,r.jsx)(n.code,{children:"bbc"})})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"using-custom-search-engine",children:"Using Custom Search Engine"}),"\n",(0,r.jsxs)(n.p,{children:["You can also use a custom search engine, that isn't included in the above list (like a self-hosted instance of ",(0,r.jsx)(n.a,{href:"https://github.com/benbusby/whoogle-search",children:"Whoogle"})," or ",(0,r.jsx)(n.a,{href:"https://searx.github.io/searx/",children:"Searx"}),"). Set ",(0,r.jsx)(n.code,{children:"searchEngine: custom"}),", and then specify the URL (plus query params) to you're search engine under ",(0,r.jsx)(n.code,{children:"customSearchEngine"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n webSearch:\n searchEngine: custom\n customSearchEngine: 'https://searx.local/search?q='\n"})}),"\n",(0,r.jsx)(n.h3,{id:"setting-opening-method",children:"Setting Opening Method"}),"\n",(0,r.jsxs)(n.p,{children:["In a similar way to opening apps, you can specify where you would like search results to be opened. This is done under the ",(0,r.jsx)(n.code,{children:"openingMethod"})," attribute, and can be set to either ",(0,r.jsx)(n.code,{children:"newtab"}),", ",(0,r.jsx)(n.code,{children:"sametab"})," or ",(0,r.jsx)(n.code,{children:"workspace"}),". By default results are opened in a new tab."]}),"\n",(0,r.jsx)(n.h3,{id:"using-bangs",children:"Using Bangs"}),"\n",(0,r.jsxs)(n.p,{children:["An insanely useful feature of DDG is ",(0,r.jsx)(n.a,{href:"https://duckduckgo.com/bang",children:"Bangs"}),", where you type a specific character combination as part of your search query, and it will be redirected the that website, such as '!w Docker' will display the Docker wikipedia page. Dashy has a similar feature, enabling you to define your own custom bangs to redirect search results to a specific app, website or search engine. The bang can appear anywhere in the query. As long as the whole token is present, pressing Enter will launch that search bang"]}),"\n",(0,r.jsxs)(n.p,{children:["This is configured under the ",(0,r.jsx)(n.code,{children:"searchBangs"})," property, with a list of key value pairs. The key is what you will type, and the value is the destination, either as an identifier or a URL with query parameters."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n webSearch:\n searchEngine: 'duckduckgo'\n openingMethod: 'newtab'\n searchBangs:\n /r: reddit\n /w: wikipedia\n /s: https://whoogle.local/search?q=\n /a: https://www.amazon.co.uk/s?k=\n ':wolf': wolframalpha\n ':so': stackoverflow\n ':git': github\n"})}),"\n",(0,r.jsxs)(n.p,{children:["In the above example, when you type ",(0,r.jsx)(n.code,{children:":so how to exit vim"})," you'll be automatically sent to StackOverflow"]}),"\n",(0,r.jsxs)(n.p,{children:["Note that bangs begging with ",(0,r.jsx)(n.code,{children:"!"})," or ",(0,r.jsx)(n.code,{children:":"})," must be surrounded in quotes in the config (to keep it valid YAML)"]}),"\n",(0,r.jsx)(n.h3,{id:"disabling-web-search",children:"Disabling Web Search"}),"\n",(0,r.jsxs)(n.p,{children:["Web search can be disabled, by setting ",(0,r.jsx)(n.code,{children:"disableWebSearch"}),", for example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n webSearch: { disableWebSearch: true }\n"})}),"\n",(0,r.jsxs)(n.p,{children:["When web search is disabled, pressing ",(0,r.jsx)(n.kbd,{children:"Enter"})," opens the first matching item instead of web search. This will use that item's configured opening method"]}),"\n",(0,r.jsx)(n.h3,{id:"opening-urls-directly",children:"Opening URLs Directly"}),"\n",(0,r.jsxs)(n.p,{children:["When enabled (with ",(0,r.jsx)(n.code,{children:"appConfig.webSearch.openUrlsDirectly"}),"), if your search query looks like a URL, pressing ",(0,r.jsx)(n.kbd,{children:"Enter"})," will navigate directly to that URL instead of searching for it. Recognized formats include:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Full URLs with scheme: ",(0,r.jsx)(n.code,{children:"https://example.org/path"})]}),"\n",(0,r.jsxs)(n.li,{children:["Dotted hostnames: ",(0,r.jsx)(n.code,{children:"github.com"}),", ",(0,r.jsx)(n.code,{children:"example.com:8080"}),", ",(0,r.jsx)(n.code,{children:"example.com/path"})]}),"\n",(0,r.jsxs)(n.li,{children:["IP addresses: ",(0,r.jsx)(n.code,{children:"192.168.1.1"}),", ",(0,r.jsx)(n.code,{children:"192.168.1.1:8080"}),", ",(0,r.jsx)(n.code,{children:"192.168.1.1/admin"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"localhost"})," (with optional port and/or path): ",(0,r.jsx)(n.code,{children:"localhost"}),", ",(0,r.jsx)(n.code,{children:"localhost:8080"})]}),"\n",(0,r.jsxs)(n.li,{children:["Bare hostnames when followed by a port or path: ",(0,r.jsx)(n.code,{children:"nas:8080"}),", ",(0,r.jsx)(n.code,{children:"router/setup"})]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Note that a bare ambiguous word on its own (e.g. ",(0,r.jsx)(n.code,{children:"nas"}),", ",(0,r.jsx)(n.code,{children:"kubernetes"}),") is still treated as a search query, since the intent isn't clear."]}),"\n",(0,r.jsxs)(n.p,{children:["Set ",(0,r.jsx)(n.code,{children:"appConfig.webSearch.openUrlsDirectly"})," to ",(0,r.jsx)(n.code,{children:"true"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n webSearch:\n openUrlsDirectly: true\n"})}),"\n",(0,r.jsx)(n.h2,{id:"clearing-search",children:"Clearing Search"}),"\n",(0,r.jsxs)(n.p,{children:["You can clear your search term at any time, resting the UI to it's initial state, by pressing ",(0,r.jsx)(n.kbd,{children:"Esc"}),".\nThis can also be used to close any open pop-up modals."]})]})}function d(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>c});var i=s(96540);const r={},a=i.createContext(r);function t(e){const n=i.useContext(a);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[7151],{92849(e,n,s){s.r(n),s.d(n,{assets:()=>h,contentTitle:()=>c,default:()=>d,frontMatter:()=>t,metadata:()=>i,toc:()=>o});const i=JSON.parse('{"id":"searching","title":"Search & Shortcuts","description":"Searching","source":"@site/docs/searching.md","sourceDirName":".","slug":"/searching","permalink":"/docs/searching","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/searching.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Zitadel OIDC","permalink":"/docs/authentication/zitadel"},"next":{"title":"Alternate Views & Opening Methods","permalink":"/docs/alternate-views"}}');var r=s(74848),a=s(28453);const t={},c="Search & Shortcuts",h={},o=[{value:"Searching",id:"searching",level:2},{value:"Navigating",id:"navigating",level:2},{value:"Launching Apps",id:"launching-apps",level:2},{value:"Tags",id:"tags",level:2},{value:"Hiding Items From The Homepage",id:"hiding-items-from-the-homepage",level:2},{value:"Custom Hotkeys",id:"custom-hotkeys",level:2},{value:"Web Search",id:"web-search",level:2},{value:"Setting Search Engine",id:"setting-search-engine",level:3},{value:"Using Custom Search Engine",id:"using-custom-search-engine",level:3},{value:"Setting Opening Method",id:"setting-opening-method",level:3},{value:"Using Bangs",id:"using-bangs",level:3},{value:"Disabling Web Search",id:"disabling-web-search",level:3},{value:"Opening URLs Directly",id:"opening-urls-directly",level:3},{value:"Clearing Search",id:"clearing-search",level:2}];function l(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",kbd:"kbd",li:"li",p:"p",pre:"pre",ul:"ul",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"search--shortcuts",children:"Search & Shortcuts"})}),"\n",(0,r.jsx)(n.h2,{id:"searching",children:"Searching"}),"\n",(0,r.jsx)(n.p,{children:"One of the primary purposes of Dashy is to allow you to quickly find and launch a given app. To make this as quick as possible, there is no need to touch the mouse, or press a certain key to begin searching - just start typing. Results will be filtered in real-time. No need to worry about case, special characters or small typos, these are taken care of, and your results should appear."}),"\n",(0,r.jsx)(n.h2,{id:"navigating",children:"Navigating"}),"\n",(0,r.jsxs)(n.p,{children:["You can navigate through your items or search results using the keyboard. You can use ",(0,r.jsx)(n.kbd,{children:"Tab"})," to cycle through results, and ",(0,r.jsx)(n.kbd,{children:"Shift"})," + ",(0,r.jsx)(n.kbd,{children:"Tab"})," to go backwards. Or use the arrow keys, ",(0,r.jsx)(n.kbd,{children:"\u2191"}),", ",(0,r.jsx)(n.kbd,{children:"\u2192"}),", ",(0,r.jsx)(n.kbd,{children:"\u2193"})," and ",(0,r.jsx)(n.kbd,{children:"\u2190"}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"launching-apps",children:"Launching Apps"}),"\n",(0,r.jsxs)(n.p,{children:["You can launch a elected app by hitting ",(0,r.jsx)(n.kbd,{children:"Enter"}),". This will open the app using your default opening method, specified in ",(0,r.jsx)(n.code,{children:"target"})," (one of ",(0,r.jsx)(n.code,{children:"newtab"}),", ",(0,r.jsx)(n.code,{children:"sametab"}),", ",(0,r.jsx)(n.code,{children:"parent"}),", ",(0,r.jsx)(n.code,{children:"top"}),", ",(0,r.jsx)(n.code,{children:"modal"}),", ",(0,r.jsx)(n.code,{children:"workspace"}),", ",(0,r.jsx)(n.code,{children:"clipboard"})," or ",(0,r.jsx)(n.code,{children:"newwindow"}),"). You can also use ",(0,r.jsx)(n.kbd,{children:"Alt"})," + ",(0,r.jsx)(n.kbd,{children:"Enter"})," to open the app in a pop-up modal, or ",(0,r.jsx)(n.kbd,{children:"Ctrl"})," + ",(0,r.jsx)(n.kbd,{children:"Enter"})," to open it in a new tab. For all available opening methods, just right-click on an item, to bring up the context menu."]}),"\n",(0,r.jsx)(n.h2,{id:"tags",children:"Tags"}),"\n",(0,r.jsxs)(n.p,{children:["By default, when searching items are filtered by the ",(0,r.jsx)(n.code,{children:"title"}),", (as well as the ",(0,r.jsx)(n.code,{children:"url"}),", ",(0,r.jsx)(n.code,{children:"provider"})," and ",(0,r.jsx)(n.code,{children:"description"}),"). If you need to find results based on text which isn't included in these attributes, then you can add ",(0,r.jsx)(n.code,{children:"tags"})," to a given item."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" items:\n - title: Plex\n description: Media library\n icon: favicon\n url: https://plex.lab.local\n tags: [ movies, videos, music ]\n - title: FreshRSS\n description: RSS Reader\n icon: favicon\n url: https://freshrss.lab.local\n tags: [ news, updates, blogs ]\n\n"})}),"\n",(0,r.jsx)(n.p,{children:"In the above example, Plex will be visible when searching for 'movies', and FreshRSS with 'news'"}),"\n",(0,r.jsx)(n.h2,{id:"hiding-items-from-the-homepage",children:"Hiding Items From The Homepage"}),"\n",(0,r.jsxs)(n.p,{children:["You can hide a rarely-used item from the main view but still have it surface when you search for it. Set ",(0,r.jsx)(n.code,{children:"displayData.hideFromHomepage: true"})," on the item."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- title: MXToolbox\n url: https://mxtoolbox.com\n tags: [dns, mail, debug]\n displayData:\n hideFromHomepage: true\n"})}),"\n",(0,r.jsx)(n.p,{children:"The item still shows up in search results, and stays visible in the workspace view, in edit mode, and when you navigate into its parent section directly."}),"\n",(0,r.jsx)(n.h2,{id:"custom-hotkeys",children:"Custom Hotkeys"}),"\n",(0,r.jsxs)(n.p,{children:["For apps that you use regularly, you can set a custom keybinding. Use the ",(0,r.jsx)(n.code,{children:"hotkey"})," parameter on a certain item to specify a numeric key, between ",(0,r.jsx)(n.code,{children:"0 - 9"}),". You can then launch that app, by just pressing that key, which is much quicker than searching for it, if it's an app you use frequently."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- title: Bookstack\n icon: far fa-books\n url: https://bookstack.lab.local/\n hotkey: 2\n- title: Git Tea\n icon: fab fa-git\n url: https://git.lab.local/\n target: workspace\n hotkey: 3\n"})}),"\n",(0,r.jsxs)(n.p,{children:["In the above example, pressing ",(0,r.jsx)(n.kbd,{children:"2"})," will launch Bookstack. Or hitting ",(0,r.jsx)(n.kbd,{children:"3"})," will open Git in the workspace view."]}),"\n",(0,r.jsx)(n.h2,{id:"web-search",children:"Web Search"}),"\n",(0,r.jsxs)(n.p,{children:["It's possible to launch a web search directly from Dashy, which might be useful if you're using Dashy as your start page. This can be done by typing your query as normal, and then pressing ",(0,r.jsx)(n.kbd,{children:"\u23ce"}),"/Enter. Web search options are configured under ",(0,r.jsx)(n.code,{children:"appConfig.webSearch"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"setting-search-engine",children:"Setting Search Engine"}),"\n",(0,r.jsxs)(n.p,{children:["Set your default search engine using the ",(0,r.jsx)(n.code,{children:"webSearch.searchEngine"})," property. This defaults to DuckDuckGo. Search engine must be referenced by their key, the following providers are supported:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://duckduckgo.com",children:(0,r.jsx)(n.code,{children:"duckduckgo"})}),", ",(0,r.jsx)(n.a,{href:"https://google.com",children:(0,r.jsx)(n.code,{children:"google"})}),", ",(0,r.jsx)(n.a,{href:"https://search.brave.com",children:(0,r.jsx)(n.code,{children:"brave"})}),", ",(0,r.jsx)(n.a,{href:"https://kagi.com",children:(0,r.jsx)(n.code,{children:"kagi"})}),", ",(0,r.jsx)(n.a,{href:"https://www.qwant.com",children:(0,r.jsx)(n.code,{children:"qwant"})}),", ",(0,r.jsx)(n.a,{href:"https://www.startpage.com",children:(0,r.jsx)(n.code,{children:"startpage"})})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://www.perplexity.ai",children:(0,r.jsx)(n.code,{children:"perplexity"})}),", ",(0,r.jsx)(n.a,{href:"https://uruky.com",children:(0,r.jsx)(n.code,{children:"uruky"})}),", ",(0,r.jsx)(n.a,{href:"https://searx.tiekoetter.com",children:(0,r.jsx)(n.code,{children:"searx-tiekoetter"})}),", ",(0,r.jsx)(n.a,{href:"https://www.ecosia.org",children:(0,r.jsx)(n.code,{children:"ecosia"})}),", ",(0,r.jsx)(n.a,{href:"https://metager.org/meta",children:(0,r.jsx)(n.code,{children:"metager"})}),", ",(0,r.jsx)(n.a,{href:"https://swisscows.com",children:(0,r.jsx)(n.code,{children:"swisscows"})}),", ",(0,r.jsx)(n.a,{href:"https://www.mojeek.com",children:(0,r.jsx)(n.code,{children:"mojeek"})}),", ",(0,r.jsx)(n.a,{href:"https://peekier.com",children:(0,r.jsx)(n.code,{children:"peekier"})})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://en.wikipedia.org",children:(0,r.jsx)(n.code,{children:"wikipedia"})}),", ",(0,r.jsx)(n.a,{href:"https://www.wolframalpha.com",children:(0,r.jsx)(n.code,{children:"wolframalpha"})}),", ",(0,r.jsx)(n.a,{href:"https://stackoverflow.com",children:(0,r.jsx)(n.code,{children:"stackoverflow"})}),", ",(0,r.jsx)(n.a,{href:"https://github.com",children:(0,r.jsx)(n.code,{children:"github"})}),", ",(0,r.jsx)(n.a,{href:"https://www.reddit.com",children:(0,r.jsx)(n.code,{children:"reddit"})}),", ",(0,r.jsx)(n.a,{href:"https://youtube.com",children:(0,r.jsx)(n.code,{children:"youtube"})}),", ",(0,r.jsx)(n.a,{href:"https://www.bbc.co.uk",children:(0,r.jsx)(n.code,{children:"bbc"})})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"using-custom-search-engine",children:"Using Custom Search Engine"}),"\n",(0,r.jsxs)(n.p,{children:["You can also use a custom search engine, that isn't included in the above list (like a self-hosted instance of ",(0,r.jsx)(n.a,{href:"https://github.com/benbusby/whoogle-search",children:"Whoogle"})," or ",(0,r.jsx)(n.a,{href:"https://searx.github.io/searx/",children:"Searx"}),"). Set ",(0,r.jsx)(n.code,{children:"searchEngine: custom"}),", and then specify the URL (plus query params) to you're search engine under ",(0,r.jsx)(n.code,{children:"customSearchEngine"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n webSearch:\n searchEngine: custom\n customSearchEngine: 'https://searx.local/search?q='\n"})}),"\n",(0,r.jsx)(n.h3,{id:"setting-opening-method",children:"Setting Opening Method"}),"\n",(0,r.jsxs)(n.p,{children:["In a similar way to opening apps, you can specify where you would like search results to be opened. This is done under the ",(0,r.jsx)(n.code,{children:"openingMethod"})," attribute, and can be set to either ",(0,r.jsx)(n.code,{children:"newtab"}),", ",(0,r.jsx)(n.code,{children:"sametab"})," or ",(0,r.jsx)(n.code,{children:"workspace"}),". By default results are opened in a new tab."]}),"\n",(0,r.jsx)(n.h3,{id:"using-bangs",children:"Using Bangs"}),"\n",(0,r.jsxs)(n.p,{children:["An insanely useful feature of DDG is ",(0,r.jsx)(n.a,{href:"https://duckduckgo.com/bang",children:"Bangs"}),", where you type a specific character combination as part of your search query, and it will be redirected the that website, such as '!w Docker' will display the Docker wikipedia page. Dashy has a similar feature, enabling you to define your own custom bangs to redirect search results to a specific app, website or search engine. The bang can appear anywhere in the query. As long as the whole token is present, pressing Enter will launch that search bang"]}),"\n",(0,r.jsxs)(n.p,{children:["This is configured under the ",(0,r.jsx)(n.code,{children:"searchBangs"})," property, with a list of key value pairs. The key is what you will type, and the value is the destination, either as an identifier or a URL with query parameters."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n webSearch:\n searchEngine: 'duckduckgo'\n openingMethod: 'newtab'\n searchBangs:\n /r: reddit\n /w: wikipedia\n /s: https://whoogle.local/search?q=\n /a: https://www.amazon.co.uk/s?k=\n ':wolf': wolframalpha\n ':so': stackoverflow\n ':git': github\n"})}),"\n",(0,r.jsxs)(n.p,{children:["In the above example, when you type ",(0,r.jsx)(n.code,{children:":so how to exit vim"})," you'll be automatically sent to StackOverflow"]}),"\n",(0,r.jsxs)(n.p,{children:["Note that bangs begging with ",(0,r.jsx)(n.code,{children:"!"})," or ",(0,r.jsx)(n.code,{children:":"})," must be surrounded in quotes in the config (to keep it valid YAML)"]}),"\n",(0,r.jsx)(n.h3,{id:"disabling-web-search",children:"Disabling Web Search"}),"\n",(0,r.jsxs)(n.p,{children:["Web search can be disabled, by setting ",(0,r.jsx)(n.code,{children:"disableWebSearch"}),", for example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n webSearch: { disableWebSearch: true }\n"})}),"\n",(0,r.jsxs)(n.p,{children:["When web search is disabled, pressing ",(0,r.jsx)(n.kbd,{children:"Enter"})," opens the first matching item instead of web search. This will use that item's configured opening method"]}),"\n",(0,r.jsx)(n.h3,{id:"opening-urls-directly",children:"Opening URLs Directly"}),"\n",(0,r.jsxs)(n.p,{children:["When enabled (with ",(0,r.jsx)(n.code,{children:"appConfig.webSearch.openUrlsDirectly"}),"), if your search query looks like a URL, pressing ",(0,r.jsx)(n.kbd,{children:"Enter"})," will navigate directly to that URL instead of searching for it. Recognized formats include:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Full URLs with scheme: ",(0,r.jsx)(n.code,{children:"https://example.org/path"})]}),"\n",(0,r.jsxs)(n.li,{children:["Dotted hostnames: ",(0,r.jsx)(n.code,{children:"github.com"}),", ",(0,r.jsx)(n.code,{children:"example.com:8080"}),", ",(0,r.jsx)(n.code,{children:"example.com/path"})]}),"\n",(0,r.jsxs)(n.li,{children:["IP addresses: ",(0,r.jsx)(n.code,{children:"192.168.1.1"}),", ",(0,r.jsx)(n.code,{children:"192.168.1.1:8080"}),", ",(0,r.jsx)(n.code,{children:"192.168.1.1/admin"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"localhost"})," (with optional port and/or path): ",(0,r.jsx)(n.code,{children:"localhost"}),", ",(0,r.jsx)(n.code,{children:"localhost:8080"})]}),"\n",(0,r.jsxs)(n.li,{children:["Bare hostnames when followed by a port or path: ",(0,r.jsx)(n.code,{children:"nas:8080"}),", ",(0,r.jsx)(n.code,{children:"router/setup"})]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Note that a bare ambiguous word on its own (e.g. ",(0,r.jsx)(n.code,{children:"nas"}),", ",(0,r.jsx)(n.code,{children:"kubernetes"}),") is still treated as a search query, since the intent isn't clear."]}),"\n",(0,r.jsxs)(n.p,{children:["Set ",(0,r.jsx)(n.code,{children:"appConfig.webSearch.openUrlsDirectly"})," to ",(0,r.jsx)(n.code,{children:"true"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n webSearch:\n openUrlsDirectly: true\n"})}),"\n",(0,r.jsx)(n.h2,{id:"clearing-search",children:"Clearing Search"}),"\n",(0,r.jsxs)(n.p,{children:["You can clear your search term at any time, resting the UI to it's initial state, by pressing ",(0,r.jsx)(n.kbd,{children:"Esc"}),".\nThis can also be used to close any open pop-up modals."]})]})}function d(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>c});var i=s(96540);const r={},a=i.createContext(r);function t(e){const n=i.useContext(a);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/15788f16.28567404.js b/assets/js/15788f16.e089cc89.js similarity index 98% rename from assets/js/15788f16.28567404.js rename to assets/js/15788f16.e089cc89.js index 5bd74dab..96088f3e 100644 --- a/assets/js/15788f16.28567404.js +++ b/assets/js/15788f16.e089cc89.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[7739],{34724(e,s,n){n.r(s),n.d(s,{assets:()=>h,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>t,toc:()=>d});const t=JSON.parse('{"id":"deployment/self-hosted-os","title":"Self-Hosted Operating Systems","description":"The following lists third-party/community-maintained apps and scripts for deploying Dashy on various device types.","source":"@site/docs/deployment/self-hosted-os.md","sourceDirName":"deployment","slug":"/deployment/self-hosted-os","permalink":"/docs/deployment/self-hosted-os","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/deployment/self-hosted-os.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{}}');var r=n(74848),a=n(28453);const o={},i="Self-Hosted Operating Systems",h={},d=[{value:"TrueNAS SCALE",id:"truenas-scale",level:2},{value:"Proxmox VE",id:"proxmox-ve",level:2},{value:"Unraid",id:"unraid",level:2},{value:"Synology NAS",id:"synology-nas",level:2},{value:"NixOS",id:"nixos",level:2},{value:"Kubernetes",id:"kubernetes",level:2}];function l(e){const s={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.header,{children:(0,r.jsx)(s.h1,{id:"self-hosted-operating-systems",children:"Self-Hosted Operating Systems"})}),"\n",(0,r.jsx)(s.p,{children:"The following lists third-party/community-maintained apps and scripts for deploying Dashy on various device types.\nNote that these are unofficial. Please ensure you're using a recent version of Dashy (4.x.x +) before opening issues here."}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"truenas-scale",children:"TrueNAS SCALE"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy is in the TrueNAS community apps catalog. Go to ",(0,r.jsx)(s.strong,{children:"Apps"})," > ",(0,r.jsx)(s.strong,{children:"Discover Apps"}),', search "Dashy", click ',(0,r.jsx)(s.strong,{children:"Install"}),", then set the web port and a host-path storage volume for ",(0,r.jsx)(s.code,{children:"/app/user-data"}),". (",(0,r.jsx)(s.a,{href:"https://apps.truenas.com/catalog/dashy_community/",children:"catalog entry"}),")"]}),"\n",(0,r.jsxs)(s.p,{children:["Older setups may instead find Dashy via ",(0,r.jsx)(s.a,{href:"https://truecharts.org/charts/stable/dashy/",children:"TrueCharts"}),", though TrueCharts is no longer integrated with TrueNAS and its chart may be outdated."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"proxmox-ve",children:"Proxmox VE"}),"\n",(0,r.jsxs)(s.p,{children:["The community-maintained ",(0,r.jsx)(s.a,{href:"https://community-scripts.github.io/ProxmoxVE/",children:"Proxmox VE Helper-Scripts"})," project has a script that spins up Dashy in its own LXC container. Run this in the Proxmox host shell:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:'bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/dashy.sh)"\n'})}),"\n",(0,r.jsxs)(s.p,{children:["See the ",(0,r.jsx)(s.a,{href:"https://community-scripts.github.io/ProxmoxVE/scripts?id=dashy",children:"script page"})," for options. These scripts are community-run (not affiliated with Proxmox or Dashy), so give it a read before running."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"unraid",children:"Unraid"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy is available through the ",(0,r.jsx)(s.a,{href:"https://docs.unraid.net/community-applications/",children:"Community Applications"}),' plugin. Search for "Dashy" in the Apps tab and install from there. The template pre-fills the Docker image, port mapping, and volume paths for you.']}),"\n",(0,r.jsxs)(s.p,{children:["If you'd prefer to set it up manually, go to Docker > Add Container and use ",(0,r.jsx)(s.code,{children:"lissy93/dashy:latest"})," as the repository. Map port ",(0,r.jsx)(s.code,{children:"8080"}),", and add a path mapping for the host directory containing your ",(0,r.jsx)(s.code,{children:"conf.yml"})," to ",(0,r.jsx)(s.code,{children:"/app/user-data"}),"."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"synology-nas",children:"Synology NAS"}),"\n",(0,r.jsxs)(s.p,{children:["On DSM 7.2 and later, Docker is provided by the ",(0,r.jsx)(s.strong,{children:"Container Manager"})," package:"]}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Install ",(0,r.jsx)(s.strong,{children:"Container Manager"})," from the ",(0,r.jsx)(s.strong,{children:"Package Center"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["In ",(0,r.jsx)(s.strong,{children:"File Station"}),", create a folder for Dashy's config (e.g. ",(0,r.jsx)(s.code,{children:"docker/dashy"}),") and put your ",(0,r.jsx)(s.code,{children:"conf.yml"})," (plus any icons/assets) inside it."]}),"\n",(0,r.jsxs)(s.li,{children:["In Container Manager, open ",(0,r.jsx)(s.strong,{children:"Registry"}),", search for ",(0,r.jsx)(s.code,{children:"lissy93/dashy"}),", and download the ",(0,r.jsx)(s.code,{children:"latest"})," tag."]}),"\n",(0,r.jsxs)(s.li,{children:["Under ",(0,r.jsx)(s.strong,{children:"Container"})," > ",(0,r.jsx)(s.strong,{children:"Create"}),", pick the image, enable auto-restart, map a host port (e.g. ",(0,r.jsx)(s.code,{children:"4000"}),") to container port ",(0,r.jsx)(s.code,{children:"8080"}),", and mount your ",(0,r.jsx)(s.code,{children:"docker/dashy"})," folder to ",(0,r.jsx)(s.code,{children:"/app/user-data"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["Alternatively, use Container Manager's ",(0,r.jsx)(s.strong,{children:"Project"})," feature with the ",(0,r.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/docker-compose.yml",children:"docker-compose.yml"})," above, or run it over SSH:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:"docker run -d \\\n -p 4000:8080 \\\n -v /volume1/docker/dashy:/app/user-data \\\n --name dashy \\\n --restart=always \\\n lissy93/dashy:latest\n"})}),"\n",(0,r.jsx)(s.p,{children:"Dashy should be reachable on your chosen port within a minute or two."}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"nixos",children:"NixOS"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy is packaged in ",(0,r.jsx)(s.a,{href:"https://search.nixos.org/packages?query=dashy-ui",children:"nixpkgs"})," as ",(0,r.jsx)(s.code,{children:"dashy-ui"}),", and NixOS ships a ",(0,r.jsx)(s.code,{children:"services.dashy"})," module. Enable it in your configuration:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-nix",children:"services.dashy.enable = true;\n"})}),"\n",(0,r.jsxs)(s.p,{children:["See the ",(0,r.jsx)(s.a,{href:"https://search.nixos.org/options?query=services.dashy",children:"module options"})," for setting the port and your config. Note that the package builds the static app, so the server-only features (status checks, saving config through the UI) aren't available."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"kubernetes",children:"Kubernetes"}),"\n",(0,r.jsx)(s.admonition,{type:"caution",children:(0,r.jsx)(s.p,{children:"This integration may need re-review, to ensure that it's using / compatible with Dashy 4+"})}),"\n",(0,r.jsxs)(s.p,{children:["This uses the Helm chart written by @vyrtualsynthese, which is available ",(0,r.jsx)(s.a,{href:"https://github.com/vyrtualsynthese/selfhosted-helmcharts/tree/main/charts/dashy",children:"here"})]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:"# Add the self-hosted repo\nhelm repo add self-hosted https://vyrtualsynthese.github.io/selfhosted-helmcharts/\nhelm repo update\n\n# Install the dashy chart\nhelm install [RELEASE_NAME] self-hosted/dashy\n"})}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://truecharts.org/charts/stable/dashy/",children:"TrueCharts"})," also publishes a Dashy Helm chart, aimed at Kubernetes and TrueNAS SCALE."]})]})}function c(e={}){const{wrapper:s}={...(0,a.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},28453(e,s,n){n.d(s,{R:()=>o,x:()=>i});var t=n(96540);const r={},a=t.createContext(r);function o(e){const s=t.useContext(a);return t.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function i(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),t.createElement(a.Provider,{value:s},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[7739],{34724(e,s,n){n.r(s),n.d(s,{assets:()=>h,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>t,toc:()=>d});const t=JSON.parse('{"id":"deployment/self-hosted-os","title":"Self-Hosted Operating Systems","description":"The following lists third-party/community-maintained apps and scripts for deploying Dashy on various device types.","source":"@site/docs/deployment/self-hosted-os.md","sourceDirName":"deployment","slug":"/deployment/self-hosted-os","permalink":"/docs/deployment/self-hosted-os","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/deployment/self-hosted-os.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{}}');var r=n(74848),a=n(28453);const o={},i="Self-Hosted Operating Systems",h={},d=[{value:"TrueNAS SCALE",id:"truenas-scale",level:2},{value:"Proxmox VE",id:"proxmox-ve",level:2},{value:"Unraid",id:"unraid",level:2},{value:"Synology NAS",id:"synology-nas",level:2},{value:"NixOS",id:"nixos",level:2},{value:"Kubernetes",id:"kubernetes",level:2}];function l(e){const s={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.header,{children:(0,r.jsx)(s.h1,{id:"self-hosted-operating-systems",children:"Self-Hosted Operating Systems"})}),"\n",(0,r.jsx)(s.p,{children:"The following lists third-party/community-maintained apps and scripts for deploying Dashy on various device types.\nNote that these are unofficial. Please ensure you're using a recent version of Dashy (4.x.x +) before opening issues here."}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"truenas-scale",children:"TrueNAS SCALE"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy is in the TrueNAS community apps catalog. Go to ",(0,r.jsx)(s.strong,{children:"Apps"})," > ",(0,r.jsx)(s.strong,{children:"Discover Apps"}),', search "Dashy", click ',(0,r.jsx)(s.strong,{children:"Install"}),", then set the web port and a host-path storage volume for ",(0,r.jsx)(s.code,{children:"/app/user-data"}),". (",(0,r.jsx)(s.a,{href:"https://apps.truenas.com/catalog/dashy_community/",children:"catalog entry"}),")"]}),"\n",(0,r.jsxs)(s.p,{children:["Older setups may instead find Dashy via ",(0,r.jsx)(s.a,{href:"https://truecharts.org/charts/stable/dashy/",children:"TrueCharts"}),", though TrueCharts is no longer integrated with TrueNAS and its chart may be outdated."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"proxmox-ve",children:"Proxmox VE"}),"\n",(0,r.jsxs)(s.p,{children:["The community-maintained ",(0,r.jsx)(s.a,{href:"https://community-scripts.github.io/ProxmoxVE/",children:"Proxmox VE Helper-Scripts"})," project has a script that spins up Dashy in its own LXC container. Run this in the Proxmox host shell:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:'bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/dashy.sh)"\n'})}),"\n",(0,r.jsxs)(s.p,{children:["See the ",(0,r.jsx)(s.a,{href:"https://community-scripts.github.io/ProxmoxVE/scripts?id=dashy",children:"script page"})," for options. These scripts are community-run (not affiliated with Proxmox or Dashy), so give it a read before running."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"unraid",children:"Unraid"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy is available through the ",(0,r.jsx)(s.a,{href:"https://docs.unraid.net/community-applications/",children:"Community Applications"}),' plugin. Search for "Dashy" in the Apps tab and install from there. The template pre-fills the Docker image, port mapping, and volume paths for you.']}),"\n",(0,r.jsxs)(s.p,{children:["If you'd prefer to set it up manually, go to Docker > Add Container and use ",(0,r.jsx)(s.code,{children:"lissy93/dashy:latest"})," as the repository. Map port ",(0,r.jsx)(s.code,{children:"8080"}),", and add a path mapping for the host directory containing your ",(0,r.jsx)(s.code,{children:"conf.yml"})," to ",(0,r.jsx)(s.code,{children:"/app/user-data"}),"."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"synology-nas",children:"Synology NAS"}),"\n",(0,r.jsxs)(s.p,{children:["On DSM 7.2 and later, Docker is provided by the ",(0,r.jsx)(s.strong,{children:"Container Manager"})," package:"]}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Install ",(0,r.jsx)(s.strong,{children:"Container Manager"})," from the ",(0,r.jsx)(s.strong,{children:"Package Center"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:["In ",(0,r.jsx)(s.strong,{children:"File Station"}),", create a folder for Dashy's config (e.g. ",(0,r.jsx)(s.code,{children:"docker/dashy"}),") and put your ",(0,r.jsx)(s.code,{children:"conf.yml"})," (plus any icons/assets) inside it."]}),"\n",(0,r.jsxs)(s.li,{children:["In Container Manager, open ",(0,r.jsx)(s.strong,{children:"Registry"}),", search for ",(0,r.jsx)(s.code,{children:"lissy93/dashy"}),", and download the ",(0,r.jsx)(s.code,{children:"latest"})," tag."]}),"\n",(0,r.jsxs)(s.li,{children:["Under ",(0,r.jsx)(s.strong,{children:"Container"})," > ",(0,r.jsx)(s.strong,{children:"Create"}),", pick the image, enable auto-restart, map a host port (e.g. ",(0,r.jsx)(s.code,{children:"4000"}),") to container port ",(0,r.jsx)(s.code,{children:"8080"}),", and mount your ",(0,r.jsx)(s.code,{children:"docker/dashy"})," folder to ",(0,r.jsx)(s.code,{children:"/app/user-data"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["Alternatively, use Container Manager's ",(0,r.jsx)(s.strong,{children:"Project"})," feature with the ",(0,r.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/docker-compose.yml",children:"docker-compose.yml"})," above, or run it over SSH:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:"docker run -d \\\n -p 4000:8080 \\\n -v /volume1/docker/dashy:/app/user-data \\\n --name dashy \\\n --restart=always \\\n lissy93/dashy:latest\n"})}),"\n",(0,r.jsx)(s.p,{children:"Dashy should be reachable on your chosen port within a minute or two."}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"nixos",children:"NixOS"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy is packaged in ",(0,r.jsx)(s.a,{href:"https://search.nixos.org/packages?query=dashy-ui",children:"nixpkgs"})," as ",(0,r.jsx)(s.code,{children:"dashy-ui"}),", and NixOS ships a ",(0,r.jsx)(s.code,{children:"services.dashy"})," module. Enable it in your configuration:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-nix",children:"services.dashy.enable = true;\n"})}),"\n",(0,r.jsxs)(s.p,{children:["See the ",(0,r.jsx)(s.a,{href:"https://search.nixos.org/options?query=services.dashy",children:"module options"})," for setting the port and your config. Note that the package builds the static app, so the server-only features (status checks, saving config through the UI) aren't available."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"kubernetes",children:"Kubernetes"}),"\n",(0,r.jsx)(s.admonition,{type:"caution",children:(0,r.jsx)(s.p,{children:"This integration may need re-review, to ensure that it's using / compatible with Dashy 4+"})}),"\n",(0,r.jsxs)(s.p,{children:["This uses the Helm chart written by @vyrtualsynthese, which is available ",(0,r.jsx)(s.a,{href:"https://github.com/vyrtualsynthese/selfhosted-helmcharts/tree/main/charts/dashy",children:"here"})]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:"# Add the self-hosted repo\nhelm repo add self-hosted https://vyrtualsynthese.github.io/selfhosted-helmcharts/\nhelm repo update\n\n# Install the dashy chart\nhelm install [RELEASE_NAME] self-hosted/dashy\n"})}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://truecharts.org/charts/stable/dashy/",children:"TrueCharts"})," also publishes a Dashy Helm chart, aimed at Kubernetes and TrueNAS SCALE."]})]})}function c(e={}){const{wrapper:s}={...(0,a.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},28453(e,s,n){n.d(s,{R:()=>o,x:()=>i});var t=n(96540);const r={},a=t.createContext(r);function o(e){const s=t.useContext(a);return t.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function i(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),t.createElement(a.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/1745c531.2ef3fa78.js b/assets/js/1745c531.8e24c3b8.js similarity index 93% rename from assets/js/1745c531.2ef3fa78.js rename to assets/js/1745c531.8e24c3b8.js index d84a99ca..8c150c5a 100644 --- a/assets/js/1745c531.2ef3fa78.js +++ b/assets/js/1745c531.8e24c3b8.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[3790],{72092(e,t,s){s.r(t),s.d(t,{assets:()=>d,contentTitle:()=>r,default:()=>h,frontMatter:()=>a,metadata:()=>o,toc:()=>i});const o=JSON.parse('{"id":"showcase/readme","title":"readme","description":"See: Showcase.","source":"@site/docs/showcase/readme.md","sourceDirName":"showcase","slug":"/showcase/","permalink":"/docs/showcase/","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/showcase/readme.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{}}');var n=s(74848),c=s(28453);const a={},r=void 0,d={},i=[];function u(e){const t={a:"a",p:"p",...(0,c.R)(),...e.components};return(0,n.jsxs)(t.p,{children:["See: ",(0,n.jsx)(t.a,{href:"/docs/showcase",children:"Showcase"}),"."]})}function h(e={}){const{wrapper:t}={...(0,c.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(u,{...e})}):u(e)}},28453(e,t,s){s.d(t,{R:()=>a,x:()=>r});var o=s(96540);const n={},c=o.createContext(n);function a(e){const t=o.useContext(c);return o.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:a(e.components),o.createElement(c.Provider,{value:t},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[3790],{72092(e,t,s){s.r(t),s.d(t,{assets:()=>d,contentTitle:()=>r,default:()=>h,frontMatter:()=>a,metadata:()=>o,toc:()=>i});const o=JSON.parse('{"id":"showcase/readme","title":"readme","description":"See: Showcase.","source":"@site/docs/showcase/readme.md","sourceDirName":"showcase","slug":"/showcase/","permalink":"/docs/showcase/","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/showcase/readme.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{}}');var n=s(74848),c=s(28453);const a={},r=void 0,d={},i=[];function u(e){const t={a:"a",p:"p",...(0,c.R)(),...e.components};return(0,n.jsxs)(t.p,{children:["See: ",(0,n.jsx)(t.a,{href:"/docs/showcase",children:"Showcase"}),"."]})}function h(e={}){const{wrapper:t}={...(0,c.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(u,{...e})}):u(e)}},28453(e,t,s){s.d(t,{R:()=>a,x:()=>r});var o=s(96540);const n={},c=o.createContext(n);function a(e){const t=o.useContext(c);return o.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:a(e.components),o.createElement(c.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/18ba09e8.de3730e3.js b/assets/js/18ba09e8.add4f6e6.js similarity index 98% rename from assets/js/18ba09e8.de3730e3.js rename to assets/js/18ba09e8.add4f6e6.js index a00b3c11..16813877 100644 --- a/assets/js/18ba09e8.de3730e3.js +++ b/assets/js/18ba09e8.add4f6e6.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[6849],{44580(e,s,t){t.r(s),t.d(s,{assets:()=>h,contentTitle:()=>d,default:()=>l,frontMatter:()=>o,metadata:()=>n,toc:()=>c});const n=JSON.parse('{"id":"credits","title":"Credits","description":"Sponsors","source":"@site/docs/credits.md","sourceDirName":".","slug":"/credits","permalink":"/docs/credits","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/credits.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Privacy & Security","permalink":"/docs/privacy"},"next":{"title":"License","permalink":"/docs/license"}}');var i=t(74848),r=t(28453);const o={},d="Credits",h={},c=[{value:"Sponsors",id:"sponsors",level:2},{value:"Contributors",id:"contributors",level:2},{value:"Newest Stargazers",id:"newest-stargazers",level:2},{value:"Dependencies",id:"dependencies",level:2},{value:"Core",id:"core",level:3},{value:"Utilities",id:"utilities",level:3},{value:"Frontend Components",id:"frontend-components",level:3},{value:"You",id:"you",level:2}];function a(e){const s={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(s.header,{children:(0,i.jsx)(s.h1,{id:"credits",children:"Credits"})}),"\n",(0,i.jsx)(s.h2,{id:"sponsors",children:"Sponsors"}),"\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.img,{src:"https://readme-contribs.as93.net/sponsors/lissy93",alt:"Sponsors"})}),"\n",(0,i.jsx)(s.h2,{id:"contributors",children:"Contributors"}),"\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.img,{src:"https://readme-contribs.as93.net/contributors/lissy93/dashy?perRow=12&limit=96",alt:"Contributors"})}),"\n",(0,i.jsx)(s.h2,{id:"newest-stargazers",children:"Newest Stargazers"}),"\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/stargazers",children:(0,i.jsx)(s.img,{src:"https://readme-contribs.as93.net/stargazers/lissy93/dashy?perRow=16&fontSize=10&limit=96",alt:"Stargazers"})})}),"\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.a,{href:"https://github.com/lissy93/dashy/network/members",children:(0,i.jsx)(s.img,{src:"https://readme-contribs.as93.net/forkers/lissy93/dashy?perRow=16&fontSize=10&limit=96",alt:"Forkers"})})}),"\n",(0,i.jsx)(s.hr,{}),"\n",(0,i.jsx)(s.h2,{id:"dependencies",children:"Dependencies"}),"\n",(0,i.jsx)(s.p,{children:"This app definitely wouldn't have been quite so possible without the making use of the following package and components. Full credit and big kudos to their respective authors, who've done an amazing job in building and maintaining them. Full credit and big kudos to their respective authors, who've done an amazing job in building and maintaining them"}),"\n",(0,i.jsx)(s.h3,{id:"core",children:"Core"}),"\n",(0,i.jsxs)(s.p,{children:["At it's core, the application uses ",(0,i.jsx)(s.a,{href:"https://github.com/vuejs/core",children:(0,i.jsx)(s.strong,{children:"Vue.js"})}),", as well as it's services with ",(0,i.jsx)(s.a,{href:"https://vuex.vuejs.org/",children:(0,i.jsx)(s.strong,{children:"VueX"})})," for state management. Styling is done with ",(0,i.jsx)(s.a,{href:"https://github.com/sass/sass",children:(0,i.jsx)(s.strong,{children:"SCSS"})}),", the app is written in ",(0,i.jsx)(s.a,{href:"https://github.com/Microsoft/TypeScript",children:(0,i.jsx)(s.strong,{children:"TypeScript"})})," and JavaScript, and bundled with ",(0,i.jsx)(s.a,{href:"https://github.com/vitejs/vite",children:(0,i.jsx)(s.strong,{children:"Vite"})}),". Linting is done with ",(0,i.jsx)(s.a,{href:"https://github.com/eslint/eslint",children:(0,i.jsx)(s.strong,{children:"ESLint"})})," and ",(0,i.jsx)(s.a,{href:"https://prettier.io/",children:(0,i.jsx)(s.strong,{children:"Prettier"})}),", both following the ",(0,i.jsx)(s.a,{href:"https://github.com/airbnb/javascript",children:(0,i.jsx)(s.strong,{children:"AirBnB Styleguide"})}),". The config is defined in ",(0,i.jsx)(s.a,{href:"https://github.com/yaml/yaml",children:(0,i.jsx)(s.strong,{children:"YAML"})}),", with a simple ",(0,i.jsx)(s.a,{href:"https://github.com/nodejs/node",children:(0,i.jsx)(s.strong,{children:"Node.js"})})," server to serve up the static app and the optional API endpoints, and container deployment is done with ",(0,i.jsx)(s.a,{href:"https://www.docker.com/",children:(0,i.jsx)(s.strong,{children:"Docker"})}),"."]}),"\n",(0,i.jsx)(s.h3,{id:"utilities",children:"Utilities"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"https://github.com/brix/crypto-js",children:(0,i.jsx)(s.code,{children:"crypto-js"})})," - Encryption implementations by @evanvosberg and community ",(0,i.jsx)(s.code,{children:"MIT"})]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"https://github.com/ajv-validator/ajv",children:(0,i.jsx)(s.code,{children:"ajv"})})," - JSON schema Validator by @epoberezkin and community ",(0,i.jsx)(s.code,{children:"MIT"})]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"https://github.com/kazupon/vue-i18n",children:(0,i.jsx)(s.code,{children:"i18n"})})," - Internationalization plugin by @kazupon and community ",(0,i.jsx)(s.code,{children:"MIT"})]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"https://github.com/frappe/charts",children:(0,i.jsx)(s.code,{children:"frappe-charts"})})," - Lightweight charting library by @frappe ",(0,i.jsx)(s.code,{children:"MIT"})]}),"\n"]}),"\n",(0,i.jsx)(s.h3,{id:"frontend-components",children:"Frontend Components"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"https://github.com/sagalbot/vue-select",children:(0,i.jsx)(s.code,{children:"vue-select"})})," - Dropdown component by @sagalbot ",(0,i.jsx)(s.code,{children:"MIT"})]}),"\n"]}),"\n",(0,i.jsx)(s.hr,{}),"\n",(0,i.jsx)(s.h2,{id:"you",children:"You"}),"\n",(0,i.jsxs)(s.p,{children:["Would you like to be listed here? Whatever your skill set, Dashy needs people like you to help support future development. Check out the ",(0,i.jsx)(s.a,{href:"/docs/contributing",children:"Contributing Page"})," for ways that you can get involved. Huge thank you to everyone who has already contributed! \ud83d\udc96"]})]})}function l(e={}){const{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,i.jsx)(s,{...e,children:(0,i.jsx)(a,{...e})}):a(e)}},28453(e,s,t){t.d(s,{R:()=>o,x:()=>d});var n=t(96540);const i={},r=n.createContext(i);function o(e){const s=n.useContext(r);return n.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function d(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),n.createElement(r.Provider,{value:s},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[6849],{44580(e,s,t){t.r(s),t.d(s,{assets:()=>h,contentTitle:()=>d,default:()=>l,frontMatter:()=>o,metadata:()=>n,toc:()=>c});const n=JSON.parse('{"id":"credits","title":"Credits","description":"Sponsors","source":"@site/docs/credits.md","sourceDirName":".","slug":"/credits","permalink":"/docs/credits","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/credits.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Privacy & Security","permalink":"/docs/privacy"},"next":{"title":"License","permalink":"/docs/license"}}');var i=t(74848),r=t(28453);const o={},d="Credits",h={},c=[{value:"Sponsors",id:"sponsors",level:2},{value:"Contributors",id:"contributors",level:2},{value:"Newest Stargazers",id:"newest-stargazers",level:2},{value:"Dependencies",id:"dependencies",level:2},{value:"Core",id:"core",level:3},{value:"Utilities",id:"utilities",level:3},{value:"Frontend Components",id:"frontend-components",level:3},{value:"You",id:"you",level:2}];function a(e){const s={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(s.header,{children:(0,i.jsx)(s.h1,{id:"credits",children:"Credits"})}),"\n",(0,i.jsx)(s.h2,{id:"sponsors",children:"Sponsors"}),"\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.img,{src:"https://readme-contribs.as93.net/sponsors/lissy93",alt:"Sponsors"})}),"\n",(0,i.jsx)(s.h2,{id:"contributors",children:"Contributors"}),"\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.img,{src:"https://readme-contribs.as93.net/contributors/lissy93/dashy?perRow=12&limit=96",alt:"Contributors"})}),"\n",(0,i.jsx)(s.h2,{id:"newest-stargazers",children:"Newest Stargazers"}),"\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/stargazers",children:(0,i.jsx)(s.img,{src:"https://readme-contribs.as93.net/stargazers/lissy93/dashy?perRow=16&fontSize=10&limit=96",alt:"Stargazers"})})}),"\n",(0,i.jsx)(s.p,{children:(0,i.jsx)(s.a,{href:"https://github.com/lissy93/dashy/network/members",children:(0,i.jsx)(s.img,{src:"https://readme-contribs.as93.net/forkers/lissy93/dashy?perRow=16&fontSize=10&limit=96",alt:"Forkers"})})}),"\n",(0,i.jsx)(s.hr,{}),"\n",(0,i.jsx)(s.h2,{id:"dependencies",children:"Dependencies"}),"\n",(0,i.jsx)(s.p,{children:"This app definitely wouldn't have been quite so possible without the making use of the following package and components. Full credit and big kudos to their respective authors, who've done an amazing job in building and maintaining them. Full credit and big kudos to their respective authors, who've done an amazing job in building and maintaining them"}),"\n",(0,i.jsx)(s.h3,{id:"core",children:"Core"}),"\n",(0,i.jsxs)(s.p,{children:["At it's core, the application uses ",(0,i.jsx)(s.a,{href:"https://github.com/vuejs/core",children:(0,i.jsx)(s.strong,{children:"Vue.js"})}),", as well as it's services with ",(0,i.jsx)(s.a,{href:"https://vuex.vuejs.org/",children:(0,i.jsx)(s.strong,{children:"VueX"})})," for state management. Styling is done with ",(0,i.jsx)(s.a,{href:"https://github.com/sass/sass",children:(0,i.jsx)(s.strong,{children:"SCSS"})}),", the app is written in ",(0,i.jsx)(s.a,{href:"https://github.com/Microsoft/TypeScript",children:(0,i.jsx)(s.strong,{children:"TypeScript"})})," and JavaScript, and bundled with ",(0,i.jsx)(s.a,{href:"https://github.com/vitejs/vite",children:(0,i.jsx)(s.strong,{children:"Vite"})}),". Linting is done with ",(0,i.jsx)(s.a,{href:"https://github.com/eslint/eslint",children:(0,i.jsx)(s.strong,{children:"ESLint"})})," and ",(0,i.jsx)(s.a,{href:"https://prettier.io/",children:(0,i.jsx)(s.strong,{children:"Prettier"})}),", both following the ",(0,i.jsx)(s.a,{href:"https://github.com/airbnb/javascript",children:(0,i.jsx)(s.strong,{children:"AirBnB Styleguide"})}),". The config is defined in ",(0,i.jsx)(s.a,{href:"https://github.com/yaml/yaml",children:(0,i.jsx)(s.strong,{children:"YAML"})}),", with a simple ",(0,i.jsx)(s.a,{href:"https://github.com/nodejs/node",children:(0,i.jsx)(s.strong,{children:"Node.js"})})," server to serve up the static app and the optional API endpoints, and container deployment is done with ",(0,i.jsx)(s.a,{href:"https://www.docker.com/",children:(0,i.jsx)(s.strong,{children:"Docker"})}),"."]}),"\n",(0,i.jsx)(s.h3,{id:"utilities",children:"Utilities"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"https://github.com/brix/crypto-js",children:(0,i.jsx)(s.code,{children:"crypto-js"})})," - Encryption implementations by @evanvosberg and community ",(0,i.jsx)(s.code,{children:"MIT"})]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"https://github.com/ajv-validator/ajv",children:(0,i.jsx)(s.code,{children:"ajv"})})," - JSON schema Validator by @epoberezkin and community ",(0,i.jsx)(s.code,{children:"MIT"})]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"https://github.com/kazupon/vue-i18n",children:(0,i.jsx)(s.code,{children:"i18n"})})," - Internationalization plugin by @kazupon and community ",(0,i.jsx)(s.code,{children:"MIT"})]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"https://github.com/frappe/charts",children:(0,i.jsx)(s.code,{children:"frappe-charts"})})," - Lightweight charting library by @frappe ",(0,i.jsx)(s.code,{children:"MIT"})]}),"\n"]}),"\n",(0,i.jsx)(s.h3,{id:"frontend-components",children:"Frontend Components"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.a,{href:"https://github.com/sagalbot/vue-select",children:(0,i.jsx)(s.code,{children:"vue-select"})})," - Dropdown component by @sagalbot ",(0,i.jsx)(s.code,{children:"MIT"})]}),"\n"]}),"\n",(0,i.jsx)(s.hr,{}),"\n",(0,i.jsx)(s.h2,{id:"you",children:"You"}),"\n",(0,i.jsxs)(s.p,{children:["Would you like to be listed here? Whatever your skill set, Dashy needs people like you to help support future development. Check out the ",(0,i.jsx)(s.a,{href:"/docs/contributing",children:"Contributing Page"})," for ways that you can get involved. Huge thank you to everyone who has already contributed! \ud83d\udc96"]})]})}function l(e={}){const{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,i.jsx)(s,{...e,children:(0,i.jsx)(a,{...e})}):a(e)}},28453(e,s,t){t.d(s,{R:()=>o,x:()=>d});var n=t(96540);const i={},r=n.createContext(i);function o(e){const s=n.useContext(r);return n.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function d(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),n.createElement(r.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/1f5fb0df.9039ab38.js b/assets/js/1f5fb0df.cba70676.js similarity index 99% rename from assets/js/1f5fb0df.9039ab38.js rename to assets/js/1f5fb0df.cba70676.js index acf7a77c..0c2d0cb9 100644 --- a/assets/js/1f5fb0df.9039ab38.js +++ b/assets/js/1f5fb0df.cba70676.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[9695],{76644(e,n,s){s.r(n),s.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>t,metadata:()=>i,toc:()=>l});const i=JSON.parse('{"id":"management","title":"App Management","description":"The following article is a primer on managing self-hosted apps. It covers everything from keeping the Dashy (or any other app) up-to-date, secure, backed up, to other topics like auto-starting, monitoring, log management, web server configuration and using custom domains.","source":"@site/docs/management.md","sourceDirName":".","slug":"/management","permalink":"/docs/management","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/management.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Configuring","permalink":"/docs/configuring"},"next":{"title":"Troubleshooting","permalink":"/docs/troubleshooting"}}');var r=s(74848),o=s(28453);const t={},a="App Management",c={},l=[{value:"Contents",id:"contents",level:2},{value:"Providing Assets",id:"providing-assets",level:2},{value:"File Ownership and Permissions",id:"file-ownership-and-permissions",level:2},{value:"Running Commands",id:"running-commands",level:2},{value:"Healthchecks",id:"healthchecks",level:2},{value:"HTTP Healthcheck Endpoint",id:"http-healthcheck-endpoint",level:3},{value:"Logs and Performance",id:"logs-and-performance",level:2},{value:"Container Logs",id:"container-logs",level:3},{value:"Container Performance",id:"container-performance",level:3},{value:"Management Apps",id:"management-apps",level:3},{value:"Advanced Logging and Monitoring",id:"advanced-logging-and-monitoring",level:3},{value:"Auto-Starting at System Boot",id:"auto-starting-at-system-boot",level:2},{value:"Updating",id:"updating",level:2},{value:"Updating Docker Container",id:"updating-docker-container",level:3},{value:"Automatic Docker Updates",id:"automatic-docker-updates",level:3},{value:"Updating Dashy from Source",id:"updating-dashy-from-source",level:3},{value:"Verifying a Release Download",id:"verifying-a-release-download",level:3},{value:"Backing Up",id:"backing-up",level:2},{value:"Backing Up Containers",id:"backing-up-containers",level:3},{value:"Backing Up Volumes",id:"backing-up-volumes",level:3},{value:"Dashy-Specific Backup",id:"dashy-specific-backup",level:3},{value:"Scheduling",id:"scheduling",level:2},{value:"SSL Certificates",id:"ssl-certificates",level:2},{value:"Auto-SSL",id:"auto-ssl",level:3},{value:"Getting a Self-Signed SSL Certificate",id:"getting-a-self-signed-ssl-certificate",level:3},{value:"Passing a Self-Signed Certificate to Dashy",id:"passing-a-self-signed-certificate-to-dashy",level:3},{value:"Authentication",id:"authentication",level:2},{value:"Network Exposure",id:"network-exposure",level:2},{value:"Managing Containers with Docker Compose",id:"managing-containers-with-docker-compose",level:2},{value:"Passing in Environmental Variables",id:"passing-in-environmental-variables",level:2},{value:"Setting Headers",id:"setting-headers",level:2},{value:"Example Headers",id:"example-headers",level:3},{value:"Caddy",id:"caddy",level:4},{value:"NGINX",id:"nginx",level:4},{value:"Traefik",id:"traefik",level:4},{value:"HAProxy",id:"haproxy",level:4},{value:"Apache",id:"apache",level:4},{value:"Squid",id:"squid",level:4},{value:"Remote Access",id:"remote-access",level:2},{value:"WireGuard",id:"wireguard",level:3},{value:"Example Server Config",id:"example-server-config",level:4},{value:"Example Client Config",id:"example-client-config",level:4},{value:"Reverse SSH Tunnel",id:"reverse-ssh-tunnel",level:3},{value:"TCP Tunnel",id:"tcp-tunnel",level:3},{value:"Custom Domain",id:"custom-domain",level:2},{value:"Using DNS",id:"using-dns",level:3},{value:"Using NGINX",id:"using-nginx",level:3},{value:"Verifying Releases",id:"verifying-releases",level:2},{value:"Container Security",id:"container-security",level:2},{value:"Keep Docker Up-To-Date",id:"keep-docker-up-to-date",level:3},{value:"Set Resource Quotas",id:"set-resource-quotas",level:3},{value:"Don't Run as Root",id:"dont-run-as-root",level:3},{value:"Specify a User",id:"specify-a-user",level:3},{value:"Limit capabilities",id:"limit-capabilities",level:3},{value:"Prevent new Privileges being Added",id:"prevent-new-privileges-being-added",level:3},{value:"Disable Inter-Container Communication",id:"disable-inter-container-communication",level:3},{value:"Don't Expose the Docker Daemon Socket",id:"dont-expose-the-docker-daemon-socket",level:3},{value:"Use Read-Only Volumes",id:"use-read-only-volumes",level:3},{value:"Set the Logging Level",id:"set-the-logging-level",level:3},{value:"Verify Image before Pulling",id:"verify-image-before-pulling",level:3},{value:"Specify the Tag",id:"specify-the-tag",level:3},{value:"Container Security Scanning",id:"container-security-scanning",level:3},{value:"Registry Security",id:"registry-security",level:3},{value:"Security Modules",id:"security-modules",level:3},{value:"Web Server Configuration",id:"web-server-configuration",level:2},{value:"NGINX",id:"nginx-1",level:3},{value:"Apache",id:"apache-1",level:3},{value:"Caddy",id:"caddy-1",level:3},{value:"Firebase Hosting",id:"firebase-hosting",level:3},{value:"cPanel",id:"cpanel",level:3},{value:"Running a Modified Version of the App",id:"running-a-modified-version-of-the-app",level:2},{value:"Building your Own Container",id:"building-your-own-container",level:2}];function d(e){const n={a:"a",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"app-management",children:"App Management"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"The following article is a primer on managing self-hosted apps. It covers everything from keeping the Dashy (or any other app) up-to-date, secure, backed up, to other topics like auto-starting, monitoring, log management, web server configuration and using custom domains."})}),"\n",(0,r.jsx)(n.h2,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#providing-assets",children:"Providing Assets"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#file-ownership-and-permissions",children:"File Ownership and Permissions"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#running-commands",children:"Running Commands"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#healthchecks",children:"Healthchecks"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#logs-and-performance",children:"Logs and Performance"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#auto-starting-at-system-boot",children:"Auto-Starting at Boot"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#updating",children:"Updating"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#backing-up",children:"Backing Up"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#scheduling",children:"Scheduling"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ssl-certificates",children:"SSL Certificates"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#authentication",children:"Authentication"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#network-exposure",children:"Network Exposure"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#managing-containers-with-docker-compose",children:"Managing with Compose"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#passing-in-environmental-variables",children:"Environmental Variables"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#setting-headers",children:"Setting Headers"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#remote-access",children:"Remote Access"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#custom-domain",children:"Custom Domain"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#verifying-releases",children:"Verifying Releases"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#container-security",children:"Securing Containers"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#web-server-configuration",children:"Web Server Configuration"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#running-a-modified-version-of-the-app",children:"Running a Modified App"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#building-your-own-container",children:"Building your Own Container"})}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"providing-assets",children:"Providing Assets"}),"\n",(0,r.jsxs)(n.p,{children:["Dashy reads everything for your dashboard from a single host directory mounted into the container at ",(0,r.jsx)(n.code,{children:"/app/user-data"}),". This is done with a ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/storage/volumes/",children:"Docker volume"}),", e.g. ",(0,r.jsx)(n.code,{children:"-v /path/to/your/user-data:/app/user-data"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["The directory must contain a ",(0,r.jsx)(n.code,{children:"conf.yml"}),". It can also contain anything else you want served from the web root: sub-config files for additional pages, item icons, favicon, fonts, custom CSS, manifest, and so on. Any file placed there is reachable at ",(0,r.jsx)(n.code,{children:"/"})," in the browser, overriding files of the same name in the bundled ",(0,r.jsx)(n.code,{children:"public/"})," defaults."]}),"\n",(0,r.jsx)(n.p,{children:"Typical contents:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"conf.yml"})," - Main config (required)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"*.yml"})," / ",(0,r.jsx)(n.code,{children:"*.yaml"})," - Sub-config files for ",(0,r.jsx)(n.a,{href:"/docs/pages-and-sections#multi-page-support",children:"multi-page support"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"item-icons/"})," - Local icons referenced by ",(0,r.jsx)(n.code,{children:"icon: ./item-icons/foo.png"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"favicon.ico"}),", ",(0,r.jsx)(n.code,{children:"manifest.json"}),", ",(0,r.jsx)(n.code,{children:"robots.txt"})," - Override the bundled defaults"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"fonts/"}),", ",(0,r.jsx)(n.code,{children:"widget-resources/"})," - Custom fonts or assets used by widgets"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"file-ownership-and-permissions",children:"File Ownership and Permissions"}),"\n",(0,r.jsxs)(n.p,{children:["Inside the container, Dashy runs as a non-root user with uid/gid 1000 (the built-in ",(0,r.jsx)(n.code,{children:"node"})," user from the Node base image). This is fine for the vast majority of installs, since the first user on a default Linux or macOS box is also uid 1000, so a bind-mounted ",(0,r.jsx)(n.code,{children:"user-data"})," directory is read/writable straight away."]}),"\n",(0,r.jsx)(n.p,{children:"It only gets fiddly if your host uid happens to be something else (NAS systems and multi-user servers being the usual culprits). In that case, config saves from the UI will fail with a permission error, because the container's uid 1000 doesn't own your directory."}),"\n",(0,r.jsx)(n.p,{children:"There are two ways to sort it. Pick whichever is less hassle:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Run the container as your own user."})," The cleanest option, since you don't touch host file ownership."]}),"\n",(0,r.jsxs)(n.p,{children:["On ",(0,r.jsx)(n.code,{children:"docker run"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d -p 8080:8080 \\\n --user $(id -u):$(id -g) \\\n -v /path/to/user-data:/app/user-data \\\n lissy93/dashy:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["In compose, uncomment the ",(0,r.jsx)(n.code,{children:"user:"})," line under the service and set it:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'user: "1001:1001" # whatever `id -u` and `id -g` give you\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Hand the directory to uid 1000."})," Quicker if you don't mind changing host ownership:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"sudo chown -R 1000:1000 /path/to/user-data\n"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Note that if you run the container as root (e.g. ",(0,r.jsx)(n.code,{children:"--user 0:0"}),"), Dashy will still work, but you lose the security benefit of a non-root container. Don't do that unless you've a good reason."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"running-commands",children:"Running Commands"}),"\n",(0,r.jsxs)(n.p,{children:["If you're running an app in Docker, then commands will need to be passed to the container to be executed. This can be done by preceding each command with ",(0,r.jsx)(n.code,{children:"docker exec -it [container-id]"}),", where container ID can be found by running ",(0,r.jsx)(n.code,{children:"docker ps"}),". For example ",(0,r.jsx)(n.code,{children:"docker exec -it 26c156c467b4 yarn build"}),". You can also enter the container, with ",(0,r.jsx)(n.code,{children:"docker exec -it [container-id] /bin/ash"}),", and navigate around it with normal Linux commands."]}),"\n",(0,r.jsxs)(n.p,{children:["Dashy has several commands that can be used for various tasks, you can find a list of these either in the ",(0,r.jsx)(n.a,{href:"/docs/developing#project-commands",children:"Developing Docs"}),", or by looking at the ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/package.json#l5",children:(0,r.jsx)(n.code,{children:"package.json"})}),". These can be used by running ",(0,r.jsx)(n.code,{children:"yarn [command-name]"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"healthchecks",children:"Healthchecks"}),"\n",(0,r.jsxs)(n.p,{children:["Healthchecks are configured to periodically check that Dashy is up and running correctly on the specified port. By default, the health script is called every 5 minutes, but this can be modified with the ",(0,r.jsx)(n.code,{children:"--health-interval"})," option. You can check the current container health with: ",(0,r.jsx)(n.code,{children:'docker inspect --format "{{json .State.Health }}" [container-id]'}),", and a summary of health status will show up under ",(0,r.jsx)(n.code,{children:"docker ps"}),". You can also manually request the current application status by running ",(0,r.jsx)(n.code,{children:"docker exec -it [container-id] yarn health-check"}),". You can disable healthchecks altogether by adding the ",(0,r.jsx)(n.code,{children:"--no-healthcheck"})," flag to your Docker run command."]}),"\n",(0,r.jsxs)(n.p,{children:["To restart unhealthy containers automatically, check out ",(0,r.jsx)(n.a,{href:"https://hub.docker.com/r/willfarrell/autoheal/",children:"Autoheal"}),". This image watches for unhealthy containers, and automatically triggers a restart. (This is a stand in for Docker's ",(0,r.jsx)(n.code,{children:"--exit-on-unhealthy"})," that was proposed, but ",(0,r.jsx)(n.a,{href:"https://github.com/moby/moby/pull/22719",children:"not merged"}),"). There's also ",(0,r.jsx)(n.a,{href:"https://github.com/qdm12/deunhealth",children:"Deunhealth"}),", which is super light-weight, and doesn't require network access."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d \\\n --name autoheal \\\n --restart=always \\\n -e AUTOHEAL_CONTAINER_LABEL=all \\\n -v /var/run/docker.sock:/var/run/docker.sock \\\n willfarrell/autoheal\n"})}),"\n",(0,r.jsx)(n.h3,{id:"http-healthcheck-endpoint",children:"HTTP Healthcheck Endpoint"}),"\n",(0,r.jsxs)(n.p,{children:["Dashy also exposes an unauthenticated HTTP liveness endpoint at ",(0,r.jsx)(n.code,{children:"/healthz"}),", which returns a ",(0,r.jsx)(n.code,{children:"200"})," with a small JSON body (",(0,r.jsx)(n.code,{children:"status"}),", ",(0,r.jsx)(n.code,{children:"uptime"}),", ",(0,r.jsx)(n.code,{children:"version"}),"). It bypasses auth and SSL redirection so probes keep working regardless of how Dashy is configured."]}),"\n",(0,r.jsx)(n.p,{children:"Useful when fronting Dashy with a load balancer / reverse proxy that needs an HTTP probe for auto-failover, or when running on Kubernetes:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"# Kubernetes\nlivenessProbe:\n httpGet:\n path: /healthz\n port: 8080\n"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'# Traefik (label on the Dashy service)\n- "traefik.http.services.dashy.loadbalancer.healthcheck.path=/healthz"\n- "traefik.http.services.dashy.loadbalancer.healthcheck.interval=30s"\n'})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-caddyfile",children:"# Caddy (reverse_proxy block)\nreverse_proxy dashy:8080 {\n health_uri /healthz\n health_interval 30s\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["For Nginx Proxy Manager, set the ",(0,r.jsx)(n.em,{children:"Forward Hostname"})," health-check path to ",(0,r.jsx)(n.code,{children:"/healthz"})," under the proxy host's ",(0,r.jsx)(n.em,{children:"Custom locations"})," / advanced config."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"logs-and-performance",children:"Logs and Performance"}),"\n",(0,r.jsx)(n.h3,{id:"container-logs",children:"Container Logs"}),"\n",(0,r.jsxs)(n.p,{children:["You can view logs for a given Docker container with ",(0,r.jsx)(n.code,{children:"docker logs [container-id]"}),", add the ",(0,r.jsx)(n.code,{children:"--follow"})," flag to stream the logs. For more info, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/containers/logging/",children:"Logging Documentation"}),". There's also ",(0,r.jsx)(n.a,{href:"https://dozzle.dev/",children:"Dozzle"}),", a useful tool, that provides a web interface where you can stream and query logs from all your running containers from a single web app."]}),"\n",(0,r.jsx)(n.h3,{id:"container-performance",children:"Container Performance"}),"\n",(0,r.jsxs)(n.p,{children:["You can check the resource usage for your running Docker containers with ",(0,r.jsx)(n.code,{children:"docker stats"})," or ",(0,r.jsx)(n.code,{children:"docker stats [container-id]"}),". For more info, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/stats/",children:"Stats Documentation"}),". There's also ",(0,r.jsx)(n.a,{href:"https://github.com/google/cadvisor",children:"cAdvisor"}),", a useful web app for viewing and analyzing resource usage and performance of all your running containers."]}),"\n",(0,r.jsx)(n.h3,{id:"management-apps",children:"Management Apps"}),"\n",(0,r.jsxs)(n.p,{children:["You can also view logs, resource usage and other info as well as manage your entire Docker workflow in third-party Docker management apps. For example ",(0,r.jsx)(n.a,{href:"https://github.com/portainer/portainer",children:"Portainer"})," an all-in-one open source management web UI for Docker and Kubernetes, or ",(0,r.jsx)(n.a,{href:"https://github.com/jesseduffield/lazydocker",children:"LazyDocker"})," a terminal UI for Docker container management and monitoring."]}),"\n",(0,r.jsx)(n.h3,{id:"advanced-logging-and-monitoring",children:"Advanced Logging and Monitoring"}),"\n",(0,r.jsxs)(n.p,{children:["Docker supports using ",(0,r.jsx)(n.a,{href:"https://prometheus.io/",children:"Prometheus"})," to collect logs, which can then be visualized using a platform like ",(0,r.jsx)(n.a,{href:"https://grafana.com/",children:"Grafana"}),". For more info, see ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/daemon/prometheus/",children:"this guide"}),". If you need to route your logs to a remote syslog, then consider using ",(0,r.jsx)(n.a,{href:"https://github.com/gliderlabs/logspout",children:"logspout"}),". For enterprise-grade instances, there are managed services, that make monitoring container logs and metrics very easy, such as ",(0,r.jsx)(n.a,{href:"https://sematext.com/blog/docker-container-monitoring-with-sematext/",children:"Sematext"})," with ",(0,r.jsx)(n.a,{href:"https://github.com/sematext/logagent-js",children:"Logagent"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"auto-starting-at-system-boot",children:"Auto-Starting at System Boot"}),"\n",(0,r.jsxs)(n.p,{children:["You can use Docker's ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/run/#restart-policies---restart",children:"restart policies"})," to instruct the container to start after a system reboot, or restart after a crash. Just add the ",(0,r.jsx)(n.code,{children:"--restart=always"})," flag to your Docker compose script or Docker run command. For more information, see the docs on ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/containers/start-containers-automatically/",children:"Starting Containers Automatically"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["For Podman, you can use ",(0,r.jsx)(n.code,{children:"systemd"})," to create a service that launches your container, ",(0,r.jsx)(n.a,{href:"https://podman.io/blogs/2018/09/13/systemd.html",children:"the docs"})," explains things further. A similar approach can be used with Docker, if you need to start containers after a reboot, but before any user interaction."]}),"\n",(0,r.jsxs)(n.p,{children:["To restart the container after something within it has crashed, consider using ",(0,r.jsx)(n.a,{href:"https://github.com/willfarrell/docker-autoheal",children:(0,r.jsx)(n.code,{children:"docker-autoheal"})})," by @willfarrell, a service that monitors and restarts unhealthy containers. For more info, see the ",(0,r.jsx)(n.a,{href:"#healthchecks",children:"Healthchecks"})," section above."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"updating",children:"Updating"}),"\n",(0,r.jsx)(n.p,{children:"Dashy is under active development, so to take advantage of the latest features, you may need to update your instance every now and again."}),"\n",(0,r.jsx)(n.h3,{id:"updating-docker-container",children:"Updating Docker Container"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Pull latest image: ",(0,r.jsx)(n.code,{children:"docker pull lissy93/dashy:latest"})]}),"\n",(0,r.jsxs)(n.li,{children:["Kill off existing container\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Find container ID: ",(0,r.jsx)(n.code,{children:"docker ps"})]}),"\n",(0,r.jsxs)(n.li,{children:["Stop container: ",(0,r.jsx)(n.code,{children:"docker stop [container_id]"})]}),"\n",(0,r.jsxs)(n.li,{children:["Remove container: ",(0,r.jsx)(n.code,{children:"docker rm [container_id]"})]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Spin up new container: ",(0,r.jsx)(n.code,{children:"docker run [params] lissy93/dashy"})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"automatic-docker-updates",children:"Automatic Docker Updates"}),"\n",(0,r.jsxs)(n.p,{children:["You can automate the above process using ",(0,r.jsx)(n.a,{href:"https://github.com/containrrr/watchtower",children:"Watchtower"}),".\nWatchtower will watch for new versions of a given image on Docker Hub, pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially."]}),"\n",(0,r.jsx)(n.p,{children:"To get started, spin up the watchtower container:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d \\\n --name watchtower \\\n -v /var/run/docker.sock:/var/run/docker.sock \\\n containrrr/watchtower\n"})}),"\n",(0,r.jsxs)(n.p,{children:["For more information, see the ",(0,r.jsx)(n.a,{href:"https://containrrr.dev/watchtower/",children:"Watchtower Docs"})]}),"\n",(0,r.jsx)(n.h3,{id:"updating-dashy-from-source",children:"Updating Dashy from Source"}),"\n",(0,r.jsxs)(n.p,{children:["Stop your current instance of Dashy, then navigate into the source directory. Pull down the latest code, with ",(0,r.jsx)(n.code,{children:"git pull origin master"}),", then update dependencies with ",(0,r.jsx)(n.code,{children:"yarn"}),", rebuild with ",(0,r.jsx)(n.code,{children:"yarn build"}),", and start the server again with ",(0,r.jsx)(n.code,{children:"yarn start"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"verifying-a-release-download",children:"Verifying a Release Download"}),"\n",(0,r.jsxs)(n.p,{children:["Each ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/releases",children:"GitHub release"})," bundles a SHA256 checksum and a SLSA build-provenance attestation alongside the source tarball (",(0,r.jsx)(n.code,{children:"dashy-.tar.gz"}),"). You don't need either to run Dashy, but they let you confirm a download arrived intact and was genuinely built from our source, rather than tampered with in transit or on a mirror."]}),"\n",(0,r.jsxs)(n.p,{children:["Check the tarball is intact using the ",(0,r.jsx)(n.code,{children:".sha256"})," file published next to it:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"sha256sum -c dashy-.tar.gz.sha256\n"})}),"\n",(0,r.jsxs)(n.p,{children:["An ",(0,r.jsx)(n.code,{children:"OK"})," means the file is untampered. To go further and prove it was built by our CI from our repo, verify the attestation with the ",(0,r.jsx)(n.a,{href:"https://cli.github.com/",children:"GitHub CLI"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"gh attestation verify dashy-.tar.gz --repo lissy93/dashy\n"})}),"\n",(0,r.jsx)(n.p,{children:"The release notes for each version also list the checksum and a link to view the attestation directly."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"backing-up",children:"Backing Up"}),"\n",(0,r.jsx)(n.h3,{id:"backing-up-containers",children:"Backing Up Containers"}),"\n",(0,r.jsxs)(n.p,{children:["You can make a backup of any running container really easily, using ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/commit/",children:(0,r.jsx)(n.code,{children:"docker commit"})})," and save it with ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/export/",children:(0,r.jsx)(n.code,{children:"docker export"})}),", to do so:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["First find the container ID, you can do this with ",(0,r.jsx)(n.code,{children:"docker container ls"})]}),"\n",(0,r.jsxs)(n.li,{children:["Now to create the snapshot, just run ",(0,r.jsx)(n.code,{children:"docker commit -p [container-id] my-backup"})]}),"\n",(0,r.jsxs)(n.li,{children:["Finally, to save the backup locally, run ",(0,r.jsx)(n.code,{children:"docker save -o ~/dashy-backup.tar my-backup"})]}),"\n",(0,r.jsxs)(n.li,{children:["If you want to push this to a container registry, run ",(0,r.jsx)(n.code,{children:"docker push my-backup:latest"})]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Note that this will not include any data in docker volumes, and the process here is a bit different. Since these files exist on your host system, if you have an existing backup solution implemented, you can incorporate and volume files within that system."}),"\n",(0,r.jsx)(n.h3,{id:"backing-up-volumes",children:"Backing Up Volumes"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/offen/docker-volume-backup",children:"offen/docker-volume-backup"})," is a useful tool for periodic Docker volume backups, to any S3-compatible storage provider. It's run as a light-weight Docker container, and is easy to setup, and also supports GPG-encryption, email notification, and routing away older backups."]}),"\n",(0,r.jsxs)(n.p,{children:["To get started, create a docker-compose similar to the example below, and then start the container. For more info, check out their ",(0,r.jsx)(n.a,{href:"https://github.com/offen/docker-volume-backup",children:"documentation"}),", which is very clear."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'services:\n backup:\n image: offen/docker-volume-backup:latest\n environment:\n BACKUP_CRON_EXPRESSION: "0 * * * *"\n BACKUP_PRUNING_PREFIX: backup-\n BACKUP_RETENTION_DAYS: 7\n AWS_BUCKET_NAME: backup-bucket\n AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE\n AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n volumes:\n - data:/backup/my-app-backup:ro\n - /var/run/docker.sock:/var/run/docker.sock:ro\nvolumes:\n data:\n'})}),"\n",(0,r.jsx)(n.p,{children:"It's worth noting that this process can also be done manually, using the following commands:"}),"\n",(0,r.jsx)(n.p,{children:"Backup:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run --rm -v some_volume:/volume -v /tmp:/backup alpine tar -cjf /backup/some_archive.tar.bz2 -C /volume ./\n"})}),"\n",(0,r.jsx)(n.p,{children:"Restore:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'docker run --rm -v some_volume:/volume -v /tmp:/backup alpine sh -c "rm -rf /volume/* /volume/..?* /volume/.[!.]* ; tar -C /volume/ -xjf /backup/some_archive.tar.bz2"\n'})}),"\n",(0,r.jsx)(n.h3,{id:"dashy-specific-backup",children:"Dashy-Specific Backup"}),"\n",(0,r.jsxs)(n.p,{children:["All configuration and dashboard settings are stored in your ",(0,r.jsx)(n.code,{children:"user-data/conf.yml"})," file. If you provide additional assets (like icons, fonts, themes, etc), these will also live in the ",(0,r.jsx)(n.code,{children:"user-data"})," directory. So to backup all Dashy data, this is the only directory you need to backup."]}),"\n",(0,r.jsxs)(n.p,{children:["When you save config through the UI, Dashy automatically creates a timestamped backup in ",(0,r.jsx)(n.code,{children:"user-data/config-backups/"})," (configurable via the ",(0,r.jsx)(n.code,{children:"BACKUP_DIR"})," env var). If you break your config, check that directory for a recent copy. Backups can be disabled by setting ",(0,r.jsx)(n.code,{children:"DISABLE_CONFIG_BACKUPS=true"})," (e.g. on read-only filesystems or where permissions don't allow it)."]}),"\n",(0,r.jsx)(n.p,{children:"Since Dashy is open source, there shouldn't be any need to backup the main container."}),"\n",(0,r.jsxs)(n.p,{children:["Dashy also has a built-in cloud backup feature, which is free for personal users, and will let you make and restore fully encrypted backups of your config directly through the UI. To learn more, see the ",(0,r.jsx)(n.a,{href:"/docs/backup-restore",children:"Cloud Backup Docs"})]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"scheduling",children:"Scheduling"}),"\n",(0,r.jsxs)(n.p,{children:["If you need to periodically schedule the running of a given command on Dashy (or any other container), then a useful tool for doing so it ",(0,r.jsx)(n.a,{href:"https://github.com/mcuadros/ofelia",children:"ofelia"}),". This runs as a Docker container and is really useful for things like backups, logging, updating, notifications, etc. Crons are specified using Go's crontab format, and a useful tool for visualizing this is ",(0,r.jsx)(n.a,{href:"https://crontab.guru/",children:"crontab.guru"}),". This can also be done natively with Alpine: ",(0,r.jsx)(n.code,{children:"docker run -it alpine ls /etc/periodic"}),".\nI recommend combining this with ",(0,r.jsx)(n.a,{href:"https://github.com/healthchecks/healthchecks",children:"healthchecks"})," for easy monitoring of jobs, and failure notifications."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"ssl-certificates",children:"SSL Certificates"}),"\n",(0,r.jsx)(n.p,{children:"Enabling HTTPS with an SSL certificate is recommended, especially if you are hosting Dashy anywhere other than your home. This will ensure that all traffic is encrypted in transit."}),"\n",(0,r.jsx)(n.h3,{id:"auto-ssl",children:"Auto-SSL"}),"\n",(0,r.jsxs)(n.p,{children:["If you are using ",(0,r.jsx)(n.a,{href:"https://nginxproxymanager.com/",children:"NGINX Proxy Manager"}),', then SSL is supported out of the box. Once you\'ve added your proxy host and web address, then set the scheme to HTTPS, then under the SSL Tab select "Request a new SSL certificate" and follow the on-screen instructions.']}),"\n",(0,r.jsxs)(n.p,{children:["If you're hosting Dashy behind Cloudflare, then they offer ",(0,r.jsx)(n.a,{href:"https://www.cloudflare.com/en-gb/learning/ssl/what-is-an-ssl-certificate/",children:"free and easy SSL"}),"- all you need to do is enable it under the SSL/TLS tab. Or if you are using shared hosting, you may find ",(0,r.jsx)(n.a,{href:"https://www.sitepoint.com/a-guide-to-setting-up-lets-encrypt-ssl-on-shared-hosting/",children:"this tutorial"})," helpful."]}),"\n",(0,r.jsx)(n.h3,{id:"getting-a-self-signed-ssl-certificate",children:"Getting a Self-Signed SSL Certificate"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://letsencrypt.org/docs/",children:"Let's Encrypt"})," is a global Certificate Authority, providing free SSL/TLS Domain Validation certificates in order to enable secure HTTPS access to your website. They have good browser/ OS ",(0,r.jsx)(n.a,{href:"https://letsencrypt.org/docs/certificate-compatibility/",children:"compatibility"})," with their ISRG X1 and DST CA X3 root certificates, support ",(0,r.jsx)(n.a,{href:"https://community.letsencrypt.org/t/acme-v2-production-environment-wildcards/55578",children:"Wildcard issuance"})," done via ACMEv2 using the DNS-01 and have ",(0,r.jsx)(n.a,{href:"https://letsencrypt.org/2020/02/19/multi-perspective-validation.html",children:"Multi-Perspective Validation"}),". Let's Encrypt provide ",(0,r.jsx)(n.a,{href:"https://certbot.eff.org/",children:"CertBot"})," an easy app for generating and setting up an SSL certificate."]}),"\n",(0,r.jsxs)(n.p,{children:["This process can be automated, using something like the ",(0,r.jsx)(n.a,{href:"https://github.com/Valian/docker-nginx-auto-ssl",children:"Docker-NGINX-Auto-SSL Container"})," to generate and renew certificates when needed."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're not so comfortable on the command line, then you can use a tool like ",(0,r.jsx)(n.a,{href:"https://www.sslforfree.com/",children:"SSL For Free"})," or ",(0,r.jsx)(n.a,{href:"https://zerossl.com/",children:"ZeroSSL"})," to generate your cert. They also provide step-by-step setup instructions for most platforms."]}),"\n",(0,r.jsx)(n.h3,{id:"passing-a-self-signed-certificate-to-dashy",children:"Passing a Self-Signed Certificate to Dashy"}),"\n",(0,r.jsxs)(n.p,{children:["Once you've generated your SSL cert, you'll need to pass it to Dashy. This can be done by specifying the paths to your public and private keys using the ",(0,r.jsx)(n.code,{children:"SSL_PRIV_KEY_PATH"})," and ",(0,r.jsx)(n.code,{children:"SSL_PUB_KEY_PATH"})," environmental variables. Or if you're using Docker, then just pass public + private SSL keys in under ",(0,r.jsx)(n.code,{children:"/etc/ssl/certs/dashy-pub.pem"})," and ",(0,r.jsx)(n.code,{children:"/etc/ssl/certs/dashy-priv.key"})," respectively, e.g:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d \\\n -p 8080:8080 \\\n -v ~/my-private-key.key:/etc/ssl/certs/dashy-priv.key:ro \\\n -v ~/my-public-key.pem:/etc/ssl/certs/dashy-pub.pem:ro \\\n lissy93/dashy:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["By default the SSL port is ",(0,r.jsx)(n.code,{children:"443"})," within a Docker container, or ",(0,r.jsx)(n.code,{children:"4001"})," if running on bare metal, but you can override this with the ",(0,r.jsx)(n.code,{children:"SSL_PORT"})," environmental variable."]}),"\n",(0,r.jsxs)(n.p,{children:["Once everything is setup, you can verify your site is secured using a tool like ",(0,r.jsx)(n.a,{href:"https://www.sslchecker.com/sslchecker",children:"SSL Checker"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"authentication",children:"Authentication"}),"\n",(0,r.jsxs)(n.p,{children:["Dashy natively supports secure authentication using KeyCloak. There is also a Simple Auth feature that doesn't require any additional setup. Usage instructions for both, as well as alternative auth methods, has now moved to the ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"/docs/authentication",children:"Authentication Docs"})})," page."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"network-exposure",children:"Network Exposure"}),"\n",(0,r.jsx)(n.p,{children:"Dashy is designed to run on your local network, behind your firewall. If you only access it from within your home or over a VPN, the defaults are fine."}),"\n",(0,r.jsxs)(n.p,{children:["If you do need to expose Dashy to the internet, you should put it behind a reverse proxy with its own authentication layer (e.g. Authelia, Authentik, Cloudflare Access, or your proxy's built-in auth). Don't rely solely on Dashy's built-in auth for internet-facing instances - it's a convenience feature for private networks, not a hardened perimeter control. See the ",(0,r.jsx)(n.a,{href:"/docs/authentication",children:"Authentication Docs"})," for setup options."]}),"\n",(0,r.jsxs)(n.p,{children:["When Dashy runs in server mode (the default Docker setup), it exposes several API endpoints for things like status checks, ping checks, config saving, system info, and a CORS proxy used by widgets. When authentication is enabled (via ",(0,r.jsx)(n.code,{children:"ENABLE_HTTP_AUTH=true"})," or ",(0,r.jsx)(n.code,{children:"BASIC_AUTH_USERNAME"}),"/",(0,r.jsx)(n.code,{children:"BASIC_AUTH_PASSWORD"})," env vars), all of these endpoints require valid credentials. Without auth configured, they are open. That's fine for private networks, but not appropriate for public access."]}),"\n",(0,r.jsxs)(n.p,{children:["The CORS proxy (",(0,r.jsx)(n.code,{children:"/cors-proxy"}),") is worth calling out specifically: it forwards requests from the Dashy server to external URLs, so widgets can reach APIs that don't set CORS headers. On a private network this is harmless, but on an internet-exposed instance without auth, it could be abused as an open proxy. Always enable authentication if your instance is reachable from untrusted networks."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"managing-containers-with-docker-compose",children:"Managing Containers with Docker Compose"}),"\n",(0,r.jsxs)(n.p,{children:["When you have a lot of containers, it quickly becomes hard to manage with ",(0,r.jsx)(n.code,{children:"docker run"})," commands. The solution to this is ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/compose/",children:"docker compose"}),", a handy tool for defining all a containers run settings in a single YAML file, and then spinning up that container with a single short command - ",(0,r.jsx)(n.code,{children:"docker compose up"}),". A good example of which can be seen in ",(0,r.jsx)(n.a,{href:"https://github.com/abhilesh/self-hosted_docker_setups",children:"@abhilesh's docker compose collection"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["You can use Dashy's default ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/docker-compose.yml",children:(0,r.jsx)(n.code,{children:"docker-compose.yml"})})," file as a template, and modify it according to your needs."]}),"\n",(0,r.jsx)(n.p,{children:"An example Docker compose, using the default base image from DockerHub, might look something like this:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"services:\n dashy:\n container_name: Dashy\n image: lissy93/dashy:latest\n volumes:\n - ./user-data:/app/user-data\n ports:\n - 4000:8080\n environment:\n - BASE_URL=/my-dashboard\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'node', '/app/services/healthcheck.js']\n interval: 1m30s\n timeout: 10s\n retries: 3\n start_period: 30s\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"passing-in-environmental-variables",children:"Passing in Environmental Variables"}),"\n",(0,r.jsxs)(n.p,{children:["With Docker, you can define environmental variables under the ",(0,r.jsx)(n.code,{children:"environment"})," section of your Docker compose file. Environmental variables are used to configure high-level settings, usually before the config file has been read. For a list of all supported env vars in Dashy, see ",(0,r.jsx)(n.a,{href:"/docs/developing#environmental-variables",children:"the developing docs"}),", or the default ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/.env",children:(0,r.jsx)(n.code,{children:".env"})})," file."]}),"\n",(0,r.jsxs)(n.p,{children:["A common use case, is to run Dashy under a sub-page, instead of at the root of a URL (e.g. ",(0,r.jsx)(n.code,{children:"https://my-homelab.local/dashy"})," instead of ",(0,r.jsx)(n.code,{children:"https://dashy.my-homelab.local"}),"). In this use-case, you'd specify the ",(0,r.jsx)(n.code,{children:"BASE_URL"})," variable in your compose file."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"environment:\n - BASE_URL=/dashy\n"})}),"\n",(0,r.jsxs)(n.p,{children:["You can also do the same thing with the docker run command, using the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file",children:(0,r.jsx)(n.code,{children:"--env"})})," flag.\nIf you've got many environmental variables, you might find it useful to put them in a ",(0,r.jsxs)(n.a,{href:"https://docs.docker.com/compose/env-file/",children:[(0,r.jsx)(n.code,{children:".env"})," file"]}),". Similarly, for Docker run you can use ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file",children:(0,r.jsx)(n.code,{children:"--env-file"})})," if you'd like to pass in a file containing all your environmental variables."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"setting-headers",children:"Setting Headers"}),"\n",(0,r.jsxs)(n.p,{children:["Any external requests made to a different origin (app/ service under a different domain) will be blocked if the correct headers are not specified. This is known as ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"Cross-Origin Resource Sharing"})," (CORS) and is a security feature built into modern browsers."]}),"\n",(0,r.jsx)(n.p,{children:"If you see a CORS error in your console, this can be easily fixed by setting the correct headers. This is not a bug with Dashy, so please don't raise it as a bug!"}),"\n",(0,r.jsx)(n.h3,{id:"example-headers",children:"Example Headers"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#caddy",children:"Caddy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nginx",children:"NGINX"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#traefik",children:"Tr\xe6f\u026ak"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#haproxy",children:"HAProxy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#apache",children:"Apache"})}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsxs)(n.em,{children:["The following section briefly outlines how you can set headers for common web proxies/ servers. More info can be found in the documentation for the proxy that you are using, or in the ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"MDN Docs"}),"."]})}),"\n",(0,r.jsx)(n.p,{children:"These examples are using:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Access-Control-Allow-Origin"})," header, but depending on what type of content you are enabling, this will vary. For example, to allow a site to be loaded in an iframe (for the modal or workspace views) you would use ",(0,r.jsx)(n.code,{children:"X-Frame-Options"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["The domain root (",(0,r.jsx)(n.code,{children:"/"}),"), if your're hosting from a sub-page, replace that with your path."]}),"\n",(0,r.jsxs)(n.li,{children:["A wildcard (",(0,r.jsx)(n.code,{children:"*"}),"), which would allow access from traffic on any domain, this is discouraged, and you should replace it with the URL where you are hosting Dashy. Note that for requests that transport sensitive info, like credentials (e.g. Keycloak login), the wildcard is ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#requests_with_credentials",children:"disallowed all together"})," and will be blocked."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"caddy",children:"Caddy"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsxs)(n.a,{href:"https://caddyserver.com/docs/caddyfile/directives/header",children:["Caddy ",(0,r.jsx)(n.code,{children:"header"})," docs"]})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"headers / {\n Access-Control-Allow-Origin *\n}\n"})}),"\n",(0,r.jsx)(n.h4,{id:"nginx",children:"NGINX"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsxs)(n.a,{href:"https://nginx.org/en/docs/http/ngx_http_headers_module.html",children:["NGINX ",(0,r.jsx)(n.code,{children:"ngx_http_headers_module"})," docs"]})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"location / {\n add_header Access-Control-Allow-Origin *;\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:"Note this can also be done through the UI, using NGINX Proxy Manager."}),"\n",(0,r.jsx)(n.h4,{id:"traefik",children:"Traefik"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsx)(n.a,{href:"https://doc.traefik.io/traefik/middlewares/http/headers/#cors-headers",children:"Tr\xe6f\u026ak CORS headers docs"})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'labels:\n - "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods=GET,OPTIONS,PUT"\n - "traefik.http.middlewares.testheader.headers.accesscontrolalloworiginlist=https://foo.bar.org,https://example.org"\n - "traefik.http.middlewares.testheader.headers.accesscontrolmaxage=100"\n - "traefik.http.middlewares.testheader.headers.addvaryheader=true"\n'})}),"\n",(0,r.jsx)(n.h4,{id:"haproxy",children:"HAProxy"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.haproxy.com/documentation/hapee/latest/traffic-routing/rewrites/rewrite-responses/",children:"HAProxy Rewrite Response Docs"})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"/\n http-response add-header Access-Control-Allow-Origin *\n"})}),"\n",(0,r.jsx)(n.h4,{id:"apache",children:"Apache"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsxs)(n.a,{href:"https://httpd.apache.org/docs/current/mod/mod_headers.html",children:["Apache ",(0,r.jsx)(n.code,{children:"mode_headers"})," docs"]})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'Header always set Access-Control-Allow-Origin "*"\n'})}),"\n",(0,r.jsx)(n.h4,{id:"squid",children:"Squid"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsxs)(n.a,{href:"http://www2.gr.squid-cache.org/Doc/config/request_header_access/",children:["Squid ",(0,r.jsx)(n.code,{children:"request_header_access"})," docs"]})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"request_header_access Authorization allow all\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"remote-access",children:"Remote Access"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#wireguard",children:"WireGuard"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#reverse-ssh-tunnel",children:"Reverse SSH Tunnel"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#tcp-tunnel",children:"TCP Tunnel"})}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"wireguard",children:"WireGuard"}),"\n",(0,r.jsxs)(n.p,{children:["Using a VPN is one of the easiest ways to provide secure, full access to your local network from remote locations. ",(0,r.jsx)(n.a,{href:"https://www.wireguard.com/",children:"WireGuard"})," is a reasonably new open source VPN protocol, that was designed with ease of use, performance and security in mind. Unlike OpenVPN, it doesn't need to recreate the tunnel whenever connection is dropped, and it's also much easier to setup, using shared keys instead."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Install Wireguard"})," - See the ",(0,r.jsx)(n.a,{href:"https://www.wireguard.com/install/",children:"Install Docs"})," for download links + instructions\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["On Debian-based systems, it's ",(0,r.jsx)(n.code,{children:"sudo apt install wireguard"})]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Generate a Private Key"})," - Run ",(0,r.jsx)(n.code,{children:"wg genkey"})," on the Wireguard server, and copy it to somewhere safe for later"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Create Server Config"})," - Open or create a file at ",(0,r.jsx)(n.code,{children:"/etc/wireguard/wg0.conf"})," and under ",(0,r.jsx)(n.code,{children:"[Interface]"})," add the following (see example below):\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Address"})," - as a subnet of all desired IPs"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"PrivateKey"})," - that you just generated"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"ListenPort"})," - Default is ",(0,r.jsx)(n.code,{children:"51820"}),", but can be anything"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Get Client App"})," - Download the ",(0,r.jsx)(n.a,{href:"https://www.wireguard.com/install/",children:"WG client app"})," for your platform (Linux, Windows, MacOS, Android or iOS are all supported)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Create new Client Tunnel"})," - On your client app, there should be an option to create a new tunnel, when doing so a client private key will be generated (but if not, use the ",(0,r.jsx)(n.code,{children:"wg genkey"})," command again), and keep it somewhere safe. A public key will also be generated, and this will go in our saver config"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Add Clients to Server Config"})," - Head back to your ",(0,r.jsx)(n.code,{children:"wg0.conf"})," file on the server, create a ",(0,r.jsx)(n.code,{children:"[Peer]"})," section, and populate the following info\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"AllowedIPs"})," - List of IP address inside the subnet, the client should have access to"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"PublicKey"})," - The public key for the client you just generated"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Start the Server"})," - You can now start the WG server, using: ",(0,r.jsx)(n.code,{children:"wg-quick up wg0"})," on your server"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Finish Client Setup"})," - Head back to your client device, and edit the config file, leave the private key as is, and add the following fields:\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"PublicKey"})," - The public key of the server"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Address"})," - This should match the ",(0,r.jsx)(n.code,{children:"AllowedIPs"})," section on the servers config file"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"DNS"})," - The DNS server that'll be used when accessing the network through the VPN"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Endpoint"})," - The hostname or IP + Port where your WG server is running (you may need to forward this in your firewall's settings)"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Done"})," - Your clients should now be able to connect to your WG server :) Depending on your networks firewall rules, you may need to port forward the address of your WG server"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"example-server-config",children:(0,r.jsx)(n.strong,{children:"Example Server Config"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ini",children:"# Server file\n[Interface]\n# Which networks does my interface belong to? Notice: /24 and /64\nAddress = 10.5.0.1/24, 2001:470:xxxx:xxxx::1/64\nPrivateKey = xxx\nListenPort = 51820\n\n# Peer 1\n[Peer]\nPublicKey = xxx\n# Which source IPs can I expect from that peer? Notice: /32 and /128\nAllowedIps = 10.5.0.35/32, 2001:470:xxxx:xxxx::746f:786f/128\n\n# Peer 2\n[Peer]\nPublicKey = xxx\n# Which source IPs can I expect from that peer? This one has a LAN which can\n# access hosts/jails without NAT.\n# Peer 2 has a single IP address inside the VPN: it's 10.5.0.25/32\nAllowedIps = 10.5.0.25/32,10.21.10.0/24,10.21.20.0/24,10.21.30.0/24,10.31.0.0/24,2001:470:xxxx:xxxx::ca:571e/128\n"})}),"\n",(0,r.jsx)(n.h4,{id:"example-client-config",children:(0,r.jsx)(n.strong,{children:"Example Client Config"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ini",children:"[Interface]\n# Which networks does my interface belong to? Notice: /24 and /64\nAddress = 10.5.0.35/24, 2001:470:xxxx:xxxx::746f:786f/64\nPrivateKey = xxx\n\n# Server\n[Peer]\nPublicKey = xxx\n# I want to route everything through the server, both IPv4 and IPv6. All IPs are\n# thus available through the Server, and I can expect packets from any IP to\n# come from that peer.\nAllowedIPs = 0.0.0.0/0, ::0/0\n# Where is the server on the internet? This is a public address. The port\n# (:51820) is the same as ListenPort in the [Interface] of the Server file above\nEndpoint = 1.2.3.4:51820\n# Usually, clients are behind NAT. to keep the connection running, keep alive.\nPersistentKeepalive = 15\n"})}),"\n",(0,r.jsxs)(n.p,{children:["A useful tool for getting WG setup is ",(0,r.jsx)(n.a,{href:"https://github.com/trailofbits/algo",children:"Algo"}),". It includes scripts and docs which cover almost all devices, platforms and clients, and has best practices implemented, and security features enabled. All of this is better explained in ",(0,r.jsx)(n.a,{href:"https://blog.trailofbits.com/2016/12/12/meet-algo-the-vpn-that-works/",children:"this blog post"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"reverse-ssh-tunnel",children:"Reverse SSH Tunnel"}),"\n",(0,r.jsxs)(n.p,{children:["SSH (or ",(0,r.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/Secure_Shell",children:"Secure Shell"}),") is a secure tunnel that allows you to connect to a remote host. Unlike the VPN methods, an SSH connection does not require an intermediary, and will not be affected by your IP changing. However it only allows you to access a single service at a time. SSH was really designed for terminal access, but because of the latter mentioned benefits it's useful to setup, as a fallback option."]}),"\n",(0,r.jsx)(n.p,{children:"Directly SSH'ing into your home, would require you to open a port (usually 22), which would be terrible for security, and is not recommended. However a reverse SSH connection is initiated from inside your network. Once the connection is established, the port is redirected, allowing you to use the established connection to SSH into your home network."}),"\n",(0,r.jsxs)(n.p,{children:["The issue you've probably spotted, is that most public, corporate, and institutional networks will block SSH connections. To overcome this, you'd have to establish a server outside of your homelab that your homelab's device could SSH into to establish the reverse SSH connection. You can then connect to that remote server (the ",(0,r.jsx)(n.em,{children:"mothership"}),"), which in turn connects to your home network."]}),"\n",(0,r.jsxs)(n.p,{children:["Now all of this is starting to sound like quite a lot of work, but this is where services like ",(0,r.jsx)(n.a,{href:"https://remote.it/",children:"remot3.it"})," come in. They maintain the intermediary mothership server, and create the tunnel service for you. It's free for personal use, secure and easy. There are several similar services, such as ",(0,r.jsx)(n.a,{href:"https://remoteiot.com/",children:"RemoteIoT"}),", or you could create your own on a cloud VPS (see ",(0,r.jsx)(n.a,{href:"https://gist.github.com/nileshtrivedi/4c615e8d3c1bf053b0d31176b9e69e42",children:"this tutorial"})," for more info on that)."]}),"\n",(0,r.jsxs)(n.p,{children:["Before getting started, you'll need to head over to ",(0,r.jsx)(n.a,{href:"https://app.remote.it/auth/#/sign-up",children:"Remote.it"})," and create an account."]}),"\n",(0,r.jsx)(n.p,{children:"Then setup your local device:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["If you haven't already done so, you'll need to enable and configure SSH.\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["This is out-of-scope of this article, but I've explained it in detail in ",(0,r.jsx)(n.a,{href:"https://notes.aliciasykes.com/22798/my-server-setup#configure-ssh",children:"this post"}),"."]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Download the Remote.it install script from their ",(0,r.jsx)(n.a,{href:"https://github.com/remoteit/installer",children:"GitHub"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"curl -LkO https://raw.githubusercontent.com/remoteit/installer/master/scripts/auto-install.sh"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Make it executable, with ",(0,r.jsx)(n.code,{children:"chmod +x ./auto-install.sh"}),", and then run it with ",(0,r.jsx)(n.code,{children:"sudo ./auto-install.sh"})]}),"\n",(0,r.jsxs)(n.li,{children:["Finally, configure your device, by running ",(0,r.jsx)(n.code,{children:"sudo connectd_installer"})," and following the on-screen instructions"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"And when you're ready to connect to it:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Login to ",(0,r.jsx)(n.a,{href:"https://app.remote.it/",children:"app.remote.it"}),", and select the name of your device"]}),"\n",(0,r.jsx)(n.li,{children:"You should see a list of running services, click SSH"}),"\n",(0,r.jsx)(n.li,{children:"You'll then be presented with some SSH credentials that you can now use to securely connect to your home, via the Remote.it servers"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Done :)"}),"\n",(0,r.jsx)(n.h3,{id:"tcp-tunnel",children:"TCP Tunnel"}),"\n",(0,r.jsxs)(n.p,{children:["If you're running Dashy on your local network, behind a firewall, but need to temporarily share it with someone external, this can be achieved quickly and securely using ",(0,r.jsx)(n.a,{href:"https://ngrok.com/",children:"Ngrok"}),". It's basically a super slick, encrypted TCP tunnel that provides an internet-accessible address that anyone use to access your local service, from anywhere."]}),"\n",(0,r.jsxs)(n.p,{children:["To get started, ",(0,r.jsx)(n.a,{href:"https://ngrok.com/download",children:"Download"})," and install Ngrok for your system, then just run ",(0,r.jsx)(n.code,{children:"ngrok http [port]"})," (replace the port with the http port where Dashy is running, e.g. 8080). When ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs#http-local-https",children:"using https"}),", specify the full local url/ ip including the protocol."]}),"\n",(0,r.jsxs)(n.p,{children:["Some Ngrok features require you to be authenticated, you can ",(0,r.jsx)(n.a,{href:"https://dashboard.ngrok.com/signup",children:"create a free account"})," and generate a token in ",(0,r.jsx)(n.a,{href:"https://dashboard.ngrok.com/auth/your-authtoken",children:"your dashboard"}),", then run ",(0,r.jsx)(n.code,{children:"ngrok authtoken [token]"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["It's recommended to use authentication for any publicly accessible service. Dashy has an ",(0,r.jsx)(n.a,{href:"/docs/authentication",children:"Auth"})," feature built in, but an even easier method it to use the ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs#http-auth",children:(0,r.jsx)(n.code,{children:"-auth"})})," switch. E.g. ",(0,r.jsx)(n.code,{children:'ngrok http -auth="username:password123" 8080'})]}),"\n",(0,r.jsxs)(n.p,{children:["By default, your web app is assigned a randomly generated ngrok domain, but you can also use your own custom domain. Under the ",(0,r.jsx)(n.a,{href:"https://dashboard.ngrok.com/endpoints/domains",children:"Domains Tab"})," of your Ngrok dashboard, add your domain, and follow the CNAME instructions. You can now use your domain, with the ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs#http-custom-domains",children:(0,r.jsx)(n.code,{children:"-hostname"})})," switch, e.g. ",(0,r.jsx)(n.code,{children:"ngrok http -region=us -hostname=dashy.example.com 8080"}),". If you don't have your own domain name, you can instead use a custom sub-domain (e.g. ",(0,r.jsx)(n.code,{children:"alicia-dashy.ngrok.io"}),"), using the ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs#custom-subdomain-names",children:(0,r.jsx)(n.code,{children:"-subdomain"})})," switch."]}),"\n",(0,r.jsxs)(n.p,{children:["To integrate this into your docker-compose, take a look at the ",(0,r.jsx)(n.a,{href:"https://github.com/gtriggiano/ngrok-tunnel",children:"gtriggiano/ngrok-tunnel"})," container."]}),"\n",(0,r.jsxs)(n.p,{children:["There's so much more you can do with Ngrok, such as exposing a directory as a file browser, using websockets, relaying requests, rewriting headers, inspecting traffic, TLS and TCP tunnels and lots more. All or which is explained in ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs",children:"the Documentation"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["It's worth noting that Ngrok isn't the only option here, other options include: ",(0,r.jsx)(n.a,{href:"https://github.com/fatedier/frp",children:"FRP"}),", ",(0,r.jsx)(n.a,{href:"https://inlets.dev",children:"Inlets"}),", ",(0,r.jsx)(n.a,{href:"https://localtunnel.me/",children:"Local Tunnel"}),", ",(0,r.jsx)(n.a,{href:"https://tailscale.com/",children:"TailScale"}),", etc. Check out ",(0,r.jsx)(n.a,{href:"https://github.com/anderspitman/awesome-tunneling",children:"Awesome Tunneling"})," for a list of alternatives."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"custom-domain",children:"Custom Domain"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#using-dns",children:"Using DNS"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#using-nginx",children:"Using NGINX"})}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"using-dns",children:"Using DNS"}),"\n",(0,r.jsxs)(n.p,{children:["For locally running services, a domain can be set up directly in the DNS records. This method is really quick and easy, and doesn't require you to purchase an actual domain. Just update your networks DNS resolver, to point your desired URL to the local IP where Dashy (or any other app) is running. For example, a line in your hosts file might look something like: ",(0,r.jsx)(n.code,{children:"192.168.0.2 dashy.homelab.local"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're using Pi-Hole, a similar thing can be done in the ",(0,r.jsx)(n.code,{children:"/etc/dnsmasq.d/03-custom-dns.conf"})," file, add a line like: ",(0,r.jsx)(n.code,{children:"address=/dashy.example.com/192.168.2.0"})," for each of your services."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're running OPNSense/ PfSense, then this can be done through the UI with Unbound, it's explained nicely in ",(0,r.jsx)(n.a,{href:"https://homenetworkguy.com/how-to/use-custom-domain-name-in-internal-network/",children:"this article"}),", by Dustin Casto."]}),"\n",(0,r.jsx)(n.h3,{id:"using-nginx",children:"Using NGINX"}),"\n",(0,r.jsx)(n.p,{children:"If you're using NGINX, then you can use your own domain name, with a config similar to the below example."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"upstream dashy {\n server 127.0.0.1:32400;\n}\n\nserver {\n listen 8080;\n server_name dashy.mydomain.com;\n\n # Setup SSL\n ssl_certificate /var/www/mydomain/sslcert.pem;\n ssl_certificate_key /var/www/mydomain/sslkey.pem;\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';\n ssl_session_timeout 5m;\n ssl_prefer_server_ciphers on;\n\n location / {\n proxy_pass http://dashy;\n proxy_redirect off;\n proxy_buffering off;\n proxy_set_header host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;\n }\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Similarly, a basic ",(0,r.jsx)(n.code,{children:"Caddyfile"})," might look like:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"dashy.example.com {\n reverse_proxy / nginx:8080\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["For more info, ",(0,r.jsx)(n.a,{href:"https://thehomelab.wiki/books/dns-reverse-proxy/page/create-domain-records-to-point-to-your-home-server-on-cloudflare-using-nginx-progy-manager",children:"this guide"})," on Setting up Domains with NGINX Proxy Manager and CloudFlare may be useful."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"verifying-releases",children:"Verifying Releases"}),"\n",(0,r.jsx)(n.p,{children:"Everything Dashy publishes can be verified, so you can check that what you're running is what our CI actually built."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Docker images"}),": Every image pushed to GHCR has a signed SBOM (software bill of materials) and build provenance attestation attached. Verify with the ",(0,r.jsx)(n.a,{href:"https://cli.github.com/",children:"GitHub CLI"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"gh attestation verify oci://ghcr.io/lissy93/dashy:latest --owner lissy93\n"})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"GitHub releases (non-Docker)"}),": Each ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/releases",children:"release"})," includes a pre-built tarball, along with a SHA256 checksum and its own provenance attestation. To check your download:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"sha256sum -c dashy-.tar.gz.sha256\ngh attestation verify dashy-.tar.gz --owner lissy93\n"})}),"\n",(0,r.jsx)(n.p,{children:"If verification passes, the artifact was built by our GitHub Actions workflow, from the Dashy repo, and hasn't been tampered with since."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"container-security",children:"Container Security"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#keep-docker-up-to-date",children:"Keep Docker Up-To-Date"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#set-resource-quotas",children:"Set Resource Quotas"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#dont-run-as-root",children:"Don't Run as Root"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#specify-a-user",children:"Specify a User"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#limit-capabilities",children:"Limit Capabilities"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#prevent-new-privileges-being-added",children:"Prevent new Privileges being Added"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#disable-inter-container-communication",children:"Disable Inter-Container Communication"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#dont-expose-the-docker-daemon-socket",children:"Don't Expose the Docker Daemon Socket"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#use-read-only-volumes",children:"Use Read-Only Volumes"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#set-the-logging-level",children:"Set the Logging Level"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#verify-image-before-pulling",children:"Verify Image before Pulling"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#specify-the-tag",children:"Specify the Tag"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#container-security-scanning",children:"Container Security Scanning"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#registry-security",children:"Registry Security"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#security-modules",children:"Security Modules"})}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"keep-docker-up-to-date",children:"Keep Docker Up-To-Date"}),"\n",(0,r.jsxs)(n.p,{children:["To prevent known container escape vulnerabilities, which typically end in escalating to root/administrator privileges, patching Docker Engine and Docker Machine is crucial. For more info, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/install/",children:"Docker Installation Docs"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"set-resource-quotas",children:"Set Resource Quotas"}),"\n",(0,r.jsxs)(n.p,{children:["Docker enables you to limit resource consumption (CPU, memory, disk) on a per-container basis. This not only enhances system performance, but also prevents a compromised container from consuming a large amount of resources, in order to disrupt service or perform malicious activities. To learn more, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/containers/resource_constraints/",children:"Resource Constraints Docs"})]}),"\n",(0,r.jsxs)(n.p,{children:["For example, to run Dashy with max of 1GB ram, and max of 50% of 1 CP core:\n",(0,r.jsx)(n.code,{children:'docker run -d -p 8080:8080 --cpus=".5" --memory="1024m" lissy93/dashy:latest'})]}),"\n",(0,r.jsx)(n.h3,{id:"dont-run-as-root",children:"Don't Run as Root"}),"\n",(0,r.jsxs)(n.p,{children:["Running Docker commands with ",(0,r.jsx)(n.code,{children:"sudo"})," gives the container more host-level access than it needs. You should run Docker as a non-root host user instead."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're facing permission issues on Debian-based systems when running Docker commands without ",(0,r.jsx)(n.code,{children:"sudo"}),", you may need to add your user to the Docker group. First create the group: ",(0,r.jsx)(n.code,{children:"sudo groupadd docker"}),", then add your (non-root) user: ",(0,r.jsx)(n.code,{children:"sudo usermod \u2212aG docker [my-username]"}),", finally ",(0,r.jsx)(n.code,{children:"newgrp docker"})," to refresh."]}),"\n",(0,r.jsx)(n.h3,{id:"specify-a-user",children:"Specify a User"}),"\n",(0,r.jsxs)(n.p,{children:["For containers in general, running as an unprivileged user is one of the best ways to prevent privilege escalation attacks. You can specify a user with the ",(0,r.jsxs)(n.a,{href:"https://docs.docker.com/engine/reference/run/#user",children:[(0,r.jsx)(n.code,{children:"--user"})," param"]}),", using the user ID (",(0,r.jsx)(n.code,{children:"UID"}),") from ",(0,r.jsx)(n.code,{children:"id -u"})," and group ID (",(0,r.jsx)(n.code,{children:"GID"}),") from ",(0,r.jsx)(n.code,{children:"id -g"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Note for Dashy:"})," If you use features that write to disk (saving config through the UI), the process needs write access to ",(0,r.jsx)(n.code,{children:"/app/user-data/"}),". Since the default image creates these directories as root, running with ",(0,r.jsx)(n.code,{children:"--user"})," will cause those features to fail with permission errors unless you also fix ownership of the mounted volumes. If you only use Dashy in read-only mode, running as a non-root user works fine:"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.code,{children:"docker run --user 1000:1000 -p 8080:8080 lissy93/dashy"})}),"\n",(0,r.jsx)(n.p,{children:"Or with Docker Compose, using an environmental variable:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"services:\n dashy:\n image: lissy93/dashy\n user: ${CURRENT_UID}\n ports: [ 4000:8080 ]\n"})}),"\n",(0,r.jsxs)(n.p,{children:["And then to set the variable, and start the container, run: ",(0,r.jsx)(n.code,{children:"CURRENT_UID=$(id -u):$(id -g) docker-compose up"})]}),"\n",(0,r.jsx)(n.h3,{id:"limit-capabilities",children:"Limit capabilities"}),"\n",(0,r.jsxs)(n.p,{children:["Docker containers run with a subset of ",(0,r.jsx)(n.a,{href:"https://man7.org/linux/man-pages/man7/capabilities.7.html",children:"Linux Kernal's Capabilities"})," by default. It's good practice to drop privilege permissions that are not needed for any given container."]}),"\n",(0,r.jsxs)(n.p,{children:["With Docker run, you can use the ",(0,r.jsx)(n.code,{children:"--cap-drop"})," flag to remove capabilities, you can also use ",(0,r.jsx)(n.code,{children:"--cap-drop=all"})," and then define just the required permissions using the ",(0,r.jsx)(n.code,{children:"--cap-add"})," option. For a list of available capabilities, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities",children:"Privilege Capabilities Docs"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Note that dropping privileges and capabilities on runtime is not fool-proof, and often any leftover privileges can be used to re-escalate, see ",(0,r.jsx)(n.a,{href:"https://wiki.sei.cmu.edu/confluence/display/c/POS36-C.+Observe+correct+revocation+order+while+relinquishing+privileges",children:"POS36-C"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"Here's an example using docker-compose, removing privileges that are not required for Dashy to run:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"services:\n dashy:\n image: lissy93/dashy\n ports: [ 4000:8080 ]\n cap_drop:\n - ALL\n cap_add:\n - CHOWN\n - SETGID\n - SETUID\n - DAC_OVERRIDE\n - NET_BIND_SERVICE\n"})}),"\n",(0,r.jsx)(n.h3,{id:"prevent-new-privileges-being-added",children:"Prevent new Privileges being Added"}),"\n",(0,r.jsxs)(n.p,{children:["To prevent processes inside the container from getting additional privileges, pass in the ",(0,r.jsx)(n.code,{children:"--security-opt=no-new-privileges:true"})," option to the Docker run command (see ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/run/#security-configuration",children:"docs"}),")."]}),"\n",(0,r.jsxs)(n.p,{children:["Run Command:\n",(0,r.jsx)(n.code,{children:"docker run --security-opt=no-new-privileges:true -p 8080:8080 lissy93/dashy"})]}),"\n",(0,r.jsx)(n.p,{children:"Docker Compose"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"security_opt:\n- no-new-privileges:true\n"})}),"\n",(0,r.jsx)(n.h3,{id:"disable-inter-container-communication",children:"Disable Inter-Container Communication"}),"\n",(0,r.jsxs)(n.p,{children:["By default Docker containers can talk to each other (using ",(0,r.jsxs)(n.a,{href:"https://docs.docker.com/config/containers/container-networking/",children:[(0,r.jsx)(n.code,{children:"docker0"})," bridged network"]}),"). If you don't need this capability, then it should be disabled. This can be done with the ",(0,r.jsx)(n.code,{children:"--icc=false"})," in your run command. You can learn more about how to facilitate secure communication between containers in the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/compose/networking/",children:"Compose Networking docs"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"dont-expose-the-docker-daemon-socket",children:"Don't Expose the Docker Daemon Socket"}),"\n",(0,r.jsxs)(n.p,{children:["Docker socket ",(0,r.jsx)(n.code,{children:"/var/run/docker.sock"})," is the UNIX socket that Docker is listening to. This is the primary entry point for the Docker API. The owner of this socket is root. Giving someone access to it is equivalent to giving unrestricted root access to your host."]}),"\n",(0,r.jsxs)(n.p,{children:["You should ",(0,r.jsx)(n.strong,{children:"not"})," enable TCP Docker daemon socket (",(0,r.jsx)(n.code,{children:"-H tcp://0.0.0.0:XXX"}),"), as doing so exposes un-encrypted and unauthenticated direct access to the Docker daemon, and if the host is connected to the internet, the daemon on your computer can be used by anyone from the public internet- which is bad. If you need TCP, you should ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-socket-option",children:"see the docs"})," to understand how to do this more securely.\nSimilarly, never expose ",(0,r.jsx)(n.code,{children:"/var/run/docker.sock"})," to other containers as a volume, as it can be exploited."]}),"\n",(0,r.jsx)(n.h3,{id:"use-read-only-volumes",children:"Use Read-Only Volumes"}),"\n",(0,r.jsxs)(n.p,{children:["You can specify that a volume should be read-only by appending ",(0,r.jsx)(n.code,{children:":ro"})," to the ",(0,r.jsx)(n.code,{children:"-v"})," switch. If you don't need the in-app config editor, mount your ",(0,r.jsx)(n.code,{children:"user-data"})," read-only:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d \\\n -p 8080:8080 \\\n -v ~/dashy-data:/app/user-data:ro \\\n lissy93/dashy:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["If you do want config changes from the UI to persist back to disk, leave the mount writable. You can also use ",(0,r.jsx)(n.code,{children:"--read-only"})," to make the whole container filesystem read-only, but in that case UI-driven config edits will not be saved."]}),"\n",(0,r.jsx)(n.h3,{id:"set-the-logging-level",children:"Set the Logging Level"}),"\n",(0,r.jsxs)(n.p,{children:["Logging is important, as it enables you to review events in the future, and in the case of a compromise this will let get an idea of what may have happened. The default log level is ",(0,r.jsx)(n.code,{children:"INFO"}),", and this is also the recommendation, use ",(0,r.jsx)(n.code,{children:"--log-level info"})," to ensure this is set."]}),"\n",(0,r.jsx)(n.h3,{id:"verify-image-before-pulling",children:"Verify Image before Pulling"}),"\n",(0,r.jsx)(n.p,{children:"Only use trusted images, from verified/ official sources. If an app is open source, it is more likely to be safe, as anyone can verify the code. There are also tools available for scanning containers,"}),"\n",(0,r.jsx)(n.p,{children:"Unless otherwise configured, containers can communicate among each other, so running one bad image may lead to other areas of your setup being compromised. Docker images typically contain both original code, as well as up-stream packages, and even if that image has come from a trusted source, the up-stream packages it includes may not have."}),"\n",(0,r.jsxs)(n.p,{children:["Every Dashy image published to ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/pkgs/container/dashy",children:"GHCR"})," ships with a build-provenance attestation and an SBOM (software bill of materials), both signed keylessly via ",(0,r.jsx)(n.a,{href:"https://www.sigstore.dev/",children:"Sigstore"}),". Provenance cryptographically ties the image back to the exact GitHub Actions run and commit that built it, so you can confirm it really came from our pipeline and was not swapped out along the way. The SBOM lists every package baked into the image, which is handy when a new CVE lands and you want to know in seconds whether you're affected."]}),"\n",(0,r.jsxs)(n.p,{children:["To verify the image you're about to run, use the ",(0,r.jsx)(n.a,{href:"https://cli.github.com/",children:"GitHub CLI"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"gh attestation verify oci://ghcr.io/lissy93/dashy:latest --repo lissy93/dashy\n"})}),"\n",(0,r.jsx)(n.p,{children:"A green check means it was genuinely built by us, from our repo. Worth doing on a fresh Proxmox or homelab box, especially before exposing Dashy beyond your LAN."}),"\n",(0,r.jsxs)(n.p,{children:["To pull the SBOM and inspect what's inside, use ",(0,r.jsx)(n.a,{href:"https://github.com/sigstore/cosign",children:"cosign"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"cosign download sbom ghcr.io/lissy93/dashy:latest\n"})}),"\n",(0,r.jsx)(n.h3,{id:"specify-the-tag",children:"Specify the Tag"}),"\n",(0,r.jsxs)(n.p,{children:["Using fixed tags (as opposed to ",(0,r.jsx)(n.code,{children:":latest"})," ) will ensure immutability, meaning the base image will not change between builds. Note that for Dashy, the app is being actively developed, new features, bug fixes and general improvements are merged each week, and if you use a fixed version you will not enjoy these benefits. So it's up to you weather you would prefer a stable and reproducible environment, or the latest features and enhancements."]}),"\n",(0,r.jsx)(n.h3,{id:"container-security-scanning",children:"Container Security Scanning"}),"\n",(0,r.jsxs)(n.p,{children:["It's helpful to be aware of any potential security issues in any of the Docker images you are using. You can run a quick scan using Snyk on any image to output known vulnerabilities using ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/scan/",children:"Docker scan"}),", e.g: ",(0,r.jsx)(n.code,{children:"docker scan lissy93/dashy:latest"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["A similar product is ",(0,r.jsx)(n.a,{href:"https://github.com/aquasecurity/trivy",children:"Trivy"}),", which is free an open source. First install it (with your package manager), then to scan an image, just run: ",(0,r.jsx)(n.code,{children:"trivy image lissy93/dashy:latest"})]}),"\n",(0,r.jsxs)(n.p,{children:["For larger systems, RedHat ",(0,r.jsx)(n.a,{href:"https://www.redhat.com/en/topics/containers/what-is-clair",children:"Clair"})," is an app for parsing image contents and reporting on any found vulnerabilities. You run it locally in a container, and configure it with YAML. It can be integrated with Red Hat Quay, to show results on a dashboard. Most of these use static analysis to find potential issues, and scan included packages for any known security vulnerabilities."]}),"\n",(0,r.jsx)(n.h3,{id:"registry-security",children:"Registry Security"}),"\n",(0,r.jsxs)(n.p,{children:["Although over-kill for most users, you could run your own registry locally which would give you ultimate control over all images, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/registry/deploying/",children:"Deploying a Registry Docs"})," for more info. Another option is ",(0,r.jsx)(n.a,{href:"https://docker-docs.netlify.app/ee/dtr/",children:"Docker Trusted Registry"}),", it's great for enterprise applications, it sits behind your firewall, running on a swarm managed by Docker Universal Control Plane, and lets you securely store and manage your Docker images, mitigating the risk of breaches from the internet."]}),"\n",(0,r.jsx)(n.h3,{id:"security-modules",children:"Security Modules"}),"\n",(0,r.jsx)(n.p,{children:"Docker supports several modules that let you write your own security profiles."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://www.apparmor.net/",children:"AppArmor"}),"is a kernel module that proactively protects the operating system and applications from external or internal threats, by enabling you to restrict programs' capabilities with per-program profiles. You can specify either a security policy by name, or by file path with the ",(0,r.jsx)(n.code,{children:"apparmor"})," flag in docker run. Learn more about writing profiles, ",(0,r.jsx)(n.a,{href:"https://gitlab.com/apparmor/apparmor/-/wikis/QuickProfileLanguage",children:"here"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/Seccomp",children:"Seccomp"})," (Secure Computing Mode) is a sandboxing facility in the Linux kernel that acts like a firewall for system calls (syscalls). It uses Berkeley Packet Filter (BPF) rules to filter syscalls and control how they are handled. These filters can significantly limit a containers access to the Docker Host's Linux kernel - especially for simple containers/applications. It requires a Linux-based Docker host, with secomp enabled, and you can check for this by running ",(0,r.jsx)(n.code,{children:"docker info | grep seccomp"}),". A great resource for learning more about this is ",(0,r.jsx)(n.a,{href:"https://training.play-with-docker.com/security-seccomp/",children:"DockerLabs"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"web-server-configuration",children:"Web Server Configuration"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"The following section only applies if you are not using Docker, and would like to use your own web server"})}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Dashy ships with a pre-configured Node.js server, in ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/server.js",children:(0,r.jsx)(n.code,{children:"server.js"})})," which serves up the contents of the ",(0,r.jsx)(n.code,{children:"./dist"})," directory on a given port. You can start the server by running ",(0,r.jsx)(n.code,{children:"node server"}),". Note that the app must have been build (run ",(0,r.jsx)(n.code,{children:"yarn build"}),"), and you need ",(0,r.jsx)(n.a,{href:"https://nodejs.org",children:"Node.js"})," installed."]}),"\n",(0,r.jsxs)(n.p,{children:["If you wish to run Dashy from a sub page (e.g. ",(0,r.jsx)(n.code,{children:"example.com/dashy"}),"), then just set the ",(0,r.jsx)(n.code,{children:"BASE_URL"})," environmental variable to that page name (in this example, ",(0,r.jsx)(n.code,{children:"/dashy"}),"), before building the app, and the path to all assets will then resolve to the new path, instead of ",(0,r.jsx)(n.code,{children:"./"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"However, since Dashy is just a static web application, it can be served with whatever server you like. The following section outlines how you can configure a web server."}),"\n",(0,r.jsxs)(n.p,{children:["Note, that if you choose not to use ",(0,r.jsx)(n.code,{children:"server.js"})," to serve up the app, you will loose access to the following features:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Loading page, while the app is building"}),"\n",(0,r.jsx)(n.li,{children:"Writing config file to disk from the UI"}),"\n",(0,r.jsx)(n.li,{children:"Website status indicators, and ping checks"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Example Configs"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nginx",children:"NGINX"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#apache",children:"Apache"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#caddy",children:"Caddy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#firebase-hosting",children:"Firebase"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cpanel",children:"cPanel"})}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"nginx-1",children:"NGINX"}),"\n",(0,r.jsxs)(n.p,{children:["Create a new file in ",(0,r.jsx)(n.code,{children:"/etc/nginx/sites-enabled/dashy"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"server {\n\tlisten 8080;\n\tlisten [::]:8080;\n\n\troot /var/www/dashy/html;\n\tindex index.html;\n\n\tserver_name your-domain.com www.your-domain.com;\n\n\tlocation / {\n\t\ttry_files $uri $uri/ =404;\n\t}\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["To use HTML5 history mode (the default - controlled via the ",(0,r.jsx)(n.code,{children:"VITE_APP_ROUTING_MODE"})," build-time env var), replace the inside of the location block with: ",(0,r.jsx)(n.code,{children:"try_files $uri $uri/ /index.html;"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Then upload the build contents of Dashy's dist directory to that location.\nFor example: ",(0,r.jsx)(n.code,{children:"scp -r ./dist/* [username]@[server_ip]:/var/www/dashy/html"})]}),"\n",(0,r.jsx)(n.h3,{id:"apache-1",children:"Apache"}),"\n",(0,r.jsxs)(n.p,{children:["Copy Dashy's dist folder to your apache server, ",(0,r.jsx)(n.code,{children:"sudo cp -r ./dashy/dist /var/www/html/dashy"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["In your Apache config, ",(0,r.jsx)(n.code,{children:"/etc/apche2/apache2.conf"})," add:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"\n\tOptions Indexes FollowSymLinks\n\tAllowOverride All\n\tRequire all granted\n\n\n\n RewriteEngine On\n RewriteBase /\n RewriteRule ^index\\.html$ - [L]\n RewriteCond %{REQUEST_FILENAME} !-f\n RewriteCond %{REQUEST_FILENAME} !-d\n RewriteRule . /index.html [L]\n\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Add a ",(0,r.jsx)(n.code,{children:".htaccess"})," file within ",(0,r.jsx)(n.code,{children:"/var/www/html/dashy/.htaccess"}),", and add:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Options -MultiViews\nRewriteEngine On\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteRule ^ index.html [QSA,L]\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Then restart Apache, with ",(0,r.jsx)(n.code,{children:"sudo systemctl restart apache2"})]}),"\n",(0,r.jsx)(n.h3,{id:"caddy-1",children:"Caddy"}),"\n",(0,r.jsx)(n.p,{children:"Caddy v2"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"try_files {path} /\n"})}),"\n",(0,r.jsx)(n.p,{children:"Caddy v1"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"rewrite {\n regexp .*\n to {path} /\n}\n"})}),"\n",(0,r.jsx)(n.h3,{id:"firebase-hosting",children:"Firebase Hosting"}),"\n",(0,r.jsxs)(n.p,{children:["Create a file names ",(0,r.jsx)(n.code,{children:"firebase.json"}),", and populate it with something similar to:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'{\n "hosting": {\n "public": "dist",\n "rewrites": [\n {\n "source": "**",\n "destination": "/index.html"\n }\n ]\n }\n}\n'})}),"\n",(0,r.jsx)(n.h3,{id:"cpanel",children:"cPanel"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Login to your WHM"}),"\n",(0,r.jsx)(n.li,{children:"Open 'Feature Manager' on the left sidebar"}),"\n",(0,r.jsx)(n.li,{children:"Under 'Manage feature list', click 'Edit'"}),"\n",(0,r.jsx)(n.li,{children:"Find 'Application manager' in the list, enable it and hit 'Save'"}),"\n",(0,r.jsx)(n.li,{children:"Log into your users cPanel account, and under 'Software' find 'Application Manager'"}),"\n",(0,r.jsx)(n.li,{children:"Click 'Register Application', fill in the form using the path that Dashy is located, and choose a domain, and hit 'Save'"}),"\n",(0,r.jsx)(n.li,{children:"The application should now show up in the list, click 'Ensure dependencies', and move the toggle switch to 'Enabled'"}),"\n",(0,r.jsx)(n.li,{children:"If you need to change the port, click 'Add environmental variable', give it the name 'PORT', choose a port number and press 'Save'."}),"\n",(0,r.jsx)(n.li,{children:"Dashy should now be running at your selected path an on a given port"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"running-a-modified-version-of-the-app",children:"Running a Modified Version of the App"}),"\n",(0,r.jsx)(n.p,{children:"If you'd like to make any code changes to the app, and deploy your modified version, this section briefly explains how."}),"\n",(0,r.jsxs)(n.p,{children:["The first step is to fork the project on GitHub, and clone it to your local system. Next, install the dependencies (",(0,r.jsx)(n.code,{children:"yarn"}),"), and start the development server (",(0,r.jsx)(n.code,{children:"yarn dev"}),") and visit ",(0,r.jsx)(n.code,{children:"localhost:8080"})," in your browser. You can then make changes to the codebase, and see the live app update in real-time. Once you've finished, running ",(0,r.jsx)(n.code,{children:"yarn build"})," will build the app for production, and output the assets into ",(0,r.jsx)(n.code,{children:"./dist"})," which can then be deployed using a web server, CDN or the built-in Node server with ",(0,r.jsx)(n.code,{children:"yarn start"}),". For more info on all of this, take a look at the ",(0,r.jsx)(n.a,{href:"/docs/developing",children:"Developing Docs"}),". To build your own Docker container from the modified app, see ",(0,r.jsx)(n.a,{href:"#building-your-own-container",children:"Building your Own Container"})]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"building-your-own-container",children:"Building your Own Container"}),"\n",(0,r.jsx)(n.p,{children:"Similar to above, you'll first need to fork and clone Dashy to your local system, and then install dependencies."}),"\n",(0,r.jsxs)(n.p,{children:["Then, either use Dashy's default ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/Dockerfile",children:(0,r.jsx)(n.code,{children:"Dockerfile"})})," as is, or modify it according to your needs."]}),"\n",(0,r.jsxs)(n.p,{children:["To build and deploy locally, first build the app with: ",(0,r.jsx)(n.code,{children:"docker build -t dashy ."}),", and then start the app with ",(0,r.jsx)(n.code,{children:"docker run -p 8080:8080 --name my-dashboard dashy"}),". Or modify the ",(0,r.jsx)(n.code,{children:"docker-compose.yml"})," file, replacing ",(0,r.jsx)(n.code,{children:"image: lissy93/dashy"})," with ",(0,r.jsx)(n.code,{children:"build: ."})," and run ",(0,r.jsx)(n.code,{children:"docker compose up"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Your container should now be running, and will appear in the list when you run ",(0,r.jsx)(n.code,{children:"docker container ls \u2013a"}),". If you'd like to enter the container, run ",(0,r.jsx)(n.code,{children:"docker exec -it [container-id] /bin/ash"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["You may wish to upload your image to a container registry for easier access. Note that if you choose to do this on a public registry, please name your container something other than just 'dashy', to avoid confusion with the official image.\nYou can push your build image, by running: ",(0,r.jsx)(n.code,{children:"docker push ghcr.io/OWNER/IMAGE_NAME:latest"}),". You will first need to authenticate, this can be done by running ",(0,r.jsx)(n.code,{children:"echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin"}),", where ",(0,r.jsx)(n.code,{children:"CR_PAT"})," is an environmental variable containing a token generated from your GitHub account. For more info, see the ",(0,r.jsx)(n.a,{href:"https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry",children:"Container Registry Docs"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>a});var i=s(96540);const r={},o=i.createContext(r);function t(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[9695],{76644(e,n,s){s.r(n),s.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>t,metadata:()=>i,toc:()=>l});const i=JSON.parse('{"id":"management","title":"App Management","description":"The following article is a primer on managing self-hosted apps. It covers everything from keeping the Dashy (or any other app) up-to-date, secure, backed up, to other topics like auto-starting, monitoring, log management, web server configuration and using custom domains.","source":"@site/docs/management.md","sourceDirName":".","slug":"/management","permalink":"/docs/management","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/management.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Configuring","permalink":"/docs/configuring"},"next":{"title":"Troubleshooting","permalink":"/docs/troubleshooting"}}');var r=s(74848),o=s(28453);const t={},a="App Management",c={},l=[{value:"Contents",id:"contents",level:2},{value:"Providing Assets",id:"providing-assets",level:2},{value:"File Ownership and Permissions",id:"file-ownership-and-permissions",level:2},{value:"Running Commands",id:"running-commands",level:2},{value:"Healthchecks",id:"healthchecks",level:2},{value:"HTTP Healthcheck Endpoint",id:"http-healthcheck-endpoint",level:3},{value:"Logs and Performance",id:"logs-and-performance",level:2},{value:"Container Logs",id:"container-logs",level:3},{value:"Container Performance",id:"container-performance",level:3},{value:"Management Apps",id:"management-apps",level:3},{value:"Advanced Logging and Monitoring",id:"advanced-logging-and-monitoring",level:3},{value:"Auto-Starting at System Boot",id:"auto-starting-at-system-boot",level:2},{value:"Updating",id:"updating",level:2},{value:"Updating Docker Container",id:"updating-docker-container",level:3},{value:"Automatic Docker Updates",id:"automatic-docker-updates",level:3},{value:"Updating Dashy from Source",id:"updating-dashy-from-source",level:3},{value:"Verifying a Release Download",id:"verifying-a-release-download",level:3},{value:"Backing Up",id:"backing-up",level:2},{value:"Backing Up Containers",id:"backing-up-containers",level:3},{value:"Backing Up Volumes",id:"backing-up-volumes",level:3},{value:"Dashy-Specific Backup",id:"dashy-specific-backup",level:3},{value:"Scheduling",id:"scheduling",level:2},{value:"SSL Certificates",id:"ssl-certificates",level:2},{value:"Auto-SSL",id:"auto-ssl",level:3},{value:"Getting a Self-Signed SSL Certificate",id:"getting-a-self-signed-ssl-certificate",level:3},{value:"Passing a Self-Signed Certificate to Dashy",id:"passing-a-self-signed-certificate-to-dashy",level:3},{value:"Authentication",id:"authentication",level:2},{value:"Network Exposure",id:"network-exposure",level:2},{value:"Managing Containers with Docker Compose",id:"managing-containers-with-docker-compose",level:2},{value:"Passing in Environmental Variables",id:"passing-in-environmental-variables",level:2},{value:"Setting Headers",id:"setting-headers",level:2},{value:"Example Headers",id:"example-headers",level:3},{value:"Caddy",id:"caddy",level:4},{value:"NGINX",id:"nginx",level:4},{value:"Traefik",id:"traefik",level:4},{value:"HAProxy",id:"haproxy",level:4},{value:"Apache",id:"apache",level:4},{value:"Squid",id:"squid",level:4},{value:"Remote Access",id:"remote-access",level:2},{value:"WireGuard",id:"wireguard",level:3},{value:"Example Server Config",id:"example-server-config",level:4},{value:"Example Client Config",id:"example-client-config",level:4},{value:"Reverse SSH Tunnel",id:"reverse-ssh-tunnel",level:3},{value:"TCP Tunnel",id:"tcp-tunnel",level:3},{value:"Custom Domain",id:"custom-domain",level:2},{value:"Using DNS",id:"using-dns",level:3},{value:"Using NGINX",id:"using-nginx",level:3},{value:"Verifying Releases",id:"verifying-releases",level:2},{value:"Container Security",id:"container-security",level:2},{value:"Keep Docker Up-To-Date",id:"keep-docker-up-to-date",level:3},{value:"Set Resource Quotas",id:"set-resource-quotas",level:3},{value:"Don't Run as Root",id:"dont-run-as-root",level:3},{value:"Specify a User",id:"specify-a-user",level:3},{value:"Limit capabilities",id:"limit-capabilities",level:3},{value:"Prevent new Privileges being Added",id:"prevent-new-privileges-being-added",level:3},{value:"Disable Inter-Container Communication",id:"disable-inter-container-communication",level:3},{value:"Don't Expose the Docker Daemon Socket",id:"dont-expose-the-docker-daemon-socket",level:3},{value:"Use Read-Only Volumes",id:"use-read-only-volumes",level:3},{value:"Set the Logging Level",id:"set-the-logging-level",level:3},{value:"Verify Image before Pulling",id:"verify-image-before-pulling",level:3},{value:"Specify the Tag",id:"specify-the-tag",level:3},{value:"Container Security Scanning",id:"container-security-scanning",level:3},{value:"Registry Security",id:"registry-security",level:3},{value:"Security Modules",id:"security-modules",level:3},{value:"Web Server Configuration",id:"web-server-configuration",level:2},{value:"NGINX",id:"nginx-1",level:3},{value:"Apache",id:"apache-1",level:3},{value:"Caddy",id:"caddy-1",level:3},{value:"Firebase Hosting",id:"firebase-hosting",level:3},{value:"cPanel",id:"cpanel",level:3},{value:"Running a Modified Version of the App",id:"running-a-modified-version-of-the-app",level:2},{value:"Building your Own Container",id:"building-your-own-container",level:2}];function d(e){const n={a:"a",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"app-management",children:"App Management"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"The following article is a primer on managing self-hosted apps. It covers everything from keeping the Dashy (or any other app) up-to-date, secure, backed up, to other topics like auto-starting, monitoring, log management, web server configuration and using custom domains."})}),"\n",(0,r.jsx)(n.h2,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#providing-assets",children:"Providing Assets"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#file-ownership-and-permissions",children:"File Ownership and Permissions"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#running-commands",children:"Running Commands"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#healthchecks",children:"Healthchecks"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#logs-and-performance",children:"Logs and Performance"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#auto-starting-at-system-boot",children:"Auto-Starting at Boot"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#updating",children:"Updating"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#backing-up",children:"Backing Up"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#scheduling",children:"Scheduling"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ssl-certificates",children:"SSL Certificates"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#authentication",children:"Authentication"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#network-exposure",children:"Network Exposure"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#managing-containers-with-docker-compose",children:"Managing with Compose"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#passing-in-environmental-variables",children:"Environmental Variables"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#setting-headers",children:"Setting Headers"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#remote-access",children:"Remote Access"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#custom-domain",children:"Custom Domain"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#verifying-releases",children:"Verifying Releases"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#container-security",children:"Securing Containers"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#web-server-configuration",children:"Web Server Configuration"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#running-a-modified-version-of-the-app",children:"Running a Modified App"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#building-your-own-container",children:"Building your Own Container"})}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"providing-assets",children:"Providing Assets"}),"\n",(0,r.jsxs)(n.p,{children:["Dashy reads everything for your dashboard from a single host directory mounted into the container at ",(0,r.jsx)(n.code,{children:"/app/user-data"}),". This is done with a ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/storage/volumes/",children:"Docker volume"}),", e.g. ",(0,r.jsx)(n.code,{children:"-v /path/to/your/user-data:/app/user-data"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["The directory must contain a ",(0,r.jsx)(n.code,{children:"conf.yml"}),". It can also contain anything else you want served from the web root: sub-config files for additional pages, item icons, favicon, fonts, custom CSS, manifest, and so on. Any file placed there is reachable at ",(0,r.jsx)(n.code,{children:"/"})," in the browser, overriding files of the same name in the bundled ",(0,r.jsx)(n.code,{children:"public/"})," defaults."]}),"\n",(0,r.jsx)(n.p,{children:"Typical contents:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"conf.yml"})," - Main config (required)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"*.yml"})," / ",(0,r.jsx)(n.code,{children:"*.yaml"})," - Sub-config files for ",(0,r.jsx)(n.a,{href:"/docs/pages-and-sections#multi-page-support",children:"multi-page support"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"item-icons/"})," - Local icons referenced by ",(0,r.jsx)(n.code,{children:"icon: ./item-icons/foo.png"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"favicon.ico"}),", ",(0,r.jsx)(n.code,{children:"manifest.json"}),", ",(0,r.jsx)(n.code,{children:"robots.txt"})," - Override the bundled defaults"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"fonts/"}),", ",(0,r.jsx)(n.code,{children:"widget-resources/"})," - Custom fonts or assets used by widgets"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"file-ownership-and-permissions",children:"File Ownership and Permissions"}),"\n",(0,r.jsxs)(n.p,{children:["Inside the container, Dashy runs as a non-root user with uid/gid 1000 (the built-in ",(0,r.jsx)(n.code,{children:"node"})," user from the Node base image). This is fine for the vast majority of installs, since the first user on a default Linux or macOS box is also uid 1000, so a bind-mounted ",(0,r.jsx)(n.code,{children:"user-data"})," directory is read/writable straight away."]}),"\n",(0,r.jsx)(n.p,{children:"It only gets fiddly if your host uid happens to be something else (NAS systems and multi-user servers being the usual culprits). In that case, config saves from the UI will fail with a permission error, because the container's uid 1000 doesn't own your directory."}),"\n",(0,r.jsx)(n.p,{children:"There are two ways to sort it. Pick whichever is less hassle:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Run the container as your own user."})," The cleanest option, since you don't touch host file ownership."]}),"\n",(0,r.jsxs)(n.p,{children:["On ",(0,r.jsx)(n.code,{children:"docker run"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d -p 8080:8080 \\\n --user $(id -u):$(id -g) \\\n -v /path/to/user-data:/app/user-data \\\n lissy93/dashy:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["In compose, uncomment the ",(0,r.jsx)(n.code,{children:"user:"})," line under the service and set it:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'user: "1001:1001" # whatever `id -u` and `id -g` give you\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Hand the directory to uid 1000."})," Quicker if you don't mind changing host ownership:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"sudo chown -R 1000:1000 /path/to/user-data\n"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Note that if you run the container as root (e.g. ",(0,r.jsx)(n.code,{children:"--user 0:0"}),"), Dashy will still work, but you lose the security benefit of a non-root container. Don't do that unless you've a good reason."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"running-commands",children:"Running Commands"}),"\n",(0,r.jsxs)(n.p,{children:["If you're running an app in Docker, then commands will need to be passed to the container to be executed. This can be done by preceding each command with ",(0,r.jsx)(n.code,{children:"docker exec -it [container-id]"}),", where container ID can be found by running ",(0,r.jsx)(n.code,{children:"docker ps"}),". For example ",(0,r.jsx)(n.code,{children:"docker exec -it 26c156c467b4 yarn build"}),". You can also enter the container, with ",(0,r.jsx)(n.code,{children:"docker exec -it [container-id] /bin/ash"}),", and navigate around it with normal Linux commands."]}),"\n",(0,r.jsxs)(n.p,{children:["Dashy has several commands that can be used for various tasks, you can find a list of these either in the ",(0,r.jsx)(n.a,{href:"/docs/developing#project-commands",children:"Developing Docs"}),", or by looking at the ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/package.json#l5",children:(0,r.jsx)(n.code,{children:"package.json"})}),". These can be used by running ",(0,r.jsx)(n.code,{children:"yarn [command-name]"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"healthchecks",children:"Healthchecks"}),"\n",(0,r.jsxs)(n.p,{children:["Healthchecks are configured to periodically check that Dashy is up and running correctly on the specified port. By default, the health script is called every 5 minutes, but this can be modified with the ",(0,r.jsx)(n.code,{children:"--health-interval"})," option. You can check the current container health with: ",(0,r.jsx)(n.code,{children:'docker inspect --format "{{json .State.Health }}" [container-id]'}),", and a summary of health status will show up under ",(0,r.jsx)(n.code,{children:"docker ps"}),". You can also manually request the current application status by running ",(0,r.jsx)(n.code,{children:"docker exec -it [container-id] yarn health-check"}),". You can disable healthchecks altogether by adding the ",(0,r.jsx)(n.code,{children:"--no-healthcheck"})," flag to your Docker run command."]}),"\n",(0,r.jsxs)(n.p,{children:["To restart unhealthy containers automatically, check out ",(0,r.jsx)(n.a,{href:"https://hub.docker.com/r/willfarrell/autoheal/",children:"Autoheal"}),". This image watches for unhealthy containers, and automatically triggers a restart. (This is a stand in for Docker's ",(0,r.jsx)(n.code,{children:"--exit-on-unhealthy"})," that was proposed, but ",(0,r.jsx)(n.a,{href:"https://github.com/moby/moby/pull/22719",children:"not merged"}),"). There's also ",(0,r.jsx)(n.a,{href:"https://github.com/qdm12/deunhealth",children:"Deunhealth"}),", which is super light-weight, and doesn't require network access."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d \\\n --name autoheal \\\n --restart=always \\\n -e AUTOHEAL_CONTAINER_LABEL=all \\\n -v /var/run/docker.sock:/var/run/docker.sock \\\n willfarrell/autoheal\n"})}),"\n",(0,r.jsx)(n.h3,{id:"http-healthcheck-endpoint",children:"HTTP Healthcheck Endpoint"}),"\n",(0,r.jsxs)(n.p,{children:["Dashy also exposes an unauthenticated HTTP liveness endpoint at ",(0,r.jsx)(n.code,{children:"/healthz"}),", which returns a ",(0,r.jsx)(n.code,{children:"200"})," with a small JSON body (",(0,r.jsx)(n.code,{children:"status"}),", ",(0,r.jsx)(n.code,{children:"uptime"}),", ",(0,r.jsx)(n.code,{children:"version"}),"). It bypasses auth and SSL redirection so probes keep working regardless of how Dashy is configured."]}),"\n",(0,r.jsx)(n.p,{children:"Useful when fronting Dashy with a load balancer / reverse proxy that needs an HTTP probe for auto-failover, or when running on Kubernetes:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"# Kubernetes\nlivenessProbe:\n httpGet:\n path: /healthz\n port: 8080\n"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'# Traefik (label on the Dashy service)\n- "traefik.http.services.dashy.loadbalancer.healthcheck.path=/healthz"\n- "traefik.http.services.dashy.loadbalancer.healthcheck.interval=30s"\n'})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-caddyfile",children:"# Caddy (reverse_proxy block)\nreverse_proxy dashy:8080 {\n health_uri /healthz\n health_interval 30s\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["For Nginx Proxy Manager, set the ",(0,r.jsx)(n.em,{children:"Forward Hostname"})," health-check path to ",(0,r.jsx)(n.code,{children:"/healthz"})," under the proxy host's ",(0,r.jsx)(n.em,{children:"Custom locations"})," / advanced config."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"logs-and-performance",children:"Logs and Performance"}),"\n",(0,r.jsx)(n.h3,{id:"container-logs",children:"Container Logs"}),"\n",(0,r.jsxs)(n.p,{children:["You can view logs for a given Docker container with ",(0,r.jsx)(n.code,{children:"docker logs [container-id]"}),", add the ",(0,r.jsx)(n.code,{children:"--follow"})," flag to stream the logs. For more info, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/containers/logging/",children:"Logging Documentation"}),". There's also ",(0,r.jsx)(n.a,{href:"https://dozzle.dev/",children:"Dozzle"}),", a useful tool, that provides a web interface where you can stream and query logs from all your running containers from a single web app."]}),"\n",(0,r.jsx)(n.h3,{id:"container-performance",children:"Container Performance"}),"\n",(0,r.jsxs)(n.p,{children:["You can check the resource usage for your running Docker containers with ",(0,r.jsx)(n.code,{children:"docker stats"})," or ",(0,r.jsx)(n.code,{children:"docker stats [container-id]"}),". For more info, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/stats/",children:"Stats Documentation"}),". There's also ",(0,r.jsx)(n.a,{href:"https://github.com/google/cadvisor",children:"cAdvisor"}),", a useful web app for viewing and analyzing resource usage and performance of all your running containers."]}),"\n",(0,r.jsx)(n.h3,{id:"management-apps",children:"Management Apps"}),"\n",(0,r.jsxs)(n.p,{children:["You can also view logs, resource usage and other info as well as manage your entire Docker workflow in third-party Docker management apps. For example ",(0,r.jsx)(n.a,{href:"https://github.com/portainer/portainer",children:"Portainer"})," an all-in-one open source management web UI for Docker and Kubernetes, or ",(0,r.jsx)(n.a,{href:"https://github.com/jesseduffield/lazydocker",children:"LazyDocker"})," a terminal UI for Docker container management and monitoring."]}),"\n",(0,r.jsx)(n.h3,{id:"advanced-logging-and-monitoring",children:"Advanced Logging and Monitoring"}),"\n",(0,r.jsxs)(n.p,{children:["Docker supports using ",(0,r.jsx)(n.a,{href:"https://prometheus.io/",children:"Prometheus"})," to collect logs, which can then be visualized using a platform like ",(0,r.jsx)(n.a,{href:"https://grafana.com/",children:"Grafana"}),". For more info, see ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/daemon/prometheus/",children:"this guide"}),". If you need to route your logs to a remote syslog, then consider using ",(0,r.jsx)(n.a,{href:"https://github.com/gliderlabs/logspout",children:"logspout"}),". For enterprise-grade instances, there are managed services, that make monitoring container logs and metrics very easy, such as ",(0,r.jsx)(n.a,{href:"https://sematext.com/blog/docker-container-monitoring-with-sematext/",children:"Sematext"})," with ",(0,r.jsx)(n.a,{href:"https://github.com/sematext/logagent-js",children:"Logagent"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"auto-starting-at-system-boot",children:"Auto-Starting at System Boot"}),"\n",(0,r.jsxs)(n.p,{children:["You can use Docker's ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/run/#restart-policies---restart",children:"restart policies"})," to instruct the container to start after a system reboot, or restart after a crash. Just add the ",(0,r.jsx)(n.code,{children:"--restart=always"})," flag to your Docker compose script or Docker run command. For more information, see the docs on ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/containers/start-containers-automatically/",children:"Starting Containers Automatically"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["For Podman, you can use ",(0,r.jsx)(n.code,{children:"systemd"})," to create a service that launches your container, ",(0,r.jsx)(n.a,{href:"https://podman.io/blogs/2018/09/13/systemd.html",children:"the docs"})," explains things further. A similar approach can be used with Docker, if you need to start containers after a reboot, but before any user interaction."]}),"\n",(0,r.jsxs)(n.p,{children:["To restart the container after something within it has crashed, consider using ",(0,r.jsx)(n.a,{href:"https://github.com/willfarrell/docker-autoheal",children:(0,r.jsx)(n.code,{children:"docker-autoheal"})})," by @willfarrell, a service that monitors and restarts unhealthy containers. For more info, see the ",(0,r.jsx)(n.a,{href:"#healthchecks",children:"Healthchecks"})," section above."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"updating",children:"Updating"}),"\n",(0,r.jsx)(n.p,{children:"Dashy is under active development, so to take advantage of the latest features, you may need to update your instance every now and again."}),"\n",(0,r.jsx)(n.h3,{id:"updating-docker-container",children:"Updating Docker Container"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Pull latest image: ",(0,r.jsx)(n.code,{children:"docker pull lissy93/dashy:latest"})]}),"\n",(0,r.jsxs)(n.li,{children:["Kill off existing container\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Find container ID: ",(0,r.jsx)(n.code,{children:"docker ps"})]}),"\n",(0,r.jsxs)(n.li,{children:["Stop container: ",(0,r.jsx)(n.code,{children:"docker stop [container_id]"})]}),"\n",(0,r.jsxs)(n.li,{children:["Remove container: ",(0,r.jsx)(n.code,{children:"docker rm [container_id]"})]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Spin up new container: ",(0,r.jsx)(n.code,{children:"docker run [params] lissy93/dashy"})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"automatic-docker-updates",children:"Automatic Docker Updates"}),"\n",(0,r.jsxs)(n.p,{children:["You can automate the above process using ",(0,r.jsx)(n.a,{href:"https://github.com/containrrr/watchtower",children:"Watchtower"}),".\nWatchtower will watch for new versions of a given image on Docker Hub, pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially."]}),"\n",(0,r.jsx)(n.p,{children:"To get started, spin up the watchtower container:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d \\\n --name watchtower \\\n -v /var/run/docker.sock:/var/run/docker.sock \\\n containrrr/watchtower\n"})}),"\n",(0,r.jsxs)(n.p,{children:["For more information, see the ",(0,r.jsx)(n.a,{href:"https://containrrr.dev/watchtower/",children:"Watchtower Docs"})]}),"\n",(0,r.jsx)(n.h3,{id:"updating-dashy-from-source",children:"Updating Dashy from Source"}),"\n",(0,r.jsxs)(n.p,{children:["Stop your current instance of Dashy, then navigate into the source directory. Pull down the latest code, with ",(0,r.jsx)(n.code,{children:"git pull origin master"}),", then update dependencies with ",(0,r.jsx)(n.code,{children:"yarn"}),", rebuild with ",(0,r.jsx)(n.code,{children:"yarn build"}),", and start the server again with ",(0,r.jsx)(n.code,{children:"yarn start"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"verifying-a-release-download",children:"Verifying a Release Download"}),"\n",(0,r.jsxs)(n.p,{children:["Each ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/releases",children:"GitHub release"})," bundles a SHA256 checksum and a SLSA build-provenance attestation alongside the source tarball (",(0,r.jsx)(n.code,{children:"dashy-.tar.gz"}),"). You don't need either to run Dashy, but they let you confirm a download arrived intact and was genuinely built from our source, rather than tampered with in transit or on a mirror."]}),"\n",(0,r.jsxs)(n.p,{children:["Check the tarball is intact using the ",(0,r.jsx)(n.code,{children:".sha256"})," file published next to it:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"sha256sum -c dashy-.tar.gz.sha256\n"})}),"\n",(0,r.jsxs)(n.p,{children:["An ",(0,r.jsx)(n.code,{children:"OK"})," means the file is untampered. To go further and prove it was built by our CI from our repo, verify the attestation with the ",(0,r.jsx)(n.a,{href:"https://cli.github.com/",children:"GitHub CLI"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"gh attestation verify dashy-.tar.gz --repo lissy93/dashy\n"})}),"\n",(0,r.jsx)(n.p,{children:"The release notes for each version also list the checksum and a link to view the attestation directly."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"backing-up",children:"Backing Up"}),"\n",(0,r.jsx)(n.h3,{id:"backing-up-containers",children:"Backing Up Containers"}),"\n",(0,r.jsxs)(n.p,{children:["You can make a backup of any running container really easily, using ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/commit/",children:(0,r.jsx)(n.code,{children:"docker commit"})})," and save it with ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/export/",children:(0,r.jsx)(n.code,{children:"docker export"})}),", to do so:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["First find the container ID, you can do this with ",(0,r.jsx)(n.code,{children:"docker container ls"})]}),"\n",(0,r.jsxs)(n.li,{children:["Now to create the snapshot, just run ",(0,r.jsx)(n.code,{children:"docker commit -p [container-id] my-backup"})]}),"\n",(0,r.jsxs)(n.li,{children:["Finally, to save the backup locally, run ",(0,r.jsx)(n.code,{children:"docker save -o ~/dashy-backup.tar my-backup"})]}),"\n",(0,r.jsxs)(n.li,{children:["If you want to push this to a container registry, run ",(0,r.jsx)(n.code,{children:"docker push my-backup:latest"})]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Note that this will not include any data in docker volumes, and the process here is a bit different. Since these files exist on your host system, if you have an existing backup solution implemented, you can incorporate and volume files within that system."}),"\n",(0,r.jsx)(n.h3,{id:"backing-up-volumes",children:"Backing Up Volumes"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/offen/docker-volume-backup",children:"offen/docker-volume-backup"})," is a useful tool for periodic Docker volume backups, to any S3-compatible storage provider. It's run as a light-weight Docker container, and is easy to setup, and also supports GPG-encryption, email notification, and routing away older backups."]}),"\n",(0,r.jsxs)(n.p,{children:["To get started, create a docker-compose similar to the example below, and then start the container. For more info, check out their ",(0,r.jsx)(n.a,{href:"https://github.com/offen/docker-volume-backup",children:"documentation"}),", which is very clear."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'services:\n backup:\n image: offen/docker-volume-backup:latest\n environment:\n BACKUP_CRON_EXPRESSION: "0 * * * *"\n BACKUP_PRUNING_PREFIX: backup-\n BACKUP_RETENTION_DAYS: 7\n AWS_BUCKET_NAME: backup-bucket\n AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE\n AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n volumes:\n - data:/backup/my-app-backup:ro\n - /var/run/docker.sock:/var/run/docker.sock:ro\nvolumes:\n data:\n'})}),"\n",(0,r.jsx)(n.p,{children:"It's worth noting that this process can also be done manually, using the following commands:"}),"\n",(0,r.jsx)(n.p,{children:"Backup:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run --rm -v some_volume:/volume -v /tmp:/backup alpine tar -cjf /backup/some_archive.tar.bz2 -C /volume ./\n"})}),"\n",(0,r.jsx)(n.p,{children:"Restore:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'docker run --rm -v some_volume:/volume -v /tmp:/backup alpine sh -c "rm -rf /volume/* /volume/..?* /volume/.[!.]* ; tar -C /volume/ -xjf /backup/some_archive.tar.bz2"\n'})}),"\n",(0,r.jsx)(n.h3,{id:"dashy-specific-backup",children:"Dashy-Specific Backup"}),"\n",(0,r.jsxs)(n.p,{children:["All configuration and dashboard settings are stored in your ",(0,r.jsx)(n.code,{children:"user-data/conf.yml"})," file. If you provide additional assets (like icons, fonts, themes, etc), these will also live in the ",(0,r.jsx)(n.code,{children:"user-data"})," directory. So to backup all Dashy data, this is the only directory you need to backup."]}),"\n",(0,r.jsxs)(n.p,{children:["When you save config through the UI, Dashy automatically creates a timestamped backup in ",(0,r.jsx)(n.code,{children:"user-data/config-backups/"})," (configurable via the ",(0,r.jsx)(n.code,{children:"BACKUP_DIR"})," env var). If you break your config, check that directory for a recent copy. Backups can be disabled by setting ",(0,r.jsx)(n.code,{children:"DISABLE_CONFIG_BACKUPS=true"})," (e.g. on read-only filesystems or where permissions don't allow it)."]}),"\n",(0,r.jsx)(n.p,{children:"Since Dashy is open source, there shouldn't be any need to backup the main container."}),"\n",(0,r.jsxs)(n.p,{children:["Dashy also has a built-in cloud backup feature, which is free for personal users, and will let you make and restore fully encrypted backups of your config directly through the UI. To learn more, see the ",(0,r.jsx)(n.a,{href:"/docs/backup-restore",children:"Cloud Backup Docs"})]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"scheduling",children:"Scheduling"}),"\n",(0,r.jsxs)(n.p,{children:["If you need to periodically schedule the running of a given command on Dashy (or any other container), then a useful tool for doing so it ",(0,r.jsx)(n.a,{href:"https://github.com/mcuadros/ofelia",children:"ofelia"}),". This runs as a Docker container and is really useful for things like backups, logging, updating, notifications, etc. Crons are specified using Go's crontab format, and a useful tool for visualizing this is ",(0,r.jsx)(n.a,{href:"https://crontab.guru/",children:"crontab.guru"}),". This can also be done natively with Alpine: ",(0,r.jsx)(n.code,{children:"docker run -it alpine ls /etc/periodic"}),".\nI recommend combining this with ",(0,r.jsx)(n.a,{href:"https://github.com/healthchecks/healthchecks",children:"healthchecks"})," for easy monitoring of jobs, and failure notifications."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"ssl-certificates",children:"SSL Certificates"}),"\n",(0,r.jsx)(n.p,{children:"Enabling HTTPS with an SSL certificate is recommended, especially if you are hosting Dashy anywhere other than your home. This will ensure that all traffic is encrypted in transit."}),"\n",(0,r.jsx)(n.h3,{id:"auto-ssl",children:"Auto-SSL"}),"\n",(0,r.jsxs)(n.p,{children:["If you are using ",(0,r.jsx)(n.a,{href:"https://nginxproxymanager.com/",children:"NGINX Proxy Manager"}),', then SSL is supported out of the box. Once you\'ve added your proxy host and web address, then set the scheme to HTTPS, then under the SSL Tab select "Request a new SSL certificate" and follow the on-screen instructions.']}),"\n",(0,r.jsxs)(n.p,{children:["If you're hosting Dashy behind Cloudflare, then they offer ",(0,r.jsx)(n.a,{href:"https://www.cloudflare.com/en-gb/learning/ssl/what-is-an-ssl-certificate/",children:"free and easy SSL"}),"- all you need to do is enable it under the SSL/TLS tab. Or if you are using shared hosting, you may find ",(0,r.jsx)(n.a,{href:"https://www.sitepoint.com/a-guide-to-setting-up-lets-encrypt-ssl-on-shared-hosting/",children:"this tutorial"})," helpful."]}),"\n",(0,r.jsx)(n.h3,{id:"getting-a-self-signed-ssl-certificate",children:"Getting a Self-Signed SSL Certificate"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://letsencrypt.org/docs/",children:"Let's Encrypt"})," is a global Certificate Authority, providing free SSL/TLS Domain Validation certificates in order to enable secure HTTPS access to your website. They have good browser/ OS ",(0,r.jsx)(n.a,{href:"https://letsencrypt.org/docs/certificate-compatibility/",children:"compatibility"})," with their ISRG X1 and DST CA X3 root certificates, support ",(0,r.jsx)(n.a,{href:"https://community.letsencrypt.org/t/acme-v2-production-environment-wildcards/55578",children:"Wildcard issuance"})," done via ACMEv2 using the DNS-01 and have ",(0,r.jsx)(n.a,{href:"https://letsencrypt.org/2020/02/19/multi-perspective-validation.html",children:"Multi-Perspective Validation"}),". Let's Encrypt provide ",(0,r.jsx)(n.a,{href:"https://certbot.eff.org/",children:"CertBot"})," an easy app for generating and setting up an SSL certificate."]}),"\n",(0,r.jsxs)(n.p,{children:["This process can be automated, using something like the ",(0,r.jsx)(n.a,{href:"https://github.com/Valian/docker-nginx-auto-ssl",children:"Docker-NGINX-Auto-SSL Container"})," to generate and renew certificates when needed."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're not so comfortable on the command line, then you can use a tool like ",(0,r.jsx)(n.a,{href:"https://www.sslforfree.com/",children:"SSL For Free"})," or ",(0,r.jsx)(n.a,{href:"https://zerossl.com/",children:"ZeroSSL"})," to generate your cert. They also provide step-by-step setup instructions for most platforms."]}),"\n",(0,r.jsx)(n.h3,{id:"passing-a-self-signed-certificate-to-dashy",children:"Passing a Self-Signed Certificate to Dashy"}),"\n",(0,r.jsxs)(n.p,{children:["Once you've generated your SSL cert, you'll need to pass it to Dashy. This can be done by specifying the paths to your public and private keys using the ",(0,r.jsx)(n.code,{children:"SSL_PRIV_KEY_PATH"})," and ",(0,r.jsx)(n.code,{children:"SSL_PUB_KEY_PATH"})," environmental variables. Or if you're using Docker, then just pass public + private SSL keys in under ",(0,r.jsx)(n.code,{children:"/etc/ssl/certs/dashy-pub.pem"})," and ",(0,r.jsx)(n.code,{children:"/etc/ssl/certs/dashy-priv.key"})," respectively, e.g:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d \\\n -p 8080:8080 \\\n -v ~/my-private-key.key:/etc/ssl/certs/dashy-priv.key:ro \\\n -v ~/my-public-key.pem:/etc/ssl/certs/dashy-pub.pem:ro \\\n lissy93/dashy:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["By default the SSL port is ",(0,r.jsx)(n.code,{children:"443"})," within a Docker container, or ",(0,r.jsx)(n.code,{children:"4001"})," if running on bare metal, but you can override this with the ",(0,r.jsx)(n.code,{children:"SSL_PORT"})," environmental variable."]}),"\n",(0,r.jsxs)(n.p,{children:["Once everything is setup, you can verify your site is secured using a tool like ",(0,r.jsx)(n.a,{href:"https://www.sslchecker.com/sslchecker",children:"SSL Checker"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"authentication",children:"Authentication"}),"\n",(0,r.jsxs)(n.p,{children:["Dashy natively supports secure authentication using KeyCloak. There is also a Simple Auth feature that doesn't require any additional setup. Usage instructions for both, as well as alternative auth methods, has now moved to the ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"/docs/authentication",children:"Authentication Docs"})})," page."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"network-exposure",children:"Network Exposure"}),"\n",(0,r.jsx)(n.p,{children:"Dashy is designed to run on your local network, behind your firewall. If you only access it from within your home or over a VPN, the defaults are fine."}),"\n",(0,r.jsxs)(n.p,{children:["If you do need to expose Dashy to the internet, you should put it behind a reverse proxy with its own authentication layer (e.g. Authelia, Authentik, Cloudflare Access, or your proxy's built-in auth). Don't rely solely on Dashy's built-in auth for internet-facing instances - it's a convenience feature for private networks, not a hardened perimeter control. See the ",(0,r.jsx)(n.a,{href:"/docs/authentication",children:"Authentication Docs"})," for setup options."]}),"\n",(0,r.jsxs)(n.p,{children:["When Dashy runs in server mode (the default Docker setup), it exposes several API endpoints for things like status checks, ping checks, config saving, system info, and a CORS proxy used by widgets. When authentication is enabled (via ",(0,r.jsx)(n.code,{children:"ENABLE_HTTP_AUTH=true"})," or ",(0,r.jsx)(n.code,{children:"BASIC_AUTH_USERNAME"}),"/",(0,r.jsx)(n.code,{children:"BASIC_AUTH_PASSWORD"})," env vars), all of these endpoints require valid credentials. Without auth configured, they are open. That's fine for private networks, but not appropriate for public access."]}),"\n",(0,r.jsxs)(n.p,{children:["The CORS proxy (",(0,r.jsx)(n.code,{children:"/cors-proxy"}),") is worth calling out specifically: it forwards requests from the Dashy server to external URLs, so widgets can reach APIs that don't set CORS headers. On a private network this is harmless, but on an internet-exposed instance without auth, it could be abused as an open proxy. Always enable authentication if your instance is reachable from untrusted networks."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"managing-containers-with-docker-compose",children:"Managing Containers with Docker Compose"}),"\n",(0,r.jsxs)(n.p,{children:["When you have a lot of containers, it quickly becomes hard to manage with ",(0,r.jsx)(n.code,{children:"docker run"})," commands. The solution to this is ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/compose/",children:"docker compose"}),", a handy tool for defining all a containers run settings in a single YAML file, and then spinning up that container with a single short command - ",(0,r.jsx)(n.code,{children:"docker compose up"}),". A good example of which can be seen in ",(0,r.jsx)(n.a,{href:"https://github.com/abhilesh/self-hosted_docker_setups",children:"@abhilesh's docker compose collection"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["You can use Dashy's default ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/docker-compose.yml",children:(0,r.jsx)(n.code,{children:"docker-compose.yml"})})," file as a template, and modify it according to your needs."]}),"\n",(0,r.jsx)(n.p,{children:"An example Docker compose, using the default base image from DockerHub, might look something like this:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"services:\n dashy:\n container_name: Dashy\n image: lissy93/dashy:latest\n volumes:\n - ./user-data:/app/user-data\n ports:\n - 4000:8080\n environment:\n - BASE_URL=/my-dashboard\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'node', '/app/services/healthcheck.js']\n interval: 1m30s\n timeout: 10s\n retries: 3\n start_period: 30s\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"passing-in-environmental-variables",children:"Passing in Environmental Variables"}),"\n",(0,r.jsxs)(n.p,{children:["With Docker, you can define environmental variables under the ",(0,r.jsx)(n.code,{children:"environment"})," section of your Docker compose file. Environmental variables are used to configure high-level settings, usually before the config file has been read. For a list of all supported env vars in Dashy, see ",(0,r.jsx)(n.a,{href:"/docs/developing#environmental-variables",children:"the developing docs"}),", or the default ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/.env",children:(0,r.jsx)(n.code,{children:".env"})})," file."]}),"\n",(0,r.jsxs)(n.p,{children:["A common use case, is to run Dashy under a sub-page, instead of at the root of a URL (e.g. ",(0,r.jsx)(n.code,{children:"https://my-homelab.local/dashy"})," instead of ",(0,r.jsx)(n.code,{children:"https://dashy.my-homelab.local"}),"). In this use-case, you'd specify the ",(0,r.jsx)(n.code,{children:"BASE_URL"})," variable in your compose file."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"environment:\n - BASE_URL=/dashy\n"})}),"\n",(0,r.jsxs)(n.p,{children:["You can also do the same thing with the docker run command, using the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file",children:(0,r.jsx)(n.code,{children:"--env"})})," flag.\nIf you've got many environmental variables, you might find it useful to put them in a ",(0,r.jsxs)(n.a,{href:"https://docs.docker.com/compose/env-file/",children:[(0,r.jsx)(n.code,{children:".env"})," file"]}),". Similarly, for Docker run you can use ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file",children:(0,r.jsx)(n.code,{children:"--env-file"})})," if you'd like to pass in a file containing all your environmental variables."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"setting-headers",children:"Setting Headers"}),"\n",(0,r.jsxs)(n.p,{children:["Any external requests made to a different origin (app/ service under a different domain) will be blocked if the correct headers are not specified. This is known as ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"Cross-Origin Resource Sharing"})," (CORS) and is a security feature built into modern browsers."]}),"\n",(0,r.jsx)(n.p,{children:"If you see a CORS error in your console, this can be easily fixed by setting the correct headers. This is not a bug with Dashy, so please don't raise it as a bug!"}),"\n",(0,r.jsx)(n.h3,{id:"example-headers",children:"Example Headers"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#caddy",children:"Caddy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nginx",children:"NGINX"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#traefik",children:"Tr\xe6f\u026ak"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#haproxy",children:"HAProxy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#apache",children:"Apache"})}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsxs)(n.em,{children:["The following section briefly outlines how you can set headers for common web proxies/ servers. More info can be found in the documentation for the proxy that you are using, or in the ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"MDN Docs"}),"."]})}),"\n",(0,r.jsx)(n.p,{children:"These examples are using:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Access-Control-Allow-Origin"})," header, but depending on what type of content you are enabling, this will vary. For example, to allow a site to be loaded in an iframe (for the modal or workspace views) you would use ",(0,r.jsx)(n.code,{children:"X-Frame-Options"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["The domain root (",(0,r.jsx)(n.code,{children:"/"}),"), if your're hosting from a sub-page, replace that with your path."]}),"\n",(0,r.jsxs)(n.li,{children:["A wildcard (",(0,r.jsx)(n.code,{children:"*"}),"), which would allow access from traffic on any domain, this is discouraged, and you should replace it with the URL where you are hosting Dashy. Note that for requests that transport sensitive info, like credentials (e.g. Keycloak login), the wildcard is ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#requests_with_credentials",children:"disallowed all together"})," and will be blocked."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"caddy",children:"Caddy"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsxs)(n.a,{href:"https://caddyserver.com/docs/caddyfile/directives/header",children:["Caddy ",(0,r.jsx)(n.code,{children:"header"})," docs"]})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"headers / {\n Access-Control-Allow-Origin *\n}\n"})}),"\n",(0,r.jsx)(n.h4,{id:"nginx",children:"NGINX"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsxs)(n.a,{href:"https://nginx.org/en/docs/http/ngx_http_headers_module.html",children:["NGINX ",(0,r.jsx)(n.code,{children:"ngx_http_headers_module"})," docs"]})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"location / {\n add_header Access-Control-Allow-Origin *;\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:"Note this can also be done through the UI, using NGINX Proxy Manager."}),"\n",(0,r.jsx)(n.h4,{id:"traefik",children:"Traefik"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsx)(n.a,{href:"https://doc.traefik.io/traefik/middlewares/http/headers/#cors-headers",children:"Tr\xe6f\u026ak CORS headers docs"})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'labels:\n - "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods=GET,OPTIONS,PUT"\n - "traefik.http.middlewares.testheader.headers.accesscontrolalloworiginlist=https://foo.bar.org,https://example.org"\n - "traefik.http.middlewares.testheader.headers.accesscontrolmaxage=100"\n - "traefik.http.middlewares.testheader.headers.addvaryheader=true"\n'})}),"\n",(0,r.jsx)(n.h4,{id:"haproxy",children:"HAProxy"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.haproxy.com/documentation/hapee/latest/traffic-routing/rewrites/rewrite-responses/",children:"HAProxy Rewrite Response Docs"})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"/\n http-response add-header Access-Control-Allow-Origin *\n"})}),"\n",(0,r.jsx)(n.h4,{id:"apache",children:"Apache"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsxs)(n.a,{href:"https://httpd.apache.org/docs/current/mod/mod_headers.html",children:["Apache ",(0,r.jsx)(n.code,{children:"mode_headers"})," docs"]})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'Header always set Access-Control-Allow-Origin "*"\n'})}),"\n",(0,r.jsx)(n.h4,{id:"squid",children:"Squid"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsxs)(n.a,{href:"http://www2.gr.squid-cache.org/Doc/config/request_header_access/",children:["Squid ",(0,r.jsx)(n.code,{children:"request_header_access"})," docs"]})," for more info."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"request_header_access Authorization allow all\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"remote-access",children:"Remote Access"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#wireguard",children:"WireGuard"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#reverse-ssh-tunnel",children:"Reverse SSH Tunnel"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#tcp-tunnel",children:"TCP Tunnel"})}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"wireguard",children:"WireGuard"}),"\n",(0,r.jsxs)(n.p,{children:["Using a VPN is one of the easiest ways to provide secure, full access to your local network from remote locations. ",(0,r.jsx)(n.a,{href:"https://www.wireguard.com/",children:"WireGuard"})," is a reasonably new open source VPN protocol, that was designed with ease of use, performance and security in mind. Unlike OpenVPN, it doesn't need to recreate the tunnel whenever connection is dropped, and it's also much easier to setup, using shared keys instead."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Install Wireguard"})," - See the ",(0,r.jsx)(n.a,{href:"https://www.wireguard.com/install/",children:"Install Docs"})," for download links + instructions\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["On Debian-based systems, it's ",(0,r.jsx)(n.code,{children:"sudo apt install wireguard"})]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Generate a Private Key"})," - Run ",(0,r.jsx)(n.code,{children:"wg genkey"})," on the Wireguard server, and copy it to somewhere safe for later"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Create Server Config"})," - Open or create a file at ",(0,r.jsx)(n.code,{children:"/etc/wireguard/wg0.conf"})," and under ",(0,r.jsx)(n.code,{children:"[Interface]"})," add the following (see example below):\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Address"})," - as a subnet of all desired IPs"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"PrivateKey"})," - that you just generated"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"ListenPort"})," - Default is ",(0,r.jsx)(n.code,{children:"51820"}),", but can be anything"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Get Client App"})," - Download the ",(0,r.jsx)(n.a,{href:"https://www.wireguard.com/install/",children:"WG client app"})," for your platform (Linux, Windows, MacOS, Android or iOS are all supported)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Create new Client Tunnel"})," - On your client app, there should be an option to create a new tunnel, when doing so a client private key will be generated (but if not, use the ",(0,r.jsx)(n.code,{children:"wg genkey"})," command again), and keep it somewhere safe. A public key will also be generated, and this will go in our saver config"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Add Clients to Server Config"})," - Head back to your ",(0,r.jsx)(n.code,{children:"wg0.conf"})," file on the server, create a ",(0,r.jsx)(n.code,{children:"[Peer]"})," section, and populate the following info\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"AllowedIPs"})," - List of IP address inside the subnet, the client should have access to"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"PublicKey"})," - The public key for the client you just generated"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Start the Server"})," - You can now start the WG server, using: ",(0,r.jsx)(n.code,{children:"wg-quick up wg0"})," on your server"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Finish Client Setup"})," - Head back to your client device, and edit the config file, leave the private key as is, and add the following fields:\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"PublicKey"})," - The public key of the server"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Address"})," - This should match the ",(0,r.jsx)(n.code,{children:"AllowedIPs"})," section on the servers config file"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"DNS"})," - The DNS server that'll be used when accessing the network through the VPN"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Endpoint"})," - The hostname or IP + Port where your WG server is running (you may need to forward this in your firewall's settings)"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Done"})," - Your clients should now be able to connect to your WG server :) Depending on your networks firewall rules, you may need to port forward the address of your WG server"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"example-server-config",children:(0,r.jsx)(n.strong,{children:"Example Server Config"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ini",children:"# Server file\n[Interface]\n# Which networks does my interface belong to? Notice: /24 and /64\nAddress = 10.5.0.1/24, 2001:470:xxxx:xxxx::1/64\nPrivateKey = xxx\nListenPort = 51820\n\n# Peer 1\n[Peer]\nPublicKey = xxx\n# Which source IPs can I expect from that peer? Notice: /32 and /128\nAllowedIps = 10.5.0.35/32, 2001:470:xxxx:xxxx::746f:786f/128\n\n# Peer 2\n[Peer]\nPublicKey = xxx\n# Which source IPs can I expect from that peer? This one has a LAN which can\n# access hosts/jails without NAT.\n# Peer 2 has a single IP address inside the VPN: it's 10.5.0.25/32\nAllowedIps = 10.5.0.25/32,10.21.10.0/24,10.21.20.0/24,10.21.30.0/24,10.31.0.0/24,2001:470:xxxx:xxxx::ca:571e/128\n"})}),"\n",(0,r.jsx)(n.h4,{id:"example-client-config",children:(0,r.jsx)(n.strong,{children:"Example Client Config"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ini",children:"[Interface]\n# Which networks does my interface belong to? Notice: /24 and /64\nAddress = 10.5.0.35/24, 2001:470:xxxx:xxxx::746f:786f/64\nPrivateKey = xxx\n\n# Server\n[Peer]\nPublicKey = xxx\n# I want to route everything through the server, both IPv4 and IPv6. All IPs are\n# thus available through the Server, and I can expect packets from any IP to\n# come from that peer.\nAllowedIPs = 0.0.0.0/0, ::0/0\n# Where is the server on the internet? This is a public address. The port\n# (:51820) is the same as ListenPort in the [Interface] of the Server file above\nEndpoint = 1.2.3.4:51820\n# Usually, clients are behind NAT. to keep the connection running, keep alive.\nPersistentKeepalive = 15\n"})}),"\n",(0,r.jsxs)(n.p,{children:["A useful tool for getting WG setup is ",(0,r.jsx)(n.a,{href:"https://github.com/trailofbits/algo",children:"Algo"}),". It includes scripts and docs which cover almost all devices, platforms and clients, and has best practices implemented, and security features enabled. All of this is better explained in ",(0,r.jsx)(n.a,{href:"https://blog.trailofbits.com/2016/12/12/meet-algo-the-vpn-that-works/",children:"this blog post"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"reverse-ssh-tunnel",children:"Reverse SSH Tunnel"}),"\n",(0,r.jsxs)(n.p,{children:["SSH (or ",(0,r.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/Secure_Shell",children:"Secure Shell"}),") is a secure tunnel that allows you to connect to a remote host. Unlike the VPN methods, an SSH connection does not require an intermediary, and will not be affected by your IP changing. However it only allows you to access a single service at a time. SSH was really designed for terminal access, but because of the latter mentioned benefits it's useful to setup, as a fallback option."]}),"\n",(0,r.jsx)(n.p,{children:"Directly SSH'ing into your home, would require you to open a port (usually 22), which would be terrible for security, and is not recommended. However a reverse SSH connection is initiated from inside your network. Once the connection is established, the port is redirected, allowing you to use the established connection to SSH into your home network."}),"\n",(0,r.jsxs)(n.p,{children:["The issue you've probably spotted, is that most public, corporate, and institutional networks will block SSH connections. To overcome this, you'd have to establish a server outside of your homelab that your homelab's device could SSH into to establish the reverse SSH connection. You can then connect to that remote server (the ",(0,r.jsx)(n.em,{children:"mothership"}),"), which in turn connects to your home network."]}),"\n",(0,r.jsxs)(n.p,{children:["Now all of this is starting to sound like quite a lot of work, but this is where services like ",(0,r.jsx)(n.a,{href:"https://remote.it/",children:"remot3.it"})," come in. They maintain the intermediary mothership server, and create the tunnel service for you. It's free for personal use, secure and easy. There are several similar services, such as ",(0,r.jsx)(n.a,{href:"https://remoteiot.com/",children:"RemoteIoT"}),", or you could create your own on a cloud VPS (see ",(0,r.jsx)(n.a,{href:"https://gist.github.com/nileshtrivedi/4c615e8d3c1bf053b0d31176b9e69e42",children:"this tutorial"})," for more info on that)."]}),"\n",(0,r.jsxs)(n.p,{children:["Before getting started, you'll need to head over to ",(0,r.jsx)(n.a,{href:"https://app.remote.it/auth/#/sign-up",children:"Remote.it"})," and create an account."]}),"\n",(0,r.jsx)(n.p,{children:"Then setup your local device:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["If you haven't already done so, you'll need to enable and configure SSH.\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["This is out-of-scope of this article, but I've explained it in detail in ",(0,r.jsx)(n.a,{href:"https://notes.aliciasykes.com/22798/my-server-setup#configure-ssh",children:"this post"}),"."]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Download the Remote.it install script from their ",(0,r.jsx)(n.a,{href:"https://github.com/remoteit/installer",children:"GitHub"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"curl -LkO https://raw.githubusercontent.com/remoteit/installer/master/scripts/auto-install.sh"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Make it executable, with ",(0,r.jsx)(n.code,{children:"chmod +x ./auto-install.sh"}),", and then run it with ",(0,r.jsx)(n.code,{children:"sudo ./auto-install.sh"})]}),"\n",(0,r.jsxs)(n.li,{children:["Finally, configure your device, by running ",(0,r.jsx)(n.code,{children:"sudo connectd_installer"})," and following the on-screen instructions"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"And when you're ready to connect to it:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Login to ",(0,r.jsx)(n.a,{href:"https://app.remote.it/",children:"app.remote.it"}),", and select the name of your device"]}),"\n",(0,r.jsx)(n.li,{children:"You should see a list of running services, click SSH"}),"\n",(0,r.jsx)(n.li,{children:"You'll then be presented with some SSH credentials that you can now use to securely connect to your home, via the Remote.it servers"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Done :)"}),"\n",(0,r.jsx)(n.h3,{id:"tcp-tunnel",children:"TCP Tunnel"}),"\n",(0,r.jsxs)(n.p,{children:["If you're running Dashy on your local network, behind a firewall, but need to temporarily share it with someone external, this can be achieved quickly and securely using ",(0,r.jsx)(n.a,{href:"https://ngrok.com/",children:"Ngrok"}),". It's basically a super slick, encrypted TCP tunnel that provides an internet-accessible address that anyone use to access your local service, from anywhere."]}),"\n",(0,r.jsxs)(n.p,{children:["To get started, ",(0,r.jsx)(n.a,{href:"https://ngrok.com/download",children:"Download"})," and install Ngrok for your system, then just run ",(0,r.jsx)(n.code,{children:"ngrok http [port]"})," (replace the port with the http port where Dashy is running, e.g. 8080). When ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs#http-local-https",children:"using https"}),", specify the full local url/ ip including the protocol."]}),"\n",(0,r.jsxs)(n.p,{children:["Some Ngrok features require you to be authenticated, you can ",(0,r.jsx)(n.a,{href:"https://dashboard.ngrok.com/signup",children:"create a free account"})," and generate a token in ",(0,r.jsx)(n.a,{href:"https://dashboard.ngrok.com/auth/your-authtoken",children:"your dashboard"}),", then run ",(0,r.jsx)(n.code,{children:"ngrok authtoken [token]"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["It's recommended to use authentication for any publicly accessible service. Dashy has an ",(0,r.jsx)(n.a,{href:"/docs/authentication",children:"Auth"})," feature built in, but an even easier method it to use the ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs#http-auth",children:(0,r.jsx)(n.code,{children:"-auth"})})," switch. E.g. ",(0,r.jsx)(n.code,{children:'ngrok http -auth="username:password123" 8080'})]}),"\n",(0,r.jsxs)(n.p,{children:["By default, your web app is assigned a randomly generated ngrok domain, but you can also use your own custom domain. Under the ",(0,r.jsx)(n.a,{href:"https://dashboard.ngrok.com/endpoints/domains",children:"Domains Tab"})," of your Ngrok dashboard, add your domain, and follow the CNAME instructions. You can now use your domain, with the ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs#http-custom-domains",children:(0,r.jsx)(n.code,{children:"-hostname"})})," switch, e.g. ",(0,r.jsx)(n.code,{children:"ngrok http -region=us -hostname=dashy.example.com 8080"}),". If you don't have your own domain name, you can instead use a custom sub-domain (e.g. ",(0,r.jsx)(n.code,{children:"alicia-dashy.ngrok.io"}),"), using the ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs#custom-subdomain-names",children:(0,r.jsx)(n.code,{children:"-subdomain"})})," switch."]}),"\n",(0,r.jsxs)(n.p,{children:["To integrate this into your docker-compose, take a look at the ",(0,r.jsx)(n.a,{href:"https://github.com/gtriggiano/ngrok-tunnel",children:"gtriggiano/ngrok-tunnel"})," container."]}),"\n",(0,r.jsxs)(n.p,{children:["There's so much more you can do with Ngrok, such as exposing a directory as a file browser, using websockets, relaying requests, rewriting headers, inspecting traffic, TLS and TCP tunnels and lots more. All or which is explained in ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs",children:"the Documentation"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["It's worth noting that Ngrok isn't the only option here, other options include: ",(0,r.jsx)(n.a,{href:"https://github.com/fatedier/frp",children:"FRP"}),", ",(0,r.jsx)(n.a,{href:"https://inlets.dev",children:"Inlets"}),", ",(0,r.jsx)(n.a,{href:"https://localtunnel.me/",children:"Local Tunnel"}),", ",(0,r.jsx)(n.a,{href:"https://tailscale.com/",children:"TailScale"}),", etc. Check out ",(0,r.jsx)(n.a,{href:"https://github.com/anderspitman/awesome-tunneling",children:"Awesome Tunneling"})," for a list of alternatives."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"custom-domain",children:"Custom Domain"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#using-dns",children:"Using DNS"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#using-nginx",children:"Using NGINX"})}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"using-dns",children:"Using DNS"}),"\n",(0,r.jsxs)(n.p,{children:["For locally running services, a domain can be set up directly in the DNS records. This method is really quick and easy, and doesn't require you to purchase an actual domain. Just update your networks DNS resolver, to point your desired URL to the local IP where Dashy (or any other app) is running. For example, a line in your hosts file might look something like: ",(0,r.jsx)(n.code,{children:"192.168.0.2 dashy.homelab.local"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're using Pi-Hole, a similar thing can be done in the ",(0,r.jsx)(n.code,{children:"/etc/dnsmasq.d/03-custom-dns.conf"})," file, add a line like: ",(0,r.jsx)(n.code,{children:"address=/dashy.example.com/192.168.2.0"})," for each of your services."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're running OPNSense/ PfSense, then this can be done through the UI with Unbound, it's explained nicely in ",(0,r.jsx)(n.a,{href:"https://homenetworkguy.com/how-to/use-custom-domain-name-in-internal-network/",children:"this article"}),", by Dustin Casto."]}),"\n",(0,r.jsx)(n.h3,{id:"using-nginx",children:"Using NGINX"}),"\n",(0,r.jsx)(n.p,{children:"If you're using NGINX, then you can use your own domain name, with a config similar to the below example."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"upstream dashy {\n server 127.0.0.1:32400;\n}\n\nserver {\n listen 8080;\n server_name dashy.mydomain.com;\n\n # Setup SSL\n ssl_certificate /var/www/mydomain/sslcert.pem;\n ssl_certificate_key /var/www/mydomain/sslkey.pem;\n ssl_protocols TLSv1.2 TLSv1.3;\n ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';\n ssl_session_timeout 5m;\n ssl_prefer_server_ciphers on;\n\n location / {\n proxy_pass http://dashy;\n proxy_redirect off;\n proxy_buffering off;\n proxy_set_header host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;\n }\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Similarly, a basic ",(0,r.jsx)(n.code,{children:"Caddyfile"})," might look like:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"dashy.example.com {\n reverse_proxy / nginx:8080\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["For more info, ",(0,r.jsx)(n.a,{href:"https://thehomelab.wiki/books/dns-reverse-proxy/page/create-domain-records-to-point-to-your-home-server-on-cloudflare-using-nginx-progy-manager",children:"this guide"})," on Setting up Domains with NGINX Proxy Manager and CloudFlare may be useful."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"verifying-releases",children:"Verifying Releases"}),"\n",(0,r.jsx)(n.p,{children:"Everything Dashy publishes can be verified, so you can check that what you're running is what our CI actually built."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Docker images"}),": Every image pushed to GHCR has a signed SBOM (software bill of materials) and build provenance attestation attached. Verify with the ",(0,r.jsx)(n.a,{href:"https://cli.github.com/",children:"GitHub CLI"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"gh attestation verify oci://ghcr.io/lissy93/dashy:latest --owner lissy93\n"})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"GitHub releases (non-Docker)"}),": Each ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/releases",children:"release"})," includes a pre-built tarball, along with a SHA256 checksum and its own provenance attestation. To check your download:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"sha256sum -c dashy-.tar.gz.sha256\ngh attestation verify dashy-.tar.gz --owner lissy93\n"})}),"\n",(0,r.jsx)(n.p,{children:"If verification passes, the artifact was built by our GitHub Actions workflow, from the Dashy repo, and hasn't been tampered with since."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"container-security",children:"Container Security"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#keep-docker-up-to-date",children:"Keep Docker Up-To-Date"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#set-resource-quotas",children:"Set Resource Quotas"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#dont-run-as-root",children:"Don't Run as Root"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#specify-a-user",children:"Specify a User"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#limit-capabilities",children:"Limit Capabilities"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#prevent-new-privileges-being-added",children:"Prevent new Privileges being Added"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#disable-inter-container-communication",children:"Disable Inter-Container Communication"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#dont-expose-the-docker-daemon-socket",children:"Don't Expose the Docker Daemon Socket"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#use-read-only-volumes",children:"Use Read-Only Volumes"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#set-the-logging-level",children:"Set the Logging Level"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#verify-image-before-pulling",children:"Verify Image before Pulling"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#specify-the-tag",children:"Specify the Tag"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#container-security-scanning",children:"Container Security Scanning"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#registry-security",children:"Registry Security"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#security-modules",children:"Security Modules"})}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"keep-docker-up-to-date",children:"Keep Docker Up-To-Date"}),"\n",(0,r.jsxs)(n.p,{children:["To prevent known container escape vulnerabilities, which typically end in escalating to root/administrator privileges, patching Docker Engine and Docker Machine is crucial. For more info, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/install/",children:"Docker Installation Docs"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"set-resource-quotas",children:"Set Resource Quotas"}),"\n",(0,r.jsxs)(n.p,{children:["Docker enables you to limit resource consumption (CPU, memory, disk) on a per-container basis. This not only enhances system performance, but also prevents a compromised container from consuming a large amount of resources, in order to disrupt service or perform malicious activities. To learn more, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/containers/resource_constraints/",children:"Resource Constraints Docs"})]}),"\n",(0,r.jsxs)(n.p,{children:["For example, to run Dashy with max of 1GB ram, and max of 50% of 1 CP core:\n",(0,r.jsx)(n.code,{children:'docker run -d -p 8080:8080 --cpus=".5" --memory="1024m" lissy93/dashy:latest'})]}),"\n",(0,r.jsx)(n.h3,{id:"dont-run-as-root",children:"Don't Run as Root"}),"\n",(0,r.jsxs)(n.p,{children:["Running Docker commands with ",(0,r.jsx)(n.code,{children:"sudo"})," gives the container more host-level access than it needs. You should run Docker as a non-root host user instead."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're facing permission issues on Debian-based systems when running Docker commands without ",(0,r.jsx)(n.code,{children:"sudo"}),", you may need to add your user to the Docker group. First create the group: ",(0,r.jsx)(n.code,{children:"sudo groupadd docker"}),", then add your (non-root) user: ",(0,r.jsx)(n.code,{children:"sudo usermod \u2212aG docker [my-username]"}),", finally ",(0,r.jsx)(n.code,{children:"newgrp docker"})," to refresh."]}),"\n",(0,r.jsx)(n.h3,{id:"specify-a-user",children:"Specify a User"}),"\n",(0,r.jsxs)(n.p,{children:["For containers in general, running as an unprivileged user is one of the best ways to prevent privilege escalation attacks. You can specify a user with the ",(0,r.jsxs)(n.a,{href:"https://docs.docker.com/engine/reference/run/#user",children:[(0,r.jsx)(n.code,{children:"--user"})," param"]}),", using the user ID (",(0,r.jsx)(n.code,{children:"UID"}),") from ",(0,r.jsx)(n.code,{children:"id -u"})," and group ID (",(0,r.jsx)(n.code,{children:"GID"}),") from ",(0,r.jsx)(n.code,{children:"id -g"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Note for Dashy:"})," If you use features that write to disk (saving config through the UI), the process needs write access to ",(0,r.jsx)(n.code,{children:"/app/user-data/"}),". Since the default image creates these directories as root, running with ",(0,r.jsx)(n.code,{children:"--user"})," will cause those features to fail with permission errors unless you also fix ownership of the mounted volumes. If you only use Dashy in read-only mode, running as a non-root user works fine:"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.code,{children:"docker run --user 1000:1000 -p 8080:8080 lissy93/dashy"})}),"\n",(0,r.jsx)(n.p,{children:"Or with Docker Compose, using an environmental variable:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"services:\n dashy:\n image: lissy93/dashy\n user: ${CURRENT_UID}\n ports: [ 4000:8080 ]\n"})}),"\n",(0,r.jsxs)(n.p,{children:["And then to set the variable, and start the container, run: ",(0,r.jsx)(n.code,{children:"CURRENT_UID=$(id -u):$(id -g) docker-compose up"})]}),"\n",(0,r.jsx)(n.h3,{id:"limit-capabilities",children:"Limit capabilities"}),"\n",(0,r.jsxs)(n.p,{children:["Docker containers run with a subset of ",(0,r.jsx)(n.a,{href:"https://man7.org/linux/man-pages/man7/capabilities.7.html",children:"Linux Kernal's Capabilities"})," by default. It's good practice to drop privilege permissions that are not needed for any given container."]}),"\n",(0,r.jsxs)(n.p,{children:["With Docker run, you can use the ",(0,r.jsx)(n.code,{children:"--cap-drop"})," flag to remove capabilities, you can also use ",(0,r.jsx)(n.code,{children:"--cap-drop=all"})," and then define just the required permissions using the ",(0,r.jsx)(n.code,{children:"--cap-add"})," option. For a list of available capabilities, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities",children:"Privilege Capabilities Docs"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Note that dropping privileges and capabilities on runtime is not fool-proof, and often any leftover privileges can be used to re-escalate, see ",(0,r.jsx)(n.a,{href:"https://wiki.sei.cmu.edu/confluence/display/c/POS36-C.+Observe+correct+revocation+order+while+relinquishing+privileges",children:"POS36-C"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"Here's an example using docker-compose, removing privileges that are not required for Dashy to run:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"services:\n dashy:\n image: lissy93/dashy\n ports: [ 4000:8080 ]\n cap_drop:\n - ALL\n cap_add:\n - CHOWN\n - SETGID\n - SETUID\n - DAC_OVERRIDE\n - NET_BIND_SERVICE\n"})}),"\n",(0,r.jsx)(n.h3,{id:"prevent-new-privileges-being-added",children:"Prevent new Privileges being Added"}),"\n",(0,r.jsxs)(n.p,{children:["To prevent processes inside the container from getting additional privileges, pass in the ",(0,r.jsx)(n.code,{children:"--security-opt=no-new-privileges:true"})," option to the Docker run command (see ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/run/#security-configuration",children:"docs"}),")."]}),"\n",(0,r.jsxs)(n.p,{children:["Run Command:\n",(0,r.jsx)(n.code,{children:"docker run --security-opt=no-new-privileges:true -p 8080:8080 lissy93/dashy"})]}),"\n",(0,r.jsx)(n.p,{children:"Docker Compose"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"security_opt:\n- no-new-privileges:true\n"})}),"\n",(0,r.jsx)(n.h3,{id:"disable-inter-container-communication",children:"Disable Inter-Container Communication"}),"\n",(0,r.jsxs)(n.p,{children:["By default Docker containers can talk to each other (using ",(0,r.jsxs)(n.a,{href:"https://docs.docker.com/config/containers/container-networking/",children:[(0,r.jsx)(n.code,{children:"docker0"})," bridged network"]}),"). If you don't need this capability, then it should be disabled. This can be done with the ",(0,r.jsx)(n.code,{children:"--icc=false"})," in your run command. You can learn more about how to facilitate secure communication between containers in the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/compose/networking/",children:"Compose Networking docs"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"dont-expose-the-docker-daemon-socket",children:"Don't Expose the Docker Daemon Socket"}),"\n",(0,r.jsxs)(n.p,{children:["Docker socket ",(0,r.jsx)(n.code,{children:"/var/run/docker.sock"})," is the UNIX socket that Docker is listening to. This is the primary entry point for the Docker API. The owner of this socket is root. Giving someone access to it is equivalent to giving unrestricted root access to your host."]}),"\n",(0,r.jsxs)(n.p,{children:["You should ",(0,r.jsx)(n.strong,{children:"not"})," enable TCP Docker daemon socket (",(0,r.jsx)(n.code,{children:"-H tcp://0.0.0.0:XXX"}),"), as doing so exposes un-encrypted and unauthenticated direct access to the Docker daemon, and if the host is connected to the internet, the daemon on your computer can be used by anyone from the public internet- which is bad. If you need TCP, you should ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-socket-option",children:"see the docs"})," to understand how to do this more securely.\nSimilarly, never expose ",(0,r.jsx)(n.code,{children:"/var/run/docker.sock"})," to other containers as a volume, as it can be exploited."]}),"\n",(0,r.jsx)(n.h3,{id:"use-read-only-volumes",children:"Use Read-Only Volumes"}),"\n",(0,r.jsxs)(n.p,{children:["You can specify that a volume should be read-only by appending ",(0,r.jsx)(n.code,{children:":ro"})," to the ",(0,r.jsx)(n.code,{children:"-v"})," switch. If you don't need the in-app config editor, mount your ",(0,r.jsx)(n.code,{children:"user-data"})," read-only:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d \\\n -p 8080:8080 \\\n -v ~/dashy-data:/app/user-data:ro \\\n lissy93/dashy:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["If you do want config changes from the UI to persist back to disk, leave the mount writable. You can also use ",(0,r.jsx)(n.code,{children:"--read-only"})," to make the whole container filesystem read-only, but in that case UI-driven config edits will not be saved."]}),"\n",(0,r.jsx)(n.h3,{id:"set-the-logging-level",children:"Set the Logging Level"}),"\n",(0,r.jsxs)(n.p,{children:["Logging is important, as it enables you to review events in the future, and in the case of a compromise this will let get an idea of what may have happened. The default log level is ",(0,r.jsx)(n.code,{children:"INFO"}),", and this is also the recommendation, use ",(0,r.jsx)(n.code,{children:"--log-level info"})," to ensure this is set."]}),"\n",(0,r.jsx)(n.h3,{id:"verify-image-before-pulling",children:"Verify Image before Pulling"}),"\n",(0,r.jsx)(n.p,{children:"Only use trusted images, from verified/ official sources. If an app is open source, it is more likely to be safe, as anyone can verify the code. There are also tools available for scanning containers,"}),"\n",(0,r.jsx)(n.p,{children:"Unless otherwise configured, containers can communicate among each other, so running one bad image may lead to other areas of your setup being compromised. Docker images typically contain both original code, as well as up-stream packages, and even if that image has come from a trusted source, the up-stream packages it includes may not have."}),"\n",(0,r.jsxs)(n.p,{children:["Every Dashy image published to ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/pkgs/container/dashy",children:"GHCR"})," ships with a build-provenance attestation and an SBOM (software bill of materials), both signed keylessly via ",(0,r.jsx)(n.a,{href:"https://www.sigstore.dev/",children:"Sigstore"}),". Provenance cryptographically ties the image back to the exact GitHub Actions run and commit that built it, so you can confirm it really came from our pipeline and was not swapped out along the way. The SBOM lists every package baked into the image, which is handy when a new CVE lands and you want to know in seconds whether you're affected."]}),"\n",(0,r.jsxs)(n.p,{children:["To verify the image you're about to run, use the ",(0,r.jsx)(n.a,{href:"https://cli.github.com/",children:"GitHub CLI"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"gh attestation verify oci://ghcr.io/lissy93/dashy:latest --repo lissy93/dashy\n"})}),"\n",(0,r.jsx)(n.p,{children:"A green check means it was genuinely built by us, from our repo. Worth doing on a fresh Proxmox or homelab box, especially before exposing Dashy beyond your LAN."}),"\n",(0,r.jsxs)(n.p,{children:["To pull the SBOM and inspect what's inside, use ",(0,r.jsx)(n.a,{href:"https://github.com/sigstore/cosign",children:"cosign"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"cosign download sbom ghcr.io/lissy93/dashy:latest\n"})}),"\n",(0,r.jsx)(n.h3,{id:"specify-the-tag",children:"Specify the Tag"}),"\n",(0,r.jsxs)(n.p,{children:["Using fixed tags (as opposed to ",(0,r.jsx)(n.code,{children:":latest"})," ) will ensure immutability, meaning the base image will not change between builds. Note that for Dashy, the app is being actively developed, new features, bug fixes and general improvements are merged each week, and if you use a fixed version you will not enjoy these benefits. So it's up to you weather you would prefer a stable and reproducible environment, or the latest features and enhancements."]}),"\n",(0,r.jsx)(n.h3,{id:"container-security-scanning",children:"Container Security Scanning"}),"\n",(0,r.jsxs)(n.p,{children:["It's helpful to be aware of any potential security issues in any of the Docker images you are using. You can run a quick scan using Snyk on any image to output known vulnerabilities using ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/engine/scan/",children:"Docker scan"}),", e.g: ",(0,r.jsx)(n.code,{children:"docker scan lissy93/dashy:latest"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["A similar product is ",(0,r.jsx)(n.a,{href:"https://github.com/aquasecurity/trivy",children:"Trivy"}),", which is free an open source. First install it (with your package manager), then to scan an image, just run: ",(0,r.jsx)(n.code,{children:"trivy image lissy93/dashy:latest"})]}),"\n",(0,r.jsxs)(n.p,{children:["For larger systems, RedHat ",(0,r.jsx)(n.a,{href:"https://www.redhat.com/en/topics/containers/what-is-clair",children:"Clair"})," is an app for parsing image contents and reporting on any found vulnerabilities. You run it locally in a container, and configure it with YAML. It can be integrated with Red Hat Quay, to show results on a dashboard. Most of these use static analysis to find potential issues, and scan included packages for any known security vulnerabilities."]}),"\n",(0,r.jsx)(n.h3,{id:"registry-security",children:"Registry Security"}),"\n",(0,r.jsxs)(n.p,{children:["Although over-kill for most users, you could run your own registry locally which would give you ultimate control over all images, see the ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/registry/deploying/",children:"Deploying a Registry Docs"})," for more info. Another option is ",(0,r.jsx)(n.a,{href:"https://docker-docs.netlify.app/ee/dtr/",children:"Docker Trusted Registry"}),", it's great for enterprise applications, it sits behind your firewall, running on a swarm managed by Docker Universal Control Plane, and lets you securely store and manage your Docker images, mitigating the risk of breaches from the internet."]}),"\n",(0,r.jsx)(n.h3,{id:"security-modules",children:"Security Modules"}),"\n",(0,r.jsx)(n.p,{children:"Docker supports several modules that let you write your own security profiles."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://www.apparmor.net/",children:"AppArmor"}),"is a kernel module that proactively protects the operating system and applications from external or internal threats, by enabling you to restrict programs' capabilities with per-program profiles. You can specify either a security policy by name, or by file path with the ",(0,r.jsx)(n.code,{children:"apparmor"})," flag in docker run. Learn more about writing profiles, ",(0,r.jsx)(n.a,{href:"https://gitlab.com/apparmor/apparmor/-/wikis/QuickProfileLanguage",children:"here"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/Seccomp",children:"Seccomp"})," (Secure Computing Mode) is a sandboxing facility in the Linux kernel that acts like a firewall for system calls (syscalls). It uses Berkeley Packet Filter (BPF) rules to filter syscalls and control how they are handled. These filters can significantly limit a containers access to the Docker Host's Linux kernel - especially for simple containers/applications. It requires a Linux-based Docker host, with secomp enabled, and you can check for this by running ",(0,r.jsx)(n.code,{children:"docker info | grep seccomp"}),". A great resource for learning more about this is ",(0,r.jsx)(n.a,{href:"https://training.play-with-docker.com/security-seccomp/",children:"DockerLabs"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"web-server-configuration",children:"Web Server Configuration"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"The following section only applies if you are not using Docker, and would like to use your own web server"})}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Dashy ships with a pre-configured Node.js server, in ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/server.js",children:(0,r.jsx)(n.code,{children:"server.js"})})," which serves up the contents of the ",(0,r.jsx)(n.code,{children:"./dist"})," directory on a given port. You can start the server by running ",(0,r.jsx)(n.code,{children:"node server"}),". Note that the app must have been build (run ",(0,r.jsx)(n.code,{children:"yarn build"}),"), and you need ",(0,r.jsx)(n.a,{href:"https://nodejs.org",children:"Node.js"})," installed."]}),"\n",(0,r.jsxs)(n.p,{children:["If you wish to run Dashy from a sub page (e.g. ",(0,r.jsx)(n.code,{children:"example.com/dashy"}),"), then just set the ",(0,r.jsx)(n.code,{children:"BASE_URL"})," environmental variable to that page name (in this example, ",(0,r.jsx)(n.code,{children:"/dashy"}),"), before building the app, and the path to all assets will then resolve to the new path, instead of ",(0,r.jsx)(n.code,{children:"./"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"However, since Dashy is just a static web application, it can be served with whatever server you like. The following section outlines how you can configure a web server."}),"\n",(0,r.jsxs)(n.p,{children:["Note, that if you choose not to use ",(0,r.jsx)(n.code,{children:"server.js"})," to serve up the app, you will loose access to the following features:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Loading page, while the app is building"}),"\n",(0,r.jsx)(n.li,{children:"Writing config file to disk from the UI"}),"\n",(0,r.jsx)(n.li,{children:"Website status indicators, and ping checks"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Example Configs"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nginx",children:"NGINX"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#apache",children:"Apache"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#caddy",children:"Caddy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#firebase-hosting",children:"Firebase"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cpanel",children:"cPanel"})}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"nginx-1",children:"NGINX"}),"\n",(0,r.jsxs)(n.p,{children:["Create a new file in ",(0,r.jsx)(n.code,{children:"/etc/nginx/sites-enabled/dashy"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"server {\n\tlisten 8080;\n\tlisten [::]:8080;\n\n\troot /var/www/dashy/html;\n\tindex index.html;\n\n\tserver_name your-domain.com www.your-domain.com;\n\n\tlocation / {\n\t\ttry_files $uri $uri/ =404;\n\t}\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["To use HTML5 history mode (the default - controlled via the ",(0,r.jsx)(n.code,{children:"VITE_APP_ROUTING_MODE"})," build-time env var), replace the inside of the location block with: ",(0,r.jsx)(n.code,{children:"try_files $uri $uri/ /index.html;"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Then upload the build contents of Dashy's dist directory to that location.\nFor example: ",(0,r.jsx)(n.code,{children:"scp -r ./dist/* [username]@[server_ip]:/var/www/dashy/html"})]}),"\n",(0,r.jsx)(n.h3,{id:"apache-1",children:"Apache"}),"\n",(0,r.jsxs)(n.p,{children:["Copy Dashy's dist folder to your apache server, ",(0,r.jsx)(n.code,{children:"sudo cp -r ./dashy/dist /var/www/html/dashy"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["In your Apache config, ",(0,r.jsx)(n.code,{children:"/etc/apche2/apache2.conf"})," add:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"\n\tOptions Indexes FollowSymLinks\n\tAllowOverride All\n\tRequire all granted\n\n\n\n RewriteEngine On\n RewriteBase /\n RewriteRule ^index\\.html$ - [L]\n RewriteCond %{REQUEST_FILENAME} !-f\n RewriteCond %{REQUEST_FILENAME} !-d\n RewriteRule . /index.html [L]\n\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Add a ",(0,r.jsx)(n.code,{children:".htaccess"})," file within ",(0,r.jsx)(n.code,{children:"/var/www/html/dashy/.htaccess"}),", and add:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Options -MultiViews\nRewriteEngine On\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteRule ^ index.html [QSA,L]\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Then restart Apache, with ",(0,r.jsx)(n.code,{children:"sudo systemctl restart apache2"})]}),"\n",(0,r.jsx)(n.h3,{id:"caddy-1",children:"Caddy"}),"\n",(0,r.jsx)(n.p,{children:"Caddy v2"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"try_files {path} /\n"})}),"\n",(0,r.jsx)(n.p,{children:"Caddy v1"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"rewrite {\n regexp .*\n to {path} /\n}\n"})}),"\n",(0,r.jsx)(n.h3,{id:"firebase-hosting",children:"Firebase Hosting"}),"\n",(0,r.jsxs)(n.p,{children:["Create a file names ",(0,r.jsx)(n.code,{children:"firebase.json"}),", and populate it with something similar to:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'{\n "hosting": {\n "public": "dist",\n "rewrites": [\n {\n "source": "**",\n "destination": "/index.html"\n }\n ]\n }\n}\n'})}),"\n",(0,r.jsx)(n.h3,{id:"cpanel",children:"cPanel"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Login to your WHM"}),"\n",(0,r.jsx)(n.li,{children:"Open 'Feature Manager' on the left sidebar"}),"\n",(0,r.jsx)(n.li,{children:"Under 'Manage feature list', click 'Edit'"}),"\n",(0,r.jsx)(n.li,{children:"Find 'Application manager' in the list, enable it and hit 'Save'"}),"\n",(0,r.jsx)(n.li,{children:"Log into your users cPanel account, and under 'Software' find 'Application Manager'"}),"\n",(0,r.jsx)(n.li,{children:"Click 'Register Application', fill in the form using the path that Dashy is located, and choose a domain, and hit 'Save'"}),"\n",(0,r.jsx)(n.li,{children:"The application should now show up in the list, click 'Ensure dependencies', and move the toggle switch to 'Enabled'"}),"\n",(0,r.jsx)(n.li,{children:"If you need to change the port, click 'Add environmental variable', give it the name 'PORT', choose a port number and press 'Save'."}),"\n",(0,r.jsx)(n.li,{children:"Dashy should now be running at your selected path an on a given port"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"running-a-modified-version-of-the-app",children:"Running a Modified Version of the App"}),"\n",(0,r.jsx)(n.p,{children:"If you'd like to make any code changes to the app, and deploy your modified version, this section briefly explains how."}),"\n",(0,r.jsxs)(n.p,{children:["The first step is to fork the project on GitHub, and clone it to your local system. Next, install the dependencies (",(0,r.jsx)(n.code,{children:"yarn"}),"), and start the development server (",(0,r.jsx)(n.code,{children:"yarn dev"}),") and visit ",(0,r.jsx)(n.code,{children:"localhost:8080"})," in your browser. You can then make changes to the codebase, and see the live app update in real-time. Once you've finished, running ",(0,r.jsx)(n.code,{children:"yarn build"})," will build the app for production, and output the assets into ",(0,r.jsx)(n.code,{children:"./dist"})," which can then be deployed using a web server, CDN or the built-in Node server with ",(0,r.jsx)(n.code,{children:"yarn start"}),". For more info on all of this, take a look at the ",(0,r.jsx)(n.a,{href:"/docs/developing",children:"Developing Docs"}),". To build your own Docker container from the modified app, see ",(0,r.jsx)(n.a,{href:"#building-your-own-container",children:"Building your Own Container"})]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"building-your-own-container",children:"Building your Own Container"}),"\n",(0,r.jsx)(n.p,{children:"Similar to above, you'll first need to fork and clone Dashy to your local system, and then install dependencies."}),"\n",(0,r.jsxs)(n.p,{children:["Then, either use Dashy's default ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/Dockerfile",children:(0,r.jsx)(n.code,{children:"Dockerfile"})})," as is, or modify it according to your needs."]}),"\n",(0,r.jsxs)(n.p,{children:["To build and deploy locally, first build the app with: ",(0,r.jsx)(n.code,{children:"docker build -t dashy ."}),", and then start the app with ",(0,r.jsx)(n.code,{children:"docker run -p 8080:8080 --name my-dashboard dashy"}),". Or modify the ",(0,r.jsx)(n.code,{children:"docker-compose.yml"})," file, replacing ",(0,r.jsx)(n.code,{children:"image: lissy93/dashy"})," with ",(0,r.jsx)(n.code,{children:"build: ."})," and run ",(0,r.jsx)(n.code,{children:"docker compose up"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Your container should now be running, and will appear in the list when you run ",(0,r.jsx)(n.code,{children:"docker container ls \u2013a"}),". If you'd like to enter the container, run ",(0,r.jsx)(n.code,{children:"docker exec -it [container-id] /bin/ash"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["You may wish to upload your image to a container registry for easier access. Note that if you choose to do this on a public registry, please name your container something other than just 'dashy', to avoid confusion with the official image.\nYou can push your build image, by running: ",(0,r.jsx)(n.code,{children:"docker push ghcr.io/OWNER/IMAGE_NAME:latest"}),". You will first need to authenticate, this can be done by running ",(0,r.jsx)(n.code,{children:"echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin"}),", where ",(0,r.jsx)(n.code,{children:"CR_PAT"})," is an environmental variable containing a token generated from your GitHub account. For more info, see the ",(0,r.jsx)(n.a,{href:"https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry",children:"Container Registry Docs"}),"."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,r.jsx)(n.hr,{})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>a});var i=s(96540);const r={},o=i.createContext(r);function t(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/2140bf91.05609c7f.js b/assets/js/2140bf91.07f53cf1.js similarity index 99% rename from assets/js/2140bf91.05609c7f.js rename to assets/js/2140bf91.07f53cf1.js index 246eecbf..89ea7005 100644 --- a/assets/js/2140bf91.05609c7f.js +++ b/assets/js/2140bf91.07f53cf1.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[146],{69327(e,n,s){s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>a,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"pages-and-sections","title":"Pages and Sections","description":"Page Metadata","source":"@site/docs/pages-and-sections.md","sourceDirName":".","slug":"/pages-and-sections","permalink":"/docs/pages-and-sections","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/pages-and-sections.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Cloud Backup and Restore","permalink":"/docs/backup-restore"},"next":{"title":"*Dashy Showcase* \ud83c\udf1f","permalink":"/docs/showcase"}}');var t=s(74848),o=s(28453);const a={},r="Pages and Sections",l={},c=[{value:"Page Metadata",id:"page-metadata",level:2},{value:"Multi-Page Support",id:"multi-page-support",level:2},{value:"Using Local Sub-Pages",id:"using-local-sub-pages",level:3},{value:"Using Remote Sub-Pages",id:"using-remote-sub-pages",level:3},{value:"Restrictions",id:"restrictions",level:3},{value:"URL Structure",id:"url-structure",level:3},{value:"Layout",id:"layout",level:2},{value:"Making a section wider or taller",id:"making-a-section-wider-or-taller",level:3},{value:"Items inside a section",id:"items-inside-a-section",level:3},{value:"Sub-Items",id:"sub-items",level:3}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"pages-and-sections",children:"Pages and Sections"})}),"\n",(0,t.jsx)(n.h2,{id:"page-metadata",children:"Page Metadata"}),"\n",(0,t.jsxs)(n.p,{children:["Set your dashboard's branding under ",(0,t.jsx)(n.code,{children:"pageInfo"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"pageInfo:\n title: My Dashboard # Used for main h1 title, and browser tab text\n description: Home server links\n logo: /web-icons/my-logo.png # path/URL to optional logo to display next to title\n favicon: 'https://example.com/path/to/icon' # path/URL to a favicon (shows in browser tab)\n color: '#2a7cf0' # Hex color, to set the browser/address bar color on mobile (supported browsers only)\n footer: '\xa9 2026 Me' # Optional text or HTML content, to display in the pages footer\n"})}),"\n",(0,t.jsx)(n.p,{children:"If you have multiple configs/pages, then these values swap automatically as you navigate between sub-pages."}),"\n",(0,t.jsxs)(n.p,{children:["The only caveat being, if you install Dashy as a PWA, the installed app's name, icon, and splash-screen colour come from the bundled ",(0,t.jsx)(n.code,{children:"manifest.webmanifest"})," (baked at build time) rather than ",(0,t.jsx)(n.code,{children:"pageInfo"}),". Runtime values only apply when browsing in a regular tab or browser."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"multi-page-support",children:"Multi-Page Support"}),"\n",(0,t.jsx)(n.p,{children:"You can have additional pages within your dashboard, with each having it's own config file. The config files for sub-pages can either be stored locally, or hosted separately. A link to each additional page will be displayed in the navigation bar."}),"\n",(0,t.jsx)(n.p,{children:"You can edit additional pages using the interactive editor, exactly the same was as your primary page (so long as it's local). But please save changes to one page, before you start editing the next."}),"\n",(0,t.jsx)(n.h3,{id:"using-local-sub-pages",children:"Using Local Sub-Pages"}),"\n",(0,t.jsxs)(n.p,{children:["To get started, create a new ",(0,t.jsx)(n.code,{children:".yml"})," config file for your sub-page, placing it within ",(0,t.jsx)(n.code,{children:"/app/user-data"}),". Then within your primary ",(0,t.jsx)(n.code,{children:"conf.yml"}),", choose a name, and specify the path to the new file."]}),"\n",(0,t.jsx)(n.p,{children:"This is an example. Make sure to add this to the topmost line above appConfig:, or anywhere else appropriately, to match the yml syntax."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"pages:\n- name: Networking Services\n path: 'networking.yml'\n- name: Work Stuff\n path: 'work.yml'\n"})}),"\n",(0,t.jsxs)(n.p,{children:["The next step is to create the new page, if you mounted ",(0,t.jsx)(n.code,{children:"/app/user-data"})," in the docker command and not a volume, you can simply create the new page into that folder on the host."]}),"\n",(0,t.jsxs)(n.p,{children:["If you mounted ",(0,t.jsx)(n.code,{children:"/app/user-data/conf.yml"})," in docker, you can either switch to the volume or create another bind mount to your new additional page."]}),"\n",(0,t.jsxs)(n.p,{children:["If you're sub-page is located within ",(0,t.jsx)(n.code,{children:"/app/user-data"}),", then you only need to specify the filename, but if it's anywhere else, then the full path is required."]}),"\n",(0,t.jsxs)(n.p,{children:["A default template a page can be found here: ",(0,t.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/master/user-data/conf.yml",children:"https://github.com/lissy93/dashy/blob/master/user-data/conf.yml"})," Keep in mind the appConfig cannot be used on subpages and should be removed, for further info see ",(0,t.jsx)(n.a,{href:"#restrictions",children:"Restrictions"})]}),"\n",(0,t.jsx)(n.p,{children:"Now if you reload the page, on the top right there should be a new button to navigate to the new page. \ud83c\udf89"}),"\n",(0,t.jsx)(n.h3,{id:"using-remote-sub-pages",children:"Using Remote Sub-Pages"}),"\n",(0,t.jsx)(n.p,{children:"Config files don't need to be local, you can store them anywhere, and data will be imported as sub-pages on page load."}),"\n",(0,t.jsx)(n.p,{children:"For example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"pages:\n- name: Getting Started\n path: 'https://snippet.host/tvcw/raw'\n- name: Homelab\n path: 'https://snippet.host/tetp/raw'\n- name: Browser Startpage\n path: 'https://snippet.host/zcom/raw'\n"})}),"\n",(0,t.jsxs)(n.p,{children:["There are many options of how this can be used. You could store your config within a Git repository, in order to easily track and rollback changes. Or host your config on your NAS, to have it backed up with the rest of your files. Or use a hosted paste service, for example ",(0,t.jsx)(n.a,{href:"https://snippet.host/",children:"snippet.host"}),", which supports never-expiring CORS-enabled pastes, which can also be edited later."]}),"\n",(0,t.jsx)(n.p,{children:"You will obviously not be able to write updates to remote configs directly through the UI editor, but you can still make and preview changes, then use the export menu to get a copy of the new config, which can then be pasted to the remote source manually.\nThe config file must, of course be accessible from within Dashy. If your config contains sensitive info (like API keys, credentials, secret URLs, etc), take care not to expose it to the internet."}),"\n",(0,t.jsxs)(n.p,{children:["The following example shows creating a config, publishing it as a ",(0,t.jsx)(n.a,{href:"https://gist.github.com/",children:"Gist"}),", copying the URL to the raw file, and using it within your dashboard."]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{width:"700",alt:"Public config in a gist demo",src:"https://i.ibb.co/55jm3LG/how-to-use-remote-config-sub-page.gif"})}),"\n",(0,t.jsx)(n.h3,{id:"restrictions",children:"Restrictions"}),"\n",(0,t.jsxs)(n.p,{children:["Only top-level fields supported by sub-pages are ",(0,t.jsx)(n.code,{children:"pageInfo"})," and ",(0,t.jsx)(n.code,{children:"sections"}),". The ",(0,t.jsx)(n.code,{children:"appConfig"})," and ",(0,t.jsx)(n.code,{children:"pages"})," will always be inherited from your main ",(0,t.jsx)(n.code,{children:"conf.yml"})," file. Other than that, sub-pages behave exactly the same as your default view, and can contain sections, items, widgets and page info like nav links, title and logo."]}),"\n",(0,t.jsx)(n.h3,{id:"url-structure",children:"URL Structure"}),"\n",(0,t.jsx)(n.p,{children:"Every view in Dashy shares the same URL shape, so any config can be reached from any view:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"/ Landing \u2014 whichever view you set as the default\n/ Root config in \n// Sub-config in \n///
Single section of in \n//main/
Single section of the root config (main is a reserved page id)\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:""})," is one of ",(0,t.jsx)(n.code,{children:"home"}),", ",(0,t.jsx)(n.code,{children:"minimal"}),", or ",(0,t.jsx)(n.code,{children:"workspace"}),". ",(0,t.jsx)(n.code,{children:""})," is the sub-config id \u2014 the sub-page's ",(0,t.jsx)(n.code,{children:"name"})," (from the ",(0,t.jsx)(n.code,{children:"pages"})," array) converted to lowercase-and-dashes (emoji and other non-word characters stripped). ",(0,t.jsx)(n.code,{children:"
"})," follows the same slugging rules. Workspace has no single-section URL; it uses its sidebar instead."]}),"\n",(0,t.jsx)(n.p,{children:"Examples:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'/home Home view, main config\n/home/homelab Home view, "Homelab" sub-config\n/home/homelab/media Home view, "Homelab" sub-config, "Media" section only\n/home/main/getting-started Home view, main config, "Getting Started" section only\n/minimal/homelab Minimal view, "Homelab" sub-config\n/minimal/homelab/media Minimal view, "Homelab" sub-config, "Media" section pre-selected\n/workspace/homelab Workspace view, "Homelab" sub-config\n'})}),"\n",(0,t.jsx)(n.p,{children:'The view switcher, sub-page nav links, and section deep-links all preserve your current view and sub-page \u2014 so clicking through a single-section view and then hitting "back to all" returns you to the same sub-page you came from.'}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"layout",children:"Layout"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"appConfig.layout"})," controls how your sections sit on the page:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"auto"})," (default): responsive CSS grid, sections size by ",(0,t.jsx)(n.code,{children:"displayData.cols"}),"/",(0,t.jsx)(n.code,{children:"rows"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"horizontal"}),": sections stacked top to bottom, each full width"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"vertical"}),": sections side by side in columns"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"masonry"}),": responsive grid where heights follow content so shorter sections fill gaps under taller ones (",(0,t.jsx)(n.code,{children:"rows"})," is ignored)"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["You can also switch between these from the settings menu. Add ",(0,t.jsx)(n.code,{children:"appConfig.colCount"})," to force a specific number of columns."]}),"\n",(0,t.jsx)(n.h3,{id:"making-a-section-wider-or-taller",children:"Making a section wider or taller"}),"\n",(0,t.jsxs)(n.p,{children:["Use ",(0,t.jsx)(n.code,{children:"displayData.cols"})," (1 to 5) and ",(0,t.jsx)(n.code,{children:"displayData.rows"})," (1 to 5) to control a section's footprint:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"- name: Important Links\n displayData:\n cols: 2\n rows: 2\n collapsed: false\n items: [...]\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"rows"})," is honoured by the ",(0,t.jsx)(n.code,{children:"auto"})," layout. Under ",(0,t.jsx)(n.code,{children:"masonry"}),", section heights always follow content."]}),"\n",(0,t.jsx)(n.h3,{id:"items-inside-a-section",children:"Items inside a section"}),"\n",(0,t.jsxs)(n.p,{children:["Items wrap responsively by default. The useful knobs on ",(0,t.jsx)(n.code,{children:"displayData"}),":"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"itemSize"}),": ",(0,t.jsx)(n.code,{children:"small"}),", ",(0,t.jsx)(n.code,{children:"medium"})," (default), or ",(0,t.jsx)(n.code,{children:"large"})," (large tiles also show a description)"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"sortBy"}),": ",(0,t.jsx)(n.code,{children:"alphabetical"}),", ",(0,t.jsx)(n.code,{children:"reverse-alphabetical"}),", ",(0,t.jsx)(n.code,{children:"most-used"}),", ",(0,t.jsx)(n.code,{children:"last-used"}),", ",(0,t.jsx)(n.code,{children:"random"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"sectionLayout: grid"})," plus ",(0,t.jsx)(n.code,{children:"itemCountX"})," and/or ",(0,t.jsx)(n.code,{children:"itemCountY"})," if you want a fixed grid instead of auto wrapping"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["See ",(0,t.jsx)(n.a,{href:"/docs/configuring#sectiondisplaydata-optional",children:"configuring.md"})," for the full list of options."]}),"\n",(0,t.jsx)(n.h3,{id:"sub-items",children:"Sub-Items"}),"\n",(0,t.jsx)(n.p,{children:"A normal section will contain zero or more items, for example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"- name: Coding\n icon: far fa-code\n items:\n - title: GitHub\n url: https://github.com/\n - title: StackOverflow\n url: http://stackoverflow.com/\n"})}),"\n",(0,t.jsx)(n.p,{children:"But items can also be grouped together, referred to as sub-items. This is useful for a group of less frequently used items, which you don't want to take up too much space."}),"\n",(0,t.jsx)(n.p,{children:"Item groups may also have an optional title."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"- name: Coding\n icon: far fa-code\n items:\n - title: Normal Item 1\n - title: Normal Item 2\n - title: Languages\n subItems:\n - title: JavaScript\n url: https://developer.mozilla.org\n icon: si-javascript\n - title: TypeScript\n url: https://www.typescriptlang.org/docs\n icon: si-typescript\n - title: Svelte\n url: https://svelte.dev/docs\n icon: si-svelte\n - title: Go\n url: https://go.dev/doc\n icon: si-go\n"})})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>a,x:()=>r});var i=s(96540);const t={},o=i.createContext(t);function a(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[146],{69327(e,n,s){s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>a,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"pages-and-sections","title":"Pages and Sections","description":"Page Metadata","source":"@site/docs/pages-and-sections.md","sourceDirName":".","slug":"/pages-and-sections","permalink":"/docs/pages-and-sections","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/pages-and-sections.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Cloud Backup and Restore","permalink":"/docs/backup-restore"},"next":{"title":"*Dashy Showcase* \ud83c\udf1f","permalink":"/docs/showcase"}}');var t=s(74848),o=s(28453);const a={},r="Pages and Sections",l={},c=[{value:"Page Metadata",id:"page-metadata",level:2},{value:"Multi-Page Support",id:"multi-page-support",level:2},{value:"Using Local Sub-Pages",id:"using-local-sub-pages",level:3},{value:"Using Remote Sub-Pages",id:"using-remote-sub-pages",level:3},{value:"Restrictions",id:"restrictions",level:3},{value:"URL Structure",id:"url-structure",level:3},{value:"Layout",id:"layout",level:2},{value:"Making a section wider or taller",id:"making-a-section-wider-or-taller",level:3},{value:"Items inside a section",id:"items-inside-a-section",level:3},{value:"Sub-Items",id:"sub-items",level:3}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"pages-and-sections",children:"Pages and Sections"})}),"\n",(0,t.jsx)(n.h2,{id:"page-metadata",children:"Page Metadata"}),"\n",(0,t.jsxs)(n.p,{children:["Set your dashboard's branding under ",(0,t.jsx)(n.code,{children:"pageInfo"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"pageInfo:\n title: My Dashboard # Used for main h1 title, and browser tab text\n description: Home server links\n logo: /web-icons/my-logo.png # path/URL to optional logo to display next to title\n favicon: 'https://example.com/path/to/icon' # path/URL to a favicon (shows in browser tab)\n color: '#2a7cf0' # Hex color, to set the browser/address bar color on mobile (supported browsers only)\n footer: '\xa9 2026 Me' # Optional text or HTML content, to display in the pages footer\n"})}),"\n",(0,t.jsx)(n.p,{children:"If you have multiple configs/pages, then these values swap automatically as you navigate between sub-pages."}),"\n",(0,t.jsxs)(n.p,{children:["The only caveat being, if you install Dashy as a PWA, the installed app's name, icon, and splash-screen colour come from the bundled ",(0,t.jsx)(n.code,{children:"manifest.webmanifest"})," (baked at build time) rather than ",(0,t.jsx)(n.code,{children:"pageInfo"}),". Runtime values only apply when browsing in a regular tab or browser."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"multi-page-support",children:"Multi-Page Support"}),"\n",(0,t.jsx)(n.p,{children:"You can have additional pages within your dashboard, with each having it's own config file. The config files for sub-pages can either be stored locally, or hosted separately. A link to each additional page will be displayed in the navigation bar."}),"\n",(0,t.jsx)(n.p,{children:"You can edit additional pages using the interactive editor, exactly the same was as your primary page (so long as it's local). But please save changes to one page, before you start editing the next."}),"\n",(0,t.jsx)(n.h3,{id:"using-local-sub-pages",children:"Using Local Sub-Pages"}),"\n",(0,t.jsxs)(n.p,{children:["To get started, create a new ",(0,t.jsx)(n.code,{children:".yml"})," config file for your sub-page, placing it within ",(0,t.jsx)(n.code,{children:"/app/user-data"}),". Then within your primary ",(0,t.jsx)(n.code,{children:"conf.yml"}),", choose a name, and specify the path to the new file."]}),"\n",(0,t.jsx)(n.p,{children:"This is an example. Make sure to add this to the topmost line above appConfig:, or anywhere else appropriately, to match the yml syntax."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"pages:\n- name: Networking Services\n path: 'networking.yml'\n- name: Work Stuff\n path: 'work.yml'\n"})}),"\n",(0,t.jsxs)(n.p,{children:["The next step is to create the new page, if you mounted ",(0,t.jsx)(n.code,{children:"/app/user-data"})," in the docker command and not a volume, you can simply create the new page into that folder on the host."]}),"\n",(0,t.jsxs)(n.p,{children:["If you mounted ",(0,t.jsx)(n.code,{children:"/app/user-data/conf.yml"})," in docker, you can either switch to the volume or create another bind mount to your new additional page."]}),"\n",(0,t.jsxs)(n.p,{children:["If you're sub-page is located within ",(0,t.jsx)(n.code,{children:"/app/user-data"}),", then you only need to specify the filename, but if it's anywhere else, then the full path is required."]}),"\n",(0,t.jsxs)(n.p,{children:["A default template a page can be found here: ",(0,t.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/master/user-data/conf.yml",children:"https://github.com/lissy93/dashy/blob/master/user-data/conf.yml"})," Keep in mind the appConfig cannot be used on subpages and should be removed, for further info see ",(0,t.jsx)(n.a,{href:"#restrictions",children:"Restrictions"})]}),"\n",(0,t.jsx)(n.p,{children:"Now if you reload the page, on the top right there should be a new button to navigate to the new page. \ud83c\udf89"}),"\n",(0,t.jsx)(n.h3,{id:"using-remote-sub-pages",children:"Using Remote Sub-Pages"}),"\n",(0,t.jsx)(n.p,{children:"Config files don't need to be local, you can store them anywhere, and data will be imported as sub-pages on page load."}),"\n",(0,t.jsx)(n.p,{children:"For example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"pages:\n- name: Getting Started\n path: 'https://snippet.host/tvcw/raw'\n- name: Homelab\n path: 'https://snippet.host/tetp/raw'\n- name: Browser Startpage\n path: 'https://snippet.host/zcom/raw'\n"})}),"\n",(0,t.jsxs)(n.p,{children:["There are many options of how this can be used. You could store your config within a Git repository, in order to easily track and rollback changes. Or host your config on your NAS, to have it backed up with the rest of your files. Or use a hosted paste service, for example ",(0,t.jsx)(n.a,{href:"https://snippet.host/",children:"snippet.host"}),", which supports never-expiring CORS-enabled pastes, which can also be edited later."]}),"\n",(0,t.jsx)(n.p,{children:"You will obviously not be able to write updates to remote configs directly through the UI editor, but you can still make and preview changes, then use the export menu to get a copy of the new config, which can then be pasted to the remote source manually.\nThe config file must, of course be accessible from within Dashy. If your config contains sensitive info (like API keys, credentials, secret URLs, etc), take care not to expose it to the internet."}),"\n",(0,t.jsxs)(n.p,{children:["The following example shows creating a config, publishing it as a ",(0,t.jsx)(n.a,{href:"https://gist.github.com/",children:"Gist"}),", copying the URL to the raw file, and using it within your dashboard."]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{width:"700",alt:"Public config in a gist demo",src:"https://i.ibb.co/55jm3LG/how-to-use-remote-config-sub-page.gif"})}),"\n",(0,t.jsx)(n.h3,{id:"restrictions",children:"Restrictions"}),"\n",(0,t.jsxs)(n.p,{children:["Only top-level fields supported by sub-pages are ",(0,t.jsx)(n.code,{children:"pageInfo"})," and ",(0,t.jsx)(n.code,{children:"sections"}),". The ",(0,t.jsx)(n.code,{children:"appConfig"})," and ",(0,t.jsx)(n.code,{children:"pages"})," will always be inherited from your main ",(0,t.jsx)(n.code,{children:"conf.yml"})," file. Other than that, sub-pages behave exactly the same as your default view, and can contain sections, items, widgets and page info like nav links, title and logo."]}),"\n",(0,t.jsx)(n.h3,{id:"url-structure",children:"URL Structure"}),"\n",(0,t.jsx)(n.p,{children:"Every view in Dashy shares the same URL shape, so any config can be reached from any view:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"/ Landing \u2014 whichever view you set as the default\n/ Root config in \n// Sub-config in \n///
Single section of in \n//main/
Single section of the root config (main is a reserved page id)\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:""})," is one of ",(0,t.jsx)(n.code,{children:"home"}),", ",(0,t.jsx)(n.code,{children:"minimal"}),", or ",(0,t.jsx)(n.code,{children:"workspace"}),". ",(0,t.jsx)(n.code,{children:""})," is the sub-config id \u2014 the sub-page's ",(0,t.jsx)(n.code,{children:"name"})," (from the ",(0,t.jsx)(n.code,{children:"pages"})," array) converted to lowercase-and-dashes (emoji and other non-word characters stripped). ",(0,t.jsx)(n.code,{children:"
"})," follows the same slugging rules. Workspace has no single-section URL; it uses its sidebar instead."]}),"\n",(0,t.jsx)(n.p,{children:"Examples:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'/home Home view, main config\n/home/homelab Home view, "Homelab" sub-config\n/home/homelab/media Home view, "Homelab" sub-config, "Media" section only\n/home/main/getting-started Home view, main config, "Getting Started" section only\n/minimal/homelab Minimal view, "Homelab" sub-config\n/minimal/homelab/media Minimal view, "Homelab" sub-config, "Media" section pre-selected\n/workspace/homelab Workspace view, "Homelab" sub-config\n'})}),"\n",(0,t.jsx)(n.p,{children:'The view switcher, sub-page nav links, and section deep-links all preserve your current view and sub-page \u2014 so clicking through a single-section view and then hitting "back to all" returns you to the same sub-page you came from.'}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"layout",children:"Layout"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"appConfig.layout"})," controls how your sections sit on the page:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"auto"})," (default): responsive CSS grid, sections size by ",(0,t.jsx)(n.code,{children:"displayData.cols"}),"/",(0,t.jsx)(n.code,{children:"rows"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"horizontal"}),": sections stacked top to bottom, each full width"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"vertical"}),": sections side by side in columns"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"masonry"}),": responsive grid where heights follow content so shorter sections fill gaps under taller ones (",(0,t.jsx)(n.code,{children:"rows"})," is ignored)"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["You can also switch between these from the settings menu. Add ",(0,t.jsx)(n.code,{children:"appConfig.colCount"})," to force a specific number of columns."]}),"\n",(0,t.jsx)(n.h3,{id:"making-a-section-wider-or-taller",children:"Making a section wider or taller"}),"\n",(0,t.jsxs)(n.p,{children:["Use ",(0,t.jsx)(n.code,{children:"displayData.cols"})," (1 to 5) and ",(0,t.jsx)(n.code,{children:"displayData.rows"})," (1 to 5) to control a section's footprint:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"- name: Important Links\n displayData:\n cols: 2\n rows: 2\n collapsed: false\n items: [...]\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"rows"})," is honoured by the ",(0,t.jsx)(n.code,{children:"auto"})," layout. Under ",(0,t.jsx)(n.code,{children:"masonry"}),", section heights always follow content."]}),"\n",(0,t.jsx)(n.h3,{id:"items-inside-a-section",children:"Items inside a section"}),"\n",(0,t.jsxs)(n.p,{children:["Items wrap responsively by default. The useful knobs on ",(0,t.jsx)(n.code,{children:"displayData"}),":"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"itemSize"}),": ",(0,t.jsx)(n.code,{children:"small"}),", ",(0,t.jsx)(n.code,{children:"medium"})," (default), or ",(0,t.jsx)(n.code,{children:"large"})," (large tiles also show a description)"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"sortBy"}),": ",(0,t.jsx)(n.code,{children:"alphabetical"}),", ",(0,t.jsx)(n.code,{children:"reverse-alphabetical"}),", ",(0,t.jsx)(n.code,{children:"most-used"}),", ",(0,t.jsx)(n.code,{children:"last-used"}),", ",(0,t.jsx)(n.code,{children:"random"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"sectionLayout: grid"})," plus ",(0,t.jsx)(n.code,{children:"itemCountX"})," and/or ",(0,t.jsx)(n.code,{children:"itemCountY"})," if you want a fixed grid instead of auto wrapping"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["See ",(0,t.jsx)(n.a,{href:"/docs/configuring#sectiondisplaydata-optional",children:"configuring.md"})," for the full list of options."]}),"\n",(0,t.jsx)(n.h3,{id:"sub-items",children:"Sub-Items"}),"\n",(0,t.jsx)(n.p,{children:"A normal section will contain zero or more items, for example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"- name: Coding\n icon: far fa-code\n items:\n - title: GitHub\n url: https://github.com/\n - title: StackOverflow\n url: http://stackoverflow.com/\n"})}),"\n",(0,t.jsx)(n.p,{children:"But items can also be grouped together, referred to as sub-items. This is useful for a group of less frequently used items, which you don't want to take up too much space."}),"\n",(0,t.jsx)(n.p,{children:"Item groups may also have an optional title."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"- name: Coding\n icon: far fa-code\n items:\n - title: Normal Item 1\n - title: Normal Item 2\n - title: Languages\n subItems:\n - title: JavaScript\n url: https://developer.mozilla.org\n icon: si-javascript\n - title: TypeScript\n url: https://www.typescriptlang.org/docs\n icon: si-typescript\n - title: Svelte\n url: https://svelte.dev/docs\n icon: si-svelte\n - title: Go\n url: https://go.dev/doc\n icon: si-go\n"})})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>a,x:()=>r});var i=s(96540);const t={},o=i.createContext(t);function a(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/27d9d47d.afa54dc8.js b/assets/js/27d9d47d.2076536d.js similarity index 99% rename from assets/js/27d9d47d.afa54dc8.js rename to assets/js/27d9d47d.2076536d.js index 8fb752c7..51ccfd7c 100644 --- a/assets/js/27d9d47d.afa54dc8.js +++ b/assets/js/27d9d47d.2076536d.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[9757],{37177(s,e,n){n.r(e),n.d(e,{assets:()=>o,contentTitle:()=>a,default:()=>l,frontMatter:()=>i,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"showcase","title":"*Dashy Showcase* \ud83c\udf1f","description":"| \ud83d\udc97 Got a sweet dashboard? Submit it to the showcase! \ud83d\udc49 See How |","source":"@site/docs/showcase.md","sourceDirName":".","slug":"/showcase","permalink":"/docs/showcase","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/showcase.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Pages and Sections","permalink":"/docs/pages-and-sections"},"next":{"title":"Contributing","permalink":"/docs/contributing"}}');var h=n(74848),t=n(28453);const i={},a="Dashy Showcase \ud83c\udf1f",o={},d=[{value:"MNDashboard",id:"mndashboard",level:2},{value:"Home Lab 2.0",id:"home-lab-20",level:2},{value:"Dipan's Dash",id:"dipans-dash",level:2},{value:"Ratty222",id:"ratty222",level:2},{value:"Hugalafutro Dashy",id:"hugalafutro-dashy",level:2},{value:"NAS Home Dashboard",id:"nas-home-dashboard",level:2},{value:"Brewhack",id:"brewhack",level:2},{value:"The Dragons Lair",id:"the-dragons-lair",level:2},{value:"Homelab & VPS dashboard",id:"homelab--vps-dashboard",level:2},{value:"Raspberry PI Docker Dashboard",id:"raspberry-pi-docker-dashboard",level:2},{value:"First Week of Self-Hosting",id:"first-week-of-self-hosting",level:2},{value:"EVO Dashboard",id:"evo-dashboard",level:2},{value:"The Private Dashboard",id:"the-private-dashboard",level:2},{value:"Networking Services",id:"networking-services",level:2},{value:"Dashy Live",id:"dashy-live",level:2},{value:"System Monitor",id:"system-monitor",level:2},{value:"Browser Startpage",id:"browser-startpage",level:2},{value:"CFT Toolbox",id:"cft-toolbox",level:2},{value:"Bookmarks",id:"bookmarks",level:2},{value:"Project Management",id:"project-management",level:2},{value:"Dashy Example",id:"dashy-example",level:2},{value:"HomeLAb 3.0",id:"homelab-30",level:2},{value:"Ground Control",id:"ground-control",level:2},{value:"Croco_Grievous",id:"croco_grievous",level:2},{value:"Crypto Dash",id:"crypto-dash",level:2},{value:"Stefantigro",id:"stefantigro",level:2},{value:"HomeLab 3.0",id:"homelab-30-1",level:2},{value:"Morning Dashboard",id:"morning-dashboard",level:2},{value:"Yet Another Homelab",id:"yet-another-homelab",level:2},{value:"Submitting your Dashboard",id:"submitting-your-dashboard",level:2},{value:"How to Submit",id:"how-to-submit",level:3},{value:"What to Include",id:"what-to-include",level:3},{value:"Template",id:"template",level:3}];function c(s){const e={a:"a",blockquote:"blockquote",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",sup:"sup",table:"table",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...s.components};return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(e.header,{children:(0,h.jsxs)(e.h1,{id:"dashy-showcase-",children:[(0,h.jsx)(e.em,{children:"Dashy Showcase"})," \ud83c\udf1f"]})}),"\n\n\n\n\n\n\n",(0,h.jsx)(e.table,{children:(0,h.jsx)(e.thead,{children:(0,h.jsx)(e.tr,{children:(0,h.jsxs)(e.th,{children:["\ud83d\udc97 Got a sweet dashboard? Submit it to the showcase! \ud83d\udc49 ",(0,h.jsx)(e.a,{href:"#submitting-your-dashboard",children:"See How"})]})})})}),"\n",(0,h.jsx)(e.h2,{id:"mndashboard",children:"MNDashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/mahrnet",children:"@mahrnet"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/1491",children:"#1491"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/mCJRZgp/d2-At-QO4c-PT4u.png",alt:"screenshot-MNDashboard"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"home-lab-20",children:"Home Lab 2.0"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/1-home-lab-material.png",alt:"screenshot-homelab"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"dipans-dash",children:"Dipan's Dash"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/dipanghosh",children:"@Dipanghosh"})," - ",(0,h.jsx)(e.a,{href:"https://dipan.de/",children:"dipan.de"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/discussions/777",children:"#777"})]})]}),"\n"]}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["With the option of embedding iframes, it becomes possible to combine and conquer. I have set up Dashy to include graphs from grafana, and set them so that they update automatically. I also have uptime monitors from uptime Kuma, panels from homeassistant are also possible to be included.",(0,h.jsx)(e.br,{}),"\nDashy is awesome!"]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/bPC6w2n/dipanghosh-dashboard.png",alt:"screenshot-dipanghosh-dashboard"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"ratty222",children:"Ratty222"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/ratty222",children:"@ratty222"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/discussions/384",children:"#384"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://user-images.githubusercontent.com/1862727/147582551-4c655d37-8bcc-4f95-ab41-164a9d0d6a07.png",alt:"screenshot-ratty222-dashy"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"hugalafutro-dashy",children:"Hugalafutro Dashy"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/hugalafutro",children:"@hugalafutro"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/discussions/505",children:"#505"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.a,{href:"https://i.ibb.co/PDpLDKS/hugalafutro-dashy.gif",children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/PDpLDKS/hugalafutro-dashy.gif",alt:"hugalafutro-dashy-screenshot"})})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"nas-home-dashboard",children:"NAS Home Dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/cerealconyogurt",children:"@cerealconyogurt"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/74",children:"#74"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/6-nas-home-dashboard.png",alt:"screenshot-networking-services"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"brewhack",children:"Brewhack"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/brpeterso",children:"@brpeterso"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/680",children:"#680"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/cNjzPT4/brewhack.png",alt:"screenshot-brewhack-dashboard"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"the-dragons-lair",children:"The Dragons Lair"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/dr460nf1r3",children:"dr460nf1r3"})," ",(0,h.jsxs)(e.sup,{children:["via ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/684",children:"#684"})]})]}),"\n"]}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsx)(e.p,{children:"My new startpage featuring the stuff I use most. And of course dragons!"}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/13-dragons-lair.png",alt:"screenshot-dragons-lair"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"homelab--vps-dashboard",children:"Homelab & VPS dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/shadowking001",children:"@shadowking001"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/86",children:"#86"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/8-shadowking001s-dashy.png",alt:"screenshot-shadowking001-dashy"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"raspberry-pi-docker-dashboard",children:"Raspberry PI Docker Dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/henkiewie",children:"@henkiewie"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/622",children:"#622"})]})]}),"\n"]}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["I use this dashboard every day. It now even includes a player for a radio stream which I configured with Logitech media server and icecast. I made an smaller version of the grafana dashboard to fit an iframe in kiosk mode, so it monitors the most important values of my RPI. The PI is in Argon m2 case and used as a NAS. The dashboard is a copy of the adventure theme with some changes saved in ",(0,h.jsx)(e.code,{children:"/app/src/styles/user-defined-themes.scss"})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/jGzPm6b/henkiewie-dashy-showcase.png",alt:"screenshot-henkiewie-dashy"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"first-week-of-self-hosting",children:"First Week of Self-Hosting"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/user/RickyCZ",children:"u//RickyCZ"})," ",(0,h.jsxs)(e.sup,{children:["via ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/r/selfhosted/comments/pose15/just_got_started_a_week_ago_selfhosting_is_very/",children:"Reddit"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/11-ricky-cz.png",alt:"screenshot-week-of-self-hosting"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"evo-dashboard",children:"EVO Dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/EVOTk",children:"@EVOTk"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/pull/316",children:"#316"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/12-evo-dashboard.png",alt:"screenshot-evo-dashboard"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"the-private-dashboard",children:"The Private Dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/DylanBeMe",children:"@DylanBeMe"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/419",children:"#419"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/hKS483T/private-dashboard-Dylan-Be-Me.png",alt:"screenshot-private-dashboard"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"networking-services",children:"Networking Services"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/lissy93",children:"@Lissy93"})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/2-networking-services-minimal-dark.png",alt:"screenshot-networking-services"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"dashy-live",children:"Dashy Live"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/lissy93",children:"@Lissy93"})]}),"\n"]}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["A dashboard I made to manage all project development links from one place. View demo at ",(0,h.jsx)(e.a,{href:"https://live.dashy.to/",children:"live.dashy.to"}),"."]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/10-dashy-live.png",alt:"screenshot-dashy-live"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"system-monitor",children:"System Monitor"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsx)(e.p,{children:"An aggregated board for monitoring system resource usage from a single view"}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/xfK6BGb/system-monitor-board.png",alt:"screenshot-monitor"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"browser-startpage",children:"Browser Startpage"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/rs07dS1/startpage.png",alt:"screenshot-startpage"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"cft-toolbox",children:"CFT Toolbox"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/3-cft-toolbox.png",alt:"screenshot-cft-toolbox"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"bookmarks",children:"Bookmarks"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/4-bookmarks-colourful.png",alt:"screenshot-bookmarks"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"project-management",children:"Project Management"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/5-project-management.png",alt:"screenshot-project-management"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"dashy-example",children:"Dashy Example"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["An example dashboard, by ",(0,h.jsx)(e.a,{href:"https://github.com/lissy93",children:"@Lissy93"}),". View live at ",(0,h.jsx)(e.a,{href:"https://demo.dashy.to/",children:"demo.dashy.to"}),"."]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/YbzqPK7/demo-dashy.png",alt:"screenshot-dashy-example"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"homelab-30",children:"HomeLAb 3.0"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/skoogee",children:"@skoogee"})," (",(0,h.jsx)(e.a,{href:"http://zhrn.cc",children:"http://zhrn.cc"}),") ",(0,h.jsx)(e.sup,{children:(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/279",children:"#279"})})]}),"\n"]}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsx)(e.p,{children:"Dashy, is the most complete dashboard I ever tried, has all the features, and it sets itself apart from the rest. It is my default homepage now. I am thankful to the developer @Lissy93 for sharing such a wonderful creation."}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.a,{href:"https://ibb.co/album/ynSwzm",children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/F5yBTsT/12-skoogee-homelab-3.png?",alt:"screenshot-12-skoogee-homelab-3"})})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"ground-control",children:"Ground Control"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/dtctek",children:"@dtctek"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/83",children:"#83"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://user-images.githubusercontent.com/1862727/149821995-e9b41dab-186c-42e6-b5b3-e233259b241d.png",alt:"screenshot-ground-control"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"croco_grievous",children:"Croco_Grievous"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/user/Croco_Grievous/",children:"u/Croco_Grievous"})," ",(0,h.jsxs)(e.sup,{children:["via ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/r/selfhosted/comments/t4xk3z/everything_started_with_pihole_on_a_raspberry_pi/",children:"reddit"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/59XR8KL/dashy-Croco-Grievous.png",alt:"screenshot-croco-grievous-dashy"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"crypto-dash",children:"Crypto Dash"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["Example usage of widgets to monitor cryptocurrencies news, prices and data. Config is ",(0,h.jsx)(e.a,{href:"https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10#file-example-8-dashy-crypto-widgets-conf-yml",children:"available here"})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://user-images.githubusercontent.com/1862727/147394584-352fe3bf-740d-4624-a01b-9003a97bc832.png",alt:"screenshot-crypto-dash"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"stefantigro",children:"Stefantigro"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/user/stefantigro/",children:"u/stefantigro"})," ",(0,h.jsxs)(e.sup,{children:["via ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/r/selfhosted/comments/t5oril/been_selfhosting_close_to_half_a_year_now_all/",children:"reddit"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/1Kb43Yy/dashy-stefantigro.png",alt:"screenshot-stefantigro-dashy"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"homelab-30-1",children:"HomeLab 3.0"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsx)(e.p,{children:(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/1999",children:"#1999"})]})}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://pixelflare.cc/alicia/dashy/12-skoogee-homelab-3",alt:"screenshot-homelab-3"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"morning-dashboard",children:"Morning Dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsx)(e.p,{children:"Displayed on my smart screen between 05:00 - 08:00, and includes all the info that I usually check before leaving for work"}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/4Wx8zb7/morning-dashboard.png",alt:"screenshot-morning-dash"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"yet-another-homelab",children:"Yet Another Homelab"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/9-home-lab-oblivion.png",alt:"screenshot-yet-another-homelab"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"submitting-your-dashboard",children:"Submitting your Dashboard"}),"\n",(0,h.jsx)(e.h3,{id:"how-to-submit",children:"How to Submit"}),"\n",(0,h.jsxs)(e.ul,{children:["\n",(0,h.jsx)(e.li,{children:(0,h.jsx)(e.a,{href:"https://git.io/JEtgM",children:"Open an Issue"})}),"\n",(0,h.jsx)(e.li,{children:(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/compare",children:"Open a PR"})}),"\n"]}),"\n",(0,h.jsx)(e.h3,{id:"what-to-include",children:"What to Include"}),"\n",(0,h.jsx)(e.p,{children:"Please include the following information:"}),"\n",(0,h.jsxs)(e.ul,{children:["\n",(0,h.jsx)(e.li,{children:"A single high-quality screenshot of your Dashboard"}),"\n",(0,h.jsx)(e.li,{children:"A short title (it doesn't have to be particularly imaginative)"}),"\n",(0,h.jsx)(e.li,{children:"An optional description, you could include details on anything interesting or unique about your dashboard, or say how you use it, and why it's awesome"}),"\n",(0,h.jsx)(e.li,{children:"Optionally leave your name or username, with a link to your GitHub, Twitter or Website"}),"\n"]}),"\n",(0,h.jsx)(e.h3,{id:"template",children:"Template"}),"\n",(0,h.jsx)(e.p,{children:"If you're submitting a pull request, please use a format similar to this:"}),"\n",(0,h.jsx)(e.pre,{children:(0,h.jsx)(e.code,{className:"language-text",children:"### [Dashboard Name] (required)\n\n> Submitted by [@username](https://github.com/user) (optional)\n\n[An optional text description, or any interesting details] (optional)\n\n![dashboard-screenshot](https://example.com/url-to-screenshot.png) (required)\n\n---\n\n"})})]})}function l(s={}){const{wrapper:e}={...(0,t.R)(),...s.components};return e?(0,h.jsx)(e,{...s,children:(0,h.jsx)(c,{...s})}):c(s)}},28453(s,e,n){n.d(e,{R:()=>i,x:()=>a});var r=n(96540);const h={},t=r.createContext(h);function i(s){const e=r.useContext(t);return r.useMemo(function(){return"function"==typeof s?s(e):{...e,...s}},[e,s])}function a(s){let e;return e=s.disableParentContext?"function"==typeof s.components?s.components(h):s.components||h:i(s.components),r.createElement(t.Provider,{value:e},s.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[9757],{37177(s,e,n){n.r(e),n.d(e,{assets:()=>o,contentTitle:()=>a,default:()=>l,frontMatter:()=>i,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"showcase","title":"*Dashy Showcase* \ud83c\udf1f","description":"| \ud83d\udc97 Got a sweet dashboard? Submit it to the showcase! \ud83d\udc49 See How |","source":"@site/docs/showcase.md","sourceDirName":".","slug":"/showcase","permalink":"/docs/showcase","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/showcase.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Pages and Sections","permalink":"/docs/pages-and-sections"},"next":{"title":"Contributing","permalink":"/docs/contributing"}}');var h=n(74848),t=n(28453);const i={},a="Dashy Showcase \ud83c\udf1f",o={},d=[{value:"MNDashboard",id:"mndashboard",level:2},{value:"Home Lab 2.0",id:"home-lab-20",level:2},{value:"Dipan's Dash",id:"dipans-dash",level:2},{value:"Ratty222",id:"ratty222",level:2},{value:"Hugalafutro Dashy",id:"hugalafutro-dashy",level:2},{value:"NAS Home Dashboard",id:"nas-home-dashboard",level:2},{value:"Brewhack",id:"brewhack",level:2},{value:"The Dragons Lair",id:"the-dragons-lair",level:2},{value:"Homelab & VPS dashboard",id:"homelab--vps-dashboard",level:2},{value:"Raspberry PI Docker Dashboard",id:"raspberry-pi-docker-dashboard",level:2},{value:"First Week of Self-Hosting",id:"first-week-of-self-hosting",level:2},{value:"EVO Dashboard",id:"evo-dashboard",level:2},{value:"The Private Dashboard",id:"the-private-dashboard",level:2},{value:"Networking Services",id:"networking-services",level:2},{value:"Dashy Live",id:"dashy-live",level:2},{value:"System Monitor",id:"system-monitor",level:2},{value:"Browser Startpage",id:"browser-startpage",level:2},{value:"CFT Toolbox",id:"cft-toolbox",level:2},{value:"Bookmarks",id:"bookmarks",level:2},{value:"Project Management",id:"project-management",level:2},{value:"Dashy Example",id:"dashy-example",level:2},{value:"HomeLAb 3.0",id:"homelab-30",level:2},{value:"Ground Control",id:"ground-control",level:2},{value:"Croco_Grievous",id:"croco_grievous",level:2},{value:"Crypto Dash",id:"crypto-dash",level:2},{value:"Stefantigro",id:"stefantigro",level:2},{value:"HomeLab 3.0",id:"homelab-30-1",level:2},{value:"Morning Dashboard",id:"morning-dashboard",level:2},{value:"Yet Another Homelab",id:"yet-another-homelab",level:2},{value:"Submitting your Dashboard",id:"submitting-your-dashboard",level:2},{value:"How to Submit",id:"how-to-submit",level:3},{value:"What to Include",id:"what-to-include",level:3},{value:"Template",id:"template",level:3}];function c(s){const e={a:"a",blockquote:"blockquote",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",sup:"sup",table:"table",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...s.components};return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(e.header,{children:(0,h.jsxs)(e.h1,{id:"dashy-showcase-",children:[(0,h.jsx)(e.em,{children:"Dashy Showcase"})," \ud83c\udf1f"]})}),"\n\n\n\n\n\n\n",(0,h.jsx)(e.table,{children:(0,h.jsx)(e.thead,{children:(0,h.jsx)(e.tr,{children:(0,h.jsxs)(e.th,{children:["\ud83d\udc97 Got a sweet dashboard? Submit it to the showcase! \ud83d\udc49 ",(0,h.jsx)(e.a,{href:"#submitting-your-dashboard",children:"See How"})]})})})}),"\n",(0,h.jsx)(e.h2,{id:"mndashboard",children:"MNDashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/mahrnet",children:"@mahrnet"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/1491",children:"#1491"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/mCJRZgp/d2-At-QO4c-PT4u.png",alt:"screenshot-MNDashboard"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"home-lab-20",children:"Home Lab 2.0"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/1-home-lab-material.png",alt:"screenshot-homelab"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"dipans-dash",children:"Dipan's Dash"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/dipanghosh",children:"@Dipanghosh"})," - ",(0,h.jsx)(e.a,{href:"https://dipan.de/",children:"dipan.de"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/discussions/777",children:"#777"})]})]}),"\n"]}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["With the option of embedding iframes, it becomes possible to combine and conquer. I have set up Dashy to include graphs from grafana, and set them so that they update automatically. I also have uptime monitors from uptime Kuma, panels from homeassistant are also possible to be included.",(0,h.jsx)(e.br,{}),"\nDashy is awesome!"]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/bPC6w2n/dipanghosh-dashboard.png",alt:"screenshot-dipanghosh-dashboard"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"ratty222",children:"Ratty222"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/ratty222",children:"@ratty222"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/discussions/384",children:"#384"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://user-images.githubusercontent.com/1862727/147582551-4c655d37-8bcc-4f95-ab41-164a9d0d6a07.png",alt:"screenshot-ratty222-dashy"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"hugalafutro-dashy",children:"Hugalafutro Dashy"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/hugalafutro",children:"@hugalafutro"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/discussions/505",children:"#505"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.a,{href:"https://i.ibb.co/PDpLDKS/hugalafutro-dashy.gif",children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/PDpLDKS/hugalafutro-dashy.gif",alt:"hugalafutro-dashy-screenshot"})})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"nas-home-dashboard",children:"NAS Home Dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/cerealconyogurt",children:"@cerealconyogurt"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/74",children:"#74"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/6-nas-home-dashboard.png",alt:"screenshot-networking-services"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"brewhack",children:"Brewhack"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/brpeterso",children:"@brpeterso"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/680",children:"#680"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/cNjzPT4/brewhack.png",alt:"screenshot-brewhack-dashboard"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"the-dragons-lair",children:"The Dragons Lair"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/dr460nf1r3",children:"dr460nf1r3"})," ",(0,h.jsxs)(e.sup,{children:["via ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/684",children:"#684"})]})]}),"\n"]}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsx)(e.p,{children:"My new startpage featuring the stuff I use most. And of course dragons!"}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/13-dragons-lair.png",alt:"screenshot-dragons-lair"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"homelab--vps-dashboard",children:"Homelab & VPS dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/shadowking001",children:"@shadowking001"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/86",children:"#86"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/8-shadowking001s-dashy.png",alt:"screenshot-shadowking001-dashy"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"raspberry-pi-docker-dashboard",children:"Raspberry PI Docker Dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/henkiewie",children:"@henkiewie"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/622",children:"#622"})]})]}),"\n"]}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["I use this dashboard every day. It now even includes a player for a radio stream which I configured with Logitech media server and icecast. I made an smaller version of the grafana dashboard to fit an iframe in kiosk mode, so it monitors the most important values of my RPI. The PI is in Argon m2 case and used as a NAS. The dashboard is a copy of the adventure theme with some changes saved in ",(0,h.jsx)(e.code,{children:"/app/src/styles/user-defined-themes.scss"})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/jGzPm6b/henkiewie-dashy-showcase.png",alt:"screenshot-henkiewie-dashy"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"first-week-of-self-hosting",children:"First Week of Self-Hosting"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/user/RickyCZ",children:"u//RickyCZ"})," ",(0,h.jsxs)(e.sup,{children:["via ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/r/selfhosted/comments/pose15/just_got_started_a_week_ago_selfhosting_is_very/",children:"Reddit"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/11-ricky-cz.png",alt:"screenshot-week-of-self-hosting"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"evo-dashboard",children:"EVO Dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/EVOTk",children:"@EVOTk"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/pull/316",children:"#316"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/12-evo-dashboard.png",alt:"screenshot-evo-dashboard"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"the-private-dashboard",children:"The Private Dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/DylanBeMe",children:"@DylanBeMe"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/419",children:"#419"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/hKS483T/private-dashboard-Dylan-Be-Me.png",alt:"screenshot-private-dashboard"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"networking-services",children:"Networking Services"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/lissy93",children:"@Lissy93"})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/2-networking-services-minimal-dark.png",alt:"screenshot-networking-services"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"dashy-live",children:"Dashy Live"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/lissy93",children:"@Lissy93"})]}),"\n"]}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["A dashboard I made to manage all project development links from one place. View demo at ",(0,h.jsx)(e.a,{href:"https://live.dashy.to/",children:"live.dashy.to"}),"."]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/10-dashy-live.png",alt:"screenshot-dashy-live"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"system-monitor",children:"System Monitor"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsx)(e.p,{children:"An aggregated board for monitoring system resource usage from a single view"}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/xfK6BGb/system-monitor-board.png",alt:"screenshot-monitor"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"browser-startpage",children:"Browser Startpage"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/rs07dS1/startpage.png",alt:"screenshot-startpage"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"cft-toolbox",children:"CFT Toolbox"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/3-cft-toolbox.png",alt:"screenshot-cft-toolbox"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"bookmarks",children:"Bookmarks"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/4-bookmarks-colourful.png",alt:"screenshot-bookmarks"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"project-management",children:"Project Management"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/5-project-management.png",alt:"screenshot-project-management"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"dashy-example",children:"Dashy Example"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["An example dashboard, by ",(0,h.jsx)(e.a,{href:"https://github.com/lissy93",children:"@Lissy93"}),". View live at ",(0,h.jsx)(e.a,{href:"https://demo.dashy.to/",children:"demo.dashy.to"}),"."]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/YbzqPK7/demo-dashy.png",alt:"screenshot-dashy-example"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"homelab-30",children:"HomeLAb 3.0"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/skoogee",children:"@skoogee"})," (",(0,h.jsx)(e.a,{href:"http://zhrn.cc",children:"http://zhrn.cc"}),") ",(0,h.jsx)(e.sup,{children:(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/279",children:"#279"})})]}),"\n"]}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsx)(e.p,{children:"Dashy, is the most complete dashboard I ever tried, has all the features, and it sets itself apart from the rest. It is my default homepage now. I am thankful to the developer @Lissy93 for sharing such a wonderful creation."}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.a,{href:"https://ibb.co/album/ynSwzm",children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/F5yBTsT/12-skoogee-homelab-3.png?",alt:"screenshot-12-skoogee-homelab-3"})})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"ground-control",children:"Ground Control"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://github.com/dtctek",children:"@dtctek"})," ",(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/83",children:"#83"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://user-images.githubusercontent.com/1862727/149821995-e9b41dab-186c-42e6-b5b3-e233259b241d.png",alt:"screenshot-ground-control"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"croco_grievous",children:"Croco_Grievous"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/user/Croco_Grievous/",children:"u/Croco_Grievous"})," ",(0,h.jsxs)(e.sup,{children:["via ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/r/selfhosted/comments/t4xk3z/everything_started_with_pihole_on_a_raspberry_pi/",children:"reddit"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/59XR8KL/dashy-Croco-Grievous.png",alt:"screenshot-croco-grievous-dashy"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"crypto-dash",children:"Crypto Dash"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["Example usage of widgets to monitor cryptocurrencies news, prices and data. Config is ",(0,h.jsx)(e.a,{href:"https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10#file-example-8-dashy-crypto-widgets-conf-yml",children:"available here"})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://user-images.githubusercontent.com/1862727/147394584-352fe3bf-740d-4624-a01b-9003a97bc832.png",alt:"screenshot-crypto-dash"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"stefantigro",children:"Stefantigro"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsxs)(e.p,{children:["By ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/user/stefantigro/",children:"u/stefantigro"})," ",(0,h.jsxs)(e.sup,{children:["via ",(0,h.jsx)(e.a,{href:"https://www.reddit.com/r/selfhosted/comments/t5oril/been_selfhosting_close_to_half_a_year_now_all/",children:"reddit"})]})]}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/1Kb43Yy/dashy-stefantigro.png",alt:"screenshot-stefantigro-dashy"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"homelab-30-1",children:"HomeLab 3.0"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsx)(e.p,{children:(0,h.jsxs)(e.sup,{children:["Re: ",(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/issues/1999",children:"#1999"})]})}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://pixelflare.cc/alicia/dashy/12-skoogee-homelab-3",alt:"screenshot-homelab-3"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"morning-dashboard",children:"Morning Dashboard"}),"\n",(0,h.jsxs)(e.blockquote,{children:["\n",(0,h.jsx)(e.p,{children:"Displayed on my smart screen between 05:00 - 08:00, and includes all the info that I usually check before leaving for work"}),"\n"]}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://i.ibb.co/4Wx8zb7/morning-dashboard.png",alt:"screenshot-morning-dash"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"yet-another-homelab",children:"Yet Another Homelab"}),"\n",(0,h.jsx)(e.p,{children:(0,h.jsx)(e.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/9-home-lab-oblivion.png",alt:"screenshot-yet-another-homelab"})}),"\n",(0,h.jsx)(e.hr,{}),"\n",(0,h.jsx)(e.h2,{id:"submitting-your-dashboard",children:"Submitting your Dashboard"}),"\n",(0,h.jsx)(e.h3,{id:"how-to-submit",children:"How to Submit"}),"\n",(0,h.jsxs)(e.ul,{children:["\n",(0,h.jsx)(e.li,{children:(0,h.jsx)(e.a,{href:"https://git.io/JEtgM",children:"Open an Issue"})}),"\n",(0,h.jsx)(e.li,{children:(0,h.jsx)(e.a,{href:"https://github.com/Lissy93/dashy/compare",children:"Open a PR"})}),"\n"]}),"\n",(0,h.jsx)(e.h3,{id:"what-to-include",children:"What to Include"}),"\n",(0,h.jsx)(e.p,{children:"Please include the following information:"}),"\n",(0,h.jsxs)(e.ul,{children:["\n",(0,h.jsx)(e.li,{children:"A single high-quality screenshot of your Dashboard"}),"\n",(0,h.jsx)(e.li,{children:"A short title (it doesn't have to be particularly imaginative)"}),"\n",(0,h.jsx)(e.li,{children:"An optional description, you could include details on anything interesting or unique about your dashboard, or say how you use it, and why it's awesome"}),"\n",(0,h.jsx)(e.li,{children:"Optionally leave your name or username, with a link to your GitHub, Twitter or Website"}),"\n"]}),"\n",(0,h.jsx)(e.h3,{id:"template",children:"Template"}),"\n",(0,h.jsx)(e.p,{children:"If you're submitting a pull request, please use a format similar to this:"}),"\n",(0,h.jsx)(e.pre,{children:(0,h.jsx)(e.code,{className:"language-text",children:"### [Dashboard Name] (required)\n\n> Submitted by [@username](https://github.com/user) (optional)\n\n[An optional text description, or any interesting details] (optional)\n\n![dashboard-screenshot](https://example.com/url-to-screenshot.png) (required)\n\n---\n\n"})})]})}function l(s={}){const{wrapper:e}={...(0,t.R)(),...s.components};return e?(0,h.jsx)(e,{...s,children:(0,h.jsx)(c,{...s})}):c(s)}},28453(s,e,n){n.d(e,{R:()=>i,x:()=>a});var r=n(96540);const h={},t=r.createContext(h);function i(s){const e=r.useContext(t);return r.useMemo(function(){return"function"==typeof s?s(e):{...e,...s}},[e,s])}function a(s){let e;return e=s.disableParentContext?"function"==typeof s.components?s.components(h):s.components||h:i(s.components),r.createElement(t.Provider,{value:e},s.children)}}}]); \ No newline at end of file diff --git a/assets/js/4120607c.443d382a.js b/assets/js/4120607c.a6f09d70.js similarity index 99% rename from assets/js/4120607c.443d382a.js rename to assets/js/4120607c.a6f09d70.js index b06d4a51..e75b8b10 100644 --- a/assets/js/4120607c.443d382a.js +++ b/assets/js/4120607c.a6f09d70.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[4108],{99616(e,s,n){n.r(s),n.d(s,{assets:()=>l,contentTitle:()=>a,default:()=>c,frontMatter:()=>i,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"deployment/cloud","title":"Cloud Services","description":"Dashy is intended to be run locally, within your LAN/homelab. But it can also be deployed to the cloud for certain use cases.","source":"@site/docs/deployment/cloud.md","sourceDirName":"deployment","slug":"/deployment/cloud","permalink":"/docs/deployment/cloud","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/deployment/cloud.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{}}');var o=n(74848),t=n(28453);const i={},a="Cloud Services",l={},d=[{value:"Static Hosting Providers",id:"static-hosting-providers",level:2},{value:"Netlify",id:"netlify",level:3},{value:"Vercel",id:"vercel",level:3},{value:"EdgeOne Pages",id:"edgeone-pages",level:3},{value:"Cloudflare Pages",id:"cloudflare-pages",level:3},{value:"Firebase Hosting",id:"firebase-hosting",level:3},{value:"Azure Static Web Apps",id:"azure-static-web-apps",level:3},{value:"Container Runtimes",id:"container-runtimes",level:2},{value:"Render",id:"render",level:3},{value:"Railway",id:"railway",level:3},{value:"Fly.io",id:"flyio",level:3},{value:"Koyeb",id:"koyeb",level:3},{value:"Northflank",id:"northflank",level:3},{value:"DigitalOcean App Platform",id:"digitalocean-app-platform",level:3},{value:"Azure Container Apps",id:"azure-container-apps",level:3},{value:"Google Cloud Run",id:"google-cloud-run",level:3},{value:"Managed Hosting",id:"managed-hosting",level:2},{value:"Hosting with any CDN",id:"hosting-with-any-cdn",level:2},{value:"Other VPS",id:"other-vps",level:2}];function h(e){const s={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,t.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(s.header,{children:(0,o.jsx)(s.h1,{id:"cloud-services",children:"Cloud Services"})}),"\n",(0,o.jsx)(s.p,{children:"Dashy is intended to be run locally, within your LAN/homelab. But it can also be deployed to the cloud for certain use cases."}),"\n",(0,o.jsx)(s.p,{children:"Where possible, I've included templates and 1-click install links, so you can deploy your own instance in under a minute."}),"\n",(0,o.jsx)(s.admonition,{type:"important",children:(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:"It's your responsibility to secure your instance and set up access control"}),"\n",(0,o.jsx)(s.li,{children:"On pure static / CDN hosting, Dashy's server endpoints (like saving config through the UI) don't work"}),"\n",(0,o.jsx)(s.li,{children:"Links are unofficial unless otherwise stated, so we can't offer support for them"}),"\n"]})}),"\n",(0,o.jsx)(s.hr,{}),"\n",(0,o.jsx)(s.h2,{id:"static-hosting-providers",children:"Static Hosting Providers"}),"\n",(0,o.jsx)(s.h3,{id:"netlify",children:"Netlify"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://www.netlify.com/",children:"Netlify"})," is static hosting with Git deploys. Click the button and Netlify uses the included ",(0,o.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/netlify.toml",children:(0,o.jsx)(s.code,{children:"netlify.toml"})})," to build and publish Dashy. Alternatively, fork the repo and import your fork into Netlify, so changes redeploy automatically. Your fork can be private if needed. Dashy's status checks and CORS proxy run as Netlify functions (wired up in ",(0,o.jsx)(s.code,{children:"netlify.toml"}),"), so those work here, though saving config through the UI still doesn't."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Netlify&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%2300C7B7&secondaryIcon=netlify&borderRadius=2",alt:"Deploy to Netlify"})})}),"\n",(0,o.jsx)(s.h3,{id:"vercel",children:"Vercel"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://vercel.com/",children:"Vercel"})," is static hosting with GitHub integration. Either click the button below, or fork and import the repo, and Vercel will build and deploy your own Dashy instance."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://vercel.com/new/clone?repository-url=https://github.com/lissy93/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Vercel&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%23000000&secondaryIcon=vercel&borderRadius=2",alt:"Deploy with Vercel"})})}),"\n",(0,o.jsx)(s.h3,{id:"edgeone-pages",children:"EdgeOne Pages"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://pages.edgeone.ai",children:"EdgeOne Pages"})," is static hosting on Tencent's edge. Click the button and import the repo; EdgeOne builds Dashy from Git."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://edgeone.ai/pages/new?repository-url=https://github.com/lissy93/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=EdgeOne&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%230055D2&borderRadius=2",alt:"Deploy to EdgeOne"})})}),"\n",(0,o.jsx)(s.h3,{id:"cloudflare-pages",children:"Cloudflare Pages"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://pages.cloudflare.com/",children:"Cloudflare Pages"})," is static hosting on Cloudflare's edge. Import the repo, set build command ",(0,o.jsx)(s.code,{children:"yarn build"}),", and set output directory ",(0,o.jsx)(s.code,{children:"dist"}),"."]}),"\n",(0,o.jsx)(s.h3,{id:"firebase-hosting",children:"Firebase Hosting"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://firebase.google.com/products/hosting",children:"Firebase Hosting"})," is static hosting on Google's CDN. Run ",(0,o.jsx)(s.code,{children:"yarn build"}),", deploy ",(0,o.jsx)(s.code,{children:"dist"})," with the Firebase CLI, and add an SPA rewrite to ",(0,o.jsx)(s.code,{children:"index.html"}),"."]}),"\n",(0,o.jsx)(s.h3,{id:"azure-static-web-apps",children:"Azure Static Web Apps"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://learn.microsoft.com/en-us/azure/static-web-apps/",children:"Azure Static Web Apps"})," is static hosting with GitHub Actions. Create a Static Web App from the repo with app location ",(0,o.jsx)(s.code,{children:"/"}),", build command ",(0,o.jsx)(s.code,{children:"yarn build"}),", and output location ",(0,o.jsx)(s.code,{children:"dist"}),"."]}),"\n",(0,o.jsx)(s.hr,{}),"\n",(0,o.jsx)(s.h2,{id:"container-runtimes",children:"Container Runtimes"}),"\n",(0,o.jsx)(s.admonition,{type:"note",children:(0,o.jsxs)(s.p,{children:["These run Dashy's Node server, so status checks and server endpoints work. Mount ",(0,o.jsx)(s.code,{children:"/app/user-data"})," if config edits or uploaded assets should survive restarts."]})}),"\n",(0,o.jsx)(s.h3,{id:"render",children:"Render"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://render.com/",children:"Render"})," is managed container hosting. Click the button and Render uses the included ",(0,o.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/render.yaml",children:(0,o.jsx)(s.code,{children:"render.yaml"})})," to build Dashy. To persist ",(0,o.jsx)(s.code,{children:"user-data"}),", uncomment the disk in that file, which requires a paid plan."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://render.com/deploy?repo=https://github.com/lissy93/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Render&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%23000000&secondaryIcon=render&borderRadius=2",alt:"Deploy to Render"})})}),"\n",(0,o.jsx)(s.h3,{id:"railway",children:"Railway"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://railway.com/",children:"Railway"})," is managed container hosting. Click the button to deploy the Dashy template with ",(0,o.jsx)(s.code,{children:"lissy93/dashy:latest"})," and a persistent ",(0,o.jsx)(s.code,{children:"/app/user-data"})," volume."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://railway.com/deploy/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Railway&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%230B0D0E&secondaryIcon=railway&borderRadius=2",alt:"Deploy on Railway"})})}),"\n",(0,o.jsx)(s.h3,{id:"flyio",children:"Fly.io"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://fly.io/",children:"Fly.io"})," runs Docker apps on Fly Machines. Install ",(0,o.jsx)(s.a,{href:"https://fly.io/docs/flyctl/install/",children:(0,o.jsx)(s.code,{children:"flyctl"})}),", log in, then run the following. If it asks you for the internal port, use ",(0,o.jsx)(s.code,{children:"8080"})]}),"\n",(0,o.jsx)(s.pre,{children:(0,o.jsx)(s.code,{className:"language-bash",children:"fly launch --from https://github.com/lissy93/dashy --no-deploy\nfly deploy\n"})}),"\n",(0,o.jsx)(s.h3,{id:"koyeb",children:"Koyeb"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://www.koyeb.com/",children:"Koyeb"})," is serverless container hosting. Click the button to deploy ",(0,o.jsx)(s.code,{children:"lissy93/dashy:latest"})," from Docker Hub with HTTP port ",(0,o.jsx)(s.code,{children:"8080"}),"."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://app.koyeb.com/deploy?type=docker&image=docker.io/lissy93/dashy:latest&name=dashy&ports=8080%3Bhttp%3B%2F",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Koyeb&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%23121212&secondaryIcon=koyeb&borderRadius=2",alt:"Deploy to Koyeb"})})}),"\n",(0,o.jsx)(s.h3,{id:"northflank",children:"Northflank"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://northflank.com/",children:"Northflank"})," is managed container hosting. To get started, either follow ",(0,o.jsx)(s.a,{href:"https://northflank.com/guides/deploy-dashy-with-northflank",children:"this guide"}),", or use the 1-click button below."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://northflank.com/stacks/deploy-dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Northflank&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%230093FF&borderRadius=2",alt:"Deploy to Northflank"})})}),"\n",(0,o.jsx)(s.h3,{id:"digitalocean-app-platform",children:"DigitalOcean App Platform"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://www.digitalocean.com/products/app-platform",children:"DigitalOcean App Platform"})," is managed app hosting. Create an app from the public Docker image ",(0,o.jsx)(s.code,{children:"lissy93/dashy:latest"})," and use HTTP port ",(0,o.jsx)(s.code,{children:"8080"}),"."]}),"\n",(0,o.jsx)(s.h3,{id:"azure-container-apps",children:"Azure Container Apps"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://learn.microsoft.com/en-us/azure/container-apps/",children:"Azure Container Apps"})," is serverless container hosting on Azure. Deploy the public image with Azure CLI:"]}),"\n",(0,o.jsx)(s.pre,{children:(0,o.jsx)(s.code,{className:"language-bash",children:"az containerapp up \\\n --name dashy \\\n --image docker.io/lissy93/dashy:latest \\\n --ingress external \\\n --target-port 8080\n"})}),"\n",(0,o.jsx)(s.h3,{id:"google-cloud-run",children:"Google Cloud Run"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://cloud.google.com/run",children:"Cloud Run"})," is serverless container hosting on Google Cloud. Click the button to open Cloud Shell and build Dashy from the repo's ",(0,o.jsx)(s.code,{children:"Dockerfile"}),"."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://deploy.cloud.run/?git_repo=https://github.com/Lissy93/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Cloud%20Run&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%234285F4&secondaryIcon=googlecloud&borderRadius=2",alt:"Run on Google Cloud"})})}),"\n",(0,o.jsx)(s.hr,{}),"\n",(0,o.jsx)(s.h2,{id:"managed-hosting",children:"Managed Hosting"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"https://elest.io/open-source/dashy",children:"Elestio"})," - Fully managed Dashy, or bring your own VM"]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"https://www.pikapods.com/apps/",children:"PikaPods"})," - Managed open-source app hosting; search for Dashy and click ",(0,o.jsx)(s.strong,{children:"Run Your Own"})]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"https://www.hostinger.com/applications/dashy",children:"Hostinger"})," - VPS hosting with a one-click Dashy Docker template"]}),"\n"]}),"\n",(0,o.jsx)(s.hr,{}),"\n",(0,o.jsx)(s.h2,{id:"hosting-with-any-cdn",children:"Hosting with any CDN"}),"\n",(0,o.jsxs)(s.p,{children:["Once Dashy has been built, it is effectively just a static web app. This means that it can be served up with pretty much any static host, CDN or web server. To host Dashy through a CDN, the steps are very similar to building from source: clone the project, cd into it, install dependencies, write your config file and build the app. Once build is complete you will have a ",(0,o.jsx)(s.code,{children:"./dist"})," directory within Dashy's root, and this is the build application which is ready to be served up."]}),"\n",(0,o.jsx)(s.p,{children:"However without Dashy's node server, there are a couple of features that will be unavailable to you, including: writing config changes to disk through the UI, and application status checks. Everything else will work fine."}),"\n",(0,o.jsx)(s.hr,{}),"\n",(0,o.jsx)(s.h2,{id:"other-vps",children:"Other VPS"}),"\n",(0,o.jsxs)(s.p,{children:["Dashy can be deployed to almost any VPS provider by following our ",(0,o.jsx)(s.a,{href:"/docs/deployment/docker",children:"Docker"})," guide."]})]})}function c(e={}){const{wrapper:s}={...(0,t.R)(),...e.components};return s?(0,o.jsx)(s,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},28453(e,s,n){n.d(s,{R:()=>i,x:()=>a});var r=n(96540);const o={},t=r.createContext(o);function i(e){const s=r.useContext(t);return r.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),r.createElement(t.Provider,{value:s},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[4108],{99616(e,s,n){n.r(s),n.d(s,{assets:()=>l,contentTitle:()=>a,default:()=>c,frontMatter:()=>i,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"deployment/cloud","title":"Cloud Services","description":"Dashy is intended to be run locally, within your LAN/homelab. But it can also be deployed to the cloud for certain use cases.","source":"@site/docs/deployment/cloud.md","sourceDirName":"deployment","slug":"/deployment/cloud","permalink":"/docs/deployment/cloud","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/deployment/cloud.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{}}');var o=n(74848),t=n(28453);const i={},a="Cloud Services",l={},d=[{value:"Static Hosting Providers",id:"static-hosting-providers",level:2},{value:"Netlify",id:"netlify",level:3},{value:"Vercel",id:"vercel",level:3},{value:"EdgeOne Pages",id:"edgeone-pages",level:3},{value:"Cloudflare Pages",id:"cloudflare-pages",level:3},{value:"Firebase Hosting",id:"firebase-hosting",level:3},{value:"Azure Static Web Apps",id:"azure-static-web-apps",level:3},{value:"Container Runtimes",id:"container-runtimes",level:2},{value:"Render",id:"render",level:3},{value:"Railway",id:"railway",level:3},{value:"Fly.io",id:"flyio",level:3},{value:"Koyeb",id:"koyeb",level:3},{value:"Northflank",id:"northflank",level:3},{value:"DigitalOcean App Platform",id:"digitalocean-app-platform",level:3},{value:"Azure Container Apps",id:"azure-container-apps",level:3},{value:"Google Cloud Run",id:"google-cloud-run",level:3},{value:"Managed Hosting",id:"managed-hosting",level:2},{value:"Hosting with any CDN",id:"hosting-with-any-cdn",level:2},{value:"Other VPS",id:"other-vps",level:2}];function h(e){const s={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,t.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(s.header,{children:(0,o.jsx)(s.h1,{id:"cloud-services",children:"Cloud Services"})}),"\n",(0,o.jsx)(s.p,{children:"Dashy is intended to be run locally, within your LAN/homelab. But it can also be deployed to the cloud for certain use cases."}),"\n",(0,o.jsx)(s.p,{children:"Where possible, I've included templates and 1-click install links, so you can deploy your own instance in under a minute."}),"\n",(0,o.jsx)(s.admonition,{type:"important",children:(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:"It's your responsibility to secure your instance and set up access control"}),"\n",(0,o.jsx)(s.li,{children:"On pure static / CDN hosting, Dashy's server endpoints (like saving config through the UI) don't work"}),"\n",(0,o.jsx)(s.li,{children:"Links are unofficial unless otherwise stated, so we can't offer support for them"}),"\n"]})}),"\n",(0,o.jsx)(s.hr,{}),"\n",(0,o.jsx)(s.h2,{id:"static-hosting-providers",children:"Static Hosting Providers"}),"\n",(0,o.jsx)(s.h3,{id:"netlify",children:"Netlify"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://www.netlify.com/",children:"Netlify"})," is static hosting with Git deploys. Click the button and Netlify uses the included ",(0,o.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/netlify.toml",children:(0,o.jsx)(s.code,{children:"netlify.toml"})})," to build and publish Dashy. Alternatively, fork the repo and import your fork into Netlify, so changes redeploy automatically. Your fork can be private if needed. Dashy's status checks and CORS proxy run as Netlify functions (wired up in ",(0,o.jsx)(s.code,{children:"netlify.toml"}),"), so those work here, though saving config through the UI still doesn't."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Netlify&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%2300C7B7&secondaryIcon=netlify&borderRadius=2",alt:"Deploy to Netlify"})})}),"\n",(0,o.jsx)(s.h3,{id:"vercel",children:"Vercel"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://vercel.com/",children:"Vercel"})," is static hosting with GitHub integration. Either click the button below, or fork and import the repo, and Vercel will build and deploy your own Dashy instance."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://vercel.com/new/clone?repository-url=https://github.com/lissy93/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Vercel&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%23000000&secondaryIcon=vercel&borderRadius=2",alt:"Deploy with Vercel"})})}),"\n",(0,o.jsx)(s.h3,{id:"edgeone-pages",children:"EdgeOne Pages"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://pages.edgeone.ai",children:"EdgeOne Pages"})," is static hosting on Tencent's edge. Click the button and import the repo; EdgeOne builds Dashy from Git."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://edgeone.ai/pages/new?repository-url=https://github.com/lissy93/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=EdgeOne&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%230055D2&borderRadius=2",alt:"Deploy to EdgeOne"})})}),"\n",(0,o.jsx)(s.h3,{id:"cloudflare-pages",children:"Cloudflare Pages"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://pages.cloudflare.com/",children:"Cloudflare Pages"})," is static hosting on Cloudflare's edge. Import the repo, set build command ",(0,o.jsx)(s.code,{children:"yarn build"}),", and set output directory ",(0,o.jsx)(s.code,{children:"dist"}),"."]}),"\n",(0,o.jsx)(s.h3,{id:"firebase-hosting",children:"Firebase Hosting"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://firebase.google.com/products/hosting",children:"Firebase Hosting"})," is static hosting on Google's CDN. Run ",(0,o.jsx)(s.code,{children:"yarn build"}),", deploy ",(0,o.jsx)(s.code,{children:"dist"})," with the Firebase CLI, and add an SPA rewrite to ",(0,o.jsx)(s.code,{children:"index.html"}),"."]}),"\n",(0,o.jsx)(s.h3,{id:"azure-static-web-apps",children:"Azure Static Web Apps"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://learn.microsoft.com/en-us/azure/static-web-apps/",children:"Azure Static Web Apps"})," is static hosting with GitHub Actions. Create a Static Web App from the repo with app location ",(0,o.jsx)(s.code,{children:"/"}),", build command ",(0,o.jsx)(s.code,{children:"yarn build"}),", and output location ",(0,o.jsx)(s.code,{children:"dist"}),"."]}),"\n",(0,o.jsx)(s.hr,{}),"\n",(0,o.jsx)(s.h2,{id:"container-runtimes",children:"Container Runtimes"}),"\n",(0,o.jsx)(s.admonition,{type:"note",children:(0,o.jsxs)(s.p,{children:["These run Dashy's Node server, so status checks and server endpoints work. Mount ",(0,o.jsx)(s.code,{children:"/app/user-data"})," if config edits or uploaded assets should survive restarts."]})}),"\n",(0,o.jsx)(s.h3,{id:"render",children:"Render"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://render.com/",children:"Render"})," is managed container hosting. Click the button and Render uses the included ",(0,o.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/render.yaml",children:(0,o.jsx)(s.code,{children:"render.yaml"})})," to build Dashy. To persist ",(0,o.jsx)(s.code,{children:"user-data"}),", uncomment the disk in that file, which requires a paid plan."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://render.com/deploy?repo=https://github.com/lissy93/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Render&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%23000000&secondaryIcon=render&borderRadius=2",alt:"Deploy to Render"})})}),"\n",(0,o.jsx)(s.h3,{id:"railway",children:"Railway"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://railway.com/",children:"Railway"})," is managed container hosting. Click the button to deploy the Dashy template with ",(0,o.jsx)(s.code,{children:"lissy93/dashy:latest"})," and a persistent ",(0,o.jsx)(s.code,{children:"/app/user-data"})," volume."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://railway.com/deploy/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Railway&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%230B0D0E&secondaryIcon=railway&borderRadius=2",alt:"Deploy on Railway"})})}),"\n",(0,o.jsx)(s.h3,{id:"flyio",children:"Fly.io"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://fly.io/",children:"Fly.io"})," runs Docker apps on Fly Machines. Install ",(0,o.jsx)(s.a,{href:"https://fly.io/docs/flyctl/install/",children:(0,o.jsx)(s.code,{children:"flyctl"})}),", log in, then run the following. If it asks you for the internal port, use ",(0,o.jsx)(s.code,{children:"8080"})]}),"\n",(0,o.jsx)(s.pre,{children:(0,o.jsx)(s.code,{className:"language-bash",children:"fly launch --from https://github.com/lissy93/dashy --no-deploy\nfly deploy\n"})}),"\n",(0,o.jsx)(s.h3,{id:"koyeb",children:"Koyeb"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://www.koyeb.com/",children:"Koyeb"})," is serverless container hosting. Click the button to deploy ",(0,o.jsx)(s.code,{children:"lissy93/dashy:latest"})," from Docker Hub with HTTP port ",(0,o.jsx)(s.code,{children:"8080"}),"."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://app.koyeb.com/deploy?type=docker&image=docker.io/lissy93/dashy:latest&name=dashy&ports=8080%3Bhttp%3B%2F",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Koyeb&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%23121212&secondaryIcon=koyeb&borderRadius=2",alt:"Deploy to Koyeb"})})}),"\n",(0,o.jsx)(s.h3,{id:"northflank",children:"Northflank"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://northflank.com/",children:"Northflank"})," is managed container hosting. To get started, either follow ",(0,o.jsx)(s.a,{href:"https://northflank.com/guides/deploy-dashy-with-northflank",children:"this guide"}),", or use the 1-click button below."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://northflank.com/stacks/deploy-dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Northflank&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%230093FF&borderRadius=2",alt:"Deploy to Northflank"})})}),"\n",(0,o.jsx)(s.h3,{id:"digitalocean-app-platform",children:"DigitalOcean App Platform"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://www.digitalocean.com/products/app-platform",children:"DigitalOcean App Platform"})," is managed app hosting. Create an app from the public Docker image ",(0,o.jsx)(s.code,{children:"lissy93/dashy:latest"})," and use HTTP port ",(0,o.jsx)(s.code,{children:"8080"}),"."]}),"\n",(0,o.jsx)(s.h3,{id:"azure-container-apps",children:"Azure Container Apps"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://learn.microsoft.com/en-us/azure/container-apps/",children:"Azure Container Apps"})," is serverless container hosting on Azure. Deploy the public image with Azure CLI:"]}),"\n",(0,o.jsx)(s.pre,{children:(0,o.jsx)(s.code,{className:"language-bash",children:"az containerapp up \\\n --name dashy \\\n --image docker.io/lissy93/dashy:latest \\\n --ingress external \\\n --target-port 8080\n"})}),"\n",(0,o.jsx)(s.h3,{id:"google-cloud-run",children:"Google Cloud Run"}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://cloud.google.com/run",children:"Cloud Run"})," is serverless container hosting on Google Cloud. Click the button to open Cloud Shell and build Dashy from the repo's ",(0,o.jsx)(s.code,{children:"Dockerfile"}),"."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.a,{href:"https://deploy.cloud.run/?git_repo=https://github.com/Lissy93/dashy",children:(0,o.jsx)(s.img,{src:"https://forthebadge.com/api/badges/generate?panels=2&primaryLabel=Deploy&secondaryLabel=Cloud%20Run&primaryBGColor=%23393939&primaryTextColor=%23FFFFFF&secondaryBGColor=%234285F4&secondaryIcon=googlecloud&borderRadius=2",alt:"Run on Google Cloud"})})}),"\n",(0,o.jsx)(s.hr,{}),"\n",(0,o.jsx)(s.h2,{id:"managed-hosting",children:"Managed Hosting"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"https://elest.io/open-source/dashy",children:"Elestio"})," - Fully managed Dashy, or bring your own VM"]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"https://www.pikapods.com/apps/",children:"PikaPods"})," - Managed open-source app hosting; search for Dashy and click ",(0,o.jsx)(s.strong,{children:"Run Your Own"})]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"https://www.hostinger.com/applications/dashy",children:"Hostinger"})," - VPS hosting with a one-click Dashy Docker template"]}),"\n"]}),"\n",(0,o.jsx)(s.hr,{}),"\n",(0,o.jsx)(s.h2,{id:"hosting-with-any-cdn",children:"Hosting with any CDN"}),"\n",(0,o.jsxs)(s.p,{children:["Once Dashy has been built, it is effectively just a static web app. This means that it can be served up with pretty much any static host, CDN or web server. To host Dashy through a CDN, the steps are very similar to building from source: clone the project, cd into it, install dependencies, write your config file and build the app. Once build is complete you will have a ",(0,o.jsx)(s.code,{children:"./dist"})," directory within Dashy's root, and this is the build application which is ready to be served up."]}),"\n",(0,o.jsx)(s.p,{children:"However without Dashy's node server, there are a couple of features that will be unavailable to you, including: writing config changes to disk through the UI, and application status checks. Everything else will work fine."}),"\n",(0,o.jsx)(s.hr,{}),"\n",(0,o.jsx)(s.h2,{id:"other-vps",children:"Other VPS"}),"\n",(0,o.jsxs)(s.p,{children:["Dashy can be deployed to almost any VPS provider by following our ",(0,o.jsx)(s.a,{href:"/docs/deployment/docker",children:"Docker"})," guide."]})]})}function c(e={}){const{wrapper:s}={...(0,t.R)(),...e.components};return s?(0,o.jsx)(s,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},28453(e,s,n){n.d(s,{R:()=>i,x:()=>a});var r=n(96540);const o={},t=r.createContext(o);function i(e){const s=r.useContext(t);return r.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),r.createElement(t.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/4809.2b896511.js b/assets/js/4809.a561fd4b.js similarity index 91% rename from assets/js/4809.2b896511.js rename to assets/js/4809.a561fd4b.js index 03c878da..2fcd89f2 100644 --- a/assets/js/4809.2b896511.js +++ b/assets/js/4809.a561fd4b.js @@ -1 +1 @@ -(()=>{"use strict";var e,r,t={84809(e,r,t){t.d(r,{BH:()=>s,Ho:()=>n,IH:()=>a,sx:()=>o});t(58291);const o=[],s=["en"],a="search-index{dir}.json?_=b90e52d4",n=1}},o={};function s(e){var r=o[e];if(void 0!==r)return r.exports;var a=o[e]={exports:{}};return t[e](a,a.exports,s),a.exports}s.m=t,s.x=()=>{var e=s.O(void 0,[540],()=>s(90540));return e=s.O(e)},e=[],s.O=(r,t,o,a)=>{if(!t){var n=1/0;for(c=0;c=a)&&Object.keys(s.O).every(e=>s.O[e](t[p]))?t.splice(p--,1):(i=!1,a0&&e[c-1][2]>a;c--)e[c]=e[c-1];e[c]=[t,o,a]},s.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return s.d(r,{a:r}),r},s.d=(e,r)=>{for(var t in r)s.o(r,t)&&!s.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},s.f={},s.e=e=>Promise.all(Object.keys(s.f).reduce((r,t)=>(s.f[t](e,r),r),[])),s.u=e=>"assets/js/"+e+".625d0ed0.js",s.miniCssF=e=>{},s.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),s.p="/",s.gca=function(e){return e={}[e]||e,s.p+s.u(e)},(()=>{var e={4809:1};s.f.i=(r,t)=>{e[r]||importScripts(s.p+s.u(r))};var r=globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[],t=r.push.bind(r);r.push=r=>{var[o,a,n]=r;for(var i in a)s.o(a,i)&&(s.m[i]=a[i]);for(n&&n(s);o.length;)e[o.pop()]=1;t(r)}})(),r=s.x,s.x=()=>s.e(540).then(r);s.x()})(); \ No newline at end of file +(()=>{"use strict";var e,r,t={84809(e,r,t){t.d(r,{BH:()=>s,Ho:()=>n,IH:()=>a,sx:()=>o});t(58291);const o=[],s=["en"],a="search-index{dir}.json?_=61bf829e",n=1}},o={};function s(e){var r=o[e];if(void 0!==r)return r.exports;var a=o[e]={exports:{}};return t[e](a,a.exports,s),a.exports}s.m=t,s.x=()=>{var e=s.O(void 0,[540],()=>s(90540));return e=s.O(e)},e=[],s.O=(r,t,o,a)=>{if(!t){var n=1/0;for(c=0;c=a)&&Object.keys(s.O).every(e=>s.O[e](t[p]))?t.splice(p--,1):(i=!1,a0&&e[c-1][2]>a;c--)e[c]=e[c-1];e[c]=[t,o,a]},s.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return s.d(r,{a:r}),r},s.d=(e,r)=>{for(var t in r)s.o(r,t)&&!s.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},s.f={},s.e=e=>Promise.all(Object.keys(s.f).reduce((r,t)=>(s.f[t](e,r),r),[])),s.u=e=>"assets/js/"+e+".625d0ed0.js",s.miniCssF=e=>{},s.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),s.p="/",s.gca=function(e){return e={}[e]||e,s.p+s.u(e)},(()=>{var e={4809:1};s.f.i=(r,t)=>{e[r]||importScripts(s.p+s.u(r))};var r=globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[],t=r.push.bind(r);r.push=r=>{var[o,a,n]=r;for(var i in a)s.o(a,i)&&(s.m[i]=a[i]);for(n&&n(s);o.length;)e[o.pop()]=1;t(r)}})(),r=s.x,s.x=()=>s.e(540).then(r);s.x()})(); \ No newline at end of file diff --git a/assets/js/4bdb0d83.4d7bfd68.js b/assets/js/4bdb0d83.c08fcb6c.js similarity index 99% rename from assets/js/4bdb0d83.4d7bfd68.js rename to assets/js/4bdb0d83.c08fcb6c.js index acb02d37..49573468 100644 --- a/assets/js/4bdb0d83.4d7bfd68.js +++ b/assets/js/4bdb0d83.c08fcb6c.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[5496],{47395(e,n,s){s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>c,default:()=>a,frontMatter:()=>t,metadata:()=>i,toc:()=>o});const i=JSON.parse('{"id":"configuring","title":"Configuring","description":"All app configuration is specified in /user-data/conf.yml which is in YAML Format format. If you\'re using Docker, this file can be passed in as a volume. Changes can either be made directly to this file, or done through the UI. From the UI you can also export, backup, reset, validate and download your configuration file.","source":"@site/docs/configuring.md","sourceDirName":".","slug":"/configuring","permalink":"/docs/configuring","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/configuring.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Deployment","permalink":"/docs/deployment"},"next":{"title":"App Management","permalink":"/docs/management"}}');var d=s(74848),r=s(28453);const t={},c="Configuring",l={},o=[{value:"There are three ways to edit the config",id:"there-are-three-ways-to-edit-the-config",level:2},{value:"Tips",id:"tips",level:2},{value:"Contents",id:"contents",level:2},{value:"Top-Level Fields",id:"top-level-fields",level:2},{value:"PageInfo",id:"pageinfo",level:2},{value:"pageInfo.navLinks (optional)",id:"pageinfonavlinks-optional",level:2},{value:"pages[] (optional)",id:"pages-optional",level:2},{value:"appConfig (optional)",id:"appconfig-optional",level:2},{value:"appConfig.auth (optional)",id:"appconfigauth-optional",level:2},{value:"appConfig.auth.users (optional)",id:"appconfigauthusers-optional",level:2},{value:"appConfig.auth.keycloak (optional)",id:"appconfigauthkeycloak-optional",level:2},{value:"appConfig.auth.headerAuth (optional)",id:"appconfigauthheaderauth-optional",level:2},{value:"appConfig.auth.oidc (optional)",id:"appconfigauthoidc-optional",level:2},{value:"appConfig.webSearch (optional)",id:"appconfigwebsearch-optional",level:2},{value:"appConfig.hideComponents (optional)",id:"appconfighidecomponents-optional",level:2},{value:"section",id:"section",level:2},{value:"section.item",id:"sectionitem",level:2},{value:"item.displayData (optional)",id:"itemdisplaydata-optional",level:2},{value:"section.widgets (optional)",id:"sectionwidgets-optional",level:2},{value:"section.displayData (optional)",id:"sectiondisplaydata-optional",level:2},{value:"section.icon and section.item.icon",id:"sectionicon-and-sectionitemicon",level:2},{value:"section.displayData.hideForKeycloakUsers, section.displayData.showForKeycloakUsers, item.displayData.hideForKeycloakUsers and item.displayData.showForKeycloakUsers",id:"sectiondisplaydatahideforkeycloakusers-sectiondisplaydatashowforkeycloakusers-itemdisplaydatahideforkeycloakusers-and-itemdisplaydatashowforkeycloakusers",level:2},{value:"Notes",id:"notes",level:2},{value:"Editing Config through the UI",id:"editing-config-through-the-ui",level:3},{value:"About YAML",id:"about-yaml",level:3},{value:"Config Saving Methods",id:"config-saving-methods",level:3},{value:"Preventing Changes",id:"preventing-changes",level:3},{value:"Example",id:"example",level:3}];function h(e){const n={a:"a",admonition:"admonition",b:"b",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(n.header,{children:(0,d.jsx)(n.h1,{id:"configuring",children:"Configuring"})}),"\n",(0,d.jsxs)(n.p,{children:["All app configuration is specified in ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/user-data/conf.yml",children:(0,d.jsx)(n.code,{children:"/user-data/conf.yml"})})," which is in ",(0,d.jsx)(n.a,{href:"https://yaml.org/",children:"YAML Format"})," format. If you're using Docker, this file can be passed in as a volume. Changes can either be made directly to this file, or done ",(0,d.jsx)(n.a,{href:"#editing-config-through-the-ui",children:"through the UI"}),". From the UI you can also export, backup, reset, validate and download your configuration file."]}),"\n",(0,d.jsx)(n.h2,{id:"there-are-three-ways-to-edit-the-config",children:"There are three ways to edit the config"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.strong,{children:"Directly in the YAML file"})," ",(0,d.jsx)(n.em,{children:"(5/5 reliability, 3/5 usability)"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:["Write changes directly to the conf.yml file, optionally using one of the templates provided. This can be done in your favorite editor and uploading to your server, or directly editing the file via SSH, but the easiest method would be to use ",(0,d.jsx)(n.a,{href:"https://github.com/coder/code-server",children:"Code Server"})]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.strong,{children:"UI JSON Editor"})," ",(0,d.jsx)(n.em,{children:"(4/5 reliability, 4/5 usability)"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsx)(n.li,{children:"From the UI, under the config menu there is a JSON editor, with built-in validation, documentation and advanced options"}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.strong,{children:"UI Visual Editor"})," ",(0,d.jsx)(n.em,{children:"(3/5 reliability, 5/5 usability)"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsx)(n.li,{children:"From the UI, enter the Interactive Edit Mode, then click any part of the page to edit. Changes are previewed live, and then saved to disk"}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.strong,{children:"REST API"})," ",(0,d.jsxs)(n.em,{children:["(see ",(0,d.jsx)(n.a,{href:"/docs/api",children:"API docs"}),")"]}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsx)(n.li,{children:"Programmatically edit config either through the command line, using a script or a third-party application"}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,d.jsx)(n.h2,{id:"tips",children:"Tips"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:["You may find it helpful to look at some sample config files to get you started, a collection of which can be found ",(0,d.jsx)(n.a,{href:"https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10",children:"here"})]}),"\n",(0,d.jsxs)(n.li,{children:["You can check that your config file fits the schema, by running ",(0,d.jsx)(n.code,{children:"yarn validate-config"})]}),"\n",(0,d.jsxs)(n.li,{children:["After modifying your config, the app needs to be recompiled, by running ",(0,d.jsx)(n.code,{children:"yarn build"})," - this happens automatically if you're using Docker"]}),"\n",(0,d.jsxs)(n.li,{children:["It is recommended to keep a backup of your config file. You can download it under Config menu, or use the ",(0,d.jsx)(n.a,{href:"/docs/backup-restore",children:"Cloud Backup"})," feature."]}),"\n",(0,d.jsx)(n.li,{children:"You can make use of YAML features, like anchors, comments, multi-line strings, etc to reuse attributes and keep your config file readable"}),"\n",(0,d.jsxs)(n.li,{children:["Once you have finished configuring your dashboard, you can choose to ",(0,d.jsx)(n.a,{href:"#preventing-changes",children:"disable UI config"})," if you wish"]}),"\n",(0,d.jsx)(n.li,{children:"All fields are optional, unless otherwise stated."}),"\n"]}),"\n",(0,d.jsx)(n.p,{children:"The following file provides a reference of all supported configuration options."}),"\n",(0,d.jsx)(n.h2,{id:"contents",children:"Contents"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#pageinfo",children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pageInfo"})})})," - Header text, footer, title, navigation, etc\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#pageinfonavlinks-optional",children:(0,d.jsx)(n.code,{children:"navLinks"})})," - Links to display in the navigation bar"]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#pages-optional",children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pages"})})})," - List of additional config files, for multi-page dashboards"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfig-optional",children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"appConfig"})})})," - Main application settings\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfigwebsearch-optional",children:(0,d.jsx)(n.code,{children:"webSearch"})})," - Configure web search engine options"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfighidecomponents-optional",children:(0,d.jsx)(n.code,{children:"hideComponents"})})," - Show/ hide page components"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfigauth-optional",children:(0,d.jsx)(n.code,{children:"auth"})})," - Built-in authentication setup\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfigauthusers-optional",children:(0,d.jsx)(n.code,{children:"users"})})," - List or users (for simple auth)"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfigauthkeycloak-optional",children:(0,d.jsx)(n.code,{children:"keycloak"})})," - Auth config for Keycloak"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfigauthheaderauth-optional",children:(0,d.jsx)(n.code,{children:"headerAuth"})})," - Auth config for HeaderAuth"]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#section",children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"sections"})})})," - List of sections\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectiondisplaydata-optional",children:(0,d.jsx)(n.code,{children:"displayData"})})," - Section display settings\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectiondisplaydatahideforkeycloakusers-sectiondisplaydatashowforkeycloakusers-itemdisplaydatahideforkeycloakusers-and-itemdisplaydatashowforkeycloakusers",children:(0,d.jsx)(n.code,{children:"show/hideForKeycloakUsers"})})," - Set user controls"]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectionicon-and-sectionitemicon",children:(0,d.jsx)(n.code,{children:"icon"})})," - Icon for a section"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectionitem",children:(0,d.jsx)(n.code,{children:"items"})})," - List of items\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectionicon-and-sectionitemicon",children:(0,d.jsx)(n.code,{children:"icon"})})," - Icon for an item"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#itemdisplaydata-optional",children:(0,d.jsx)(n.code,{children:"displayData"})})," - Item display settings\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectiondisplaydatahideforkeycloakusers-sectiondisplaydatashowforkeycloakusers-itemdisplaydatahideforkeycloakusers-and-itemdisplaydatashowforkeycloakusers",children:(0,d.jsx)(n.code,{children:"show/hideForKeycloakUsers"})})," - Set user controls"]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectionwidgets-optional",children:(0,d.jsx)(n.code,{children:"widgets"})})," - List of widgets"]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#notes",children:(0,d.jsx)(n.strong,{children:"Notes"})}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsx)(n.li,{children:(0,d.jsx)(n.a,{href:"#editing-config-through-the-ui",children:"Editing Config through the UI"})}),"\n",(0,d.jsx)(n.li,{children:(0,d.jsx)(n.a,{href:"#about-yaml",children:"About YAML"})}),"\n",(0,d.jsx)(n.li,{children:(0,d.jsx)(n.a,{href:"#config-saving-methods",children:"Config Saving Methods"})}),"\n",(0,d.jsx)(n.li,{children:(0,d.jsx)(n.a,{href:"#preventing-changes",children:"Preventing Changes"})}),"\n",(0,d.jsx)(n.li,{children:(0,d.jsx)(n.a,{href:"#example",children:"Example"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,d.jsx)(n.hr,{}),"\n",(0,d.jsx)(n.h2,{id:"top-level-fields",children:"Top-Level Fields"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pageInfo"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["Basic meta data like title, description, nav bar links, footer text. See ",(0,d.jsx)(n.a,{href:"#pageinfo",children:(0,d.jsx)(n.code,{children:"pageInfo"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"appConfig"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Settings related to how the app functions, including API keys and global styles. See ",(0,d.jsx)(n.a,{href:"#appconfig-optional",children:(0,d.jsx)(n.code,{children:"appConfig"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"sections"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["An array of sections, each containing an array of items, which will be displayed as links. See ",(0,d.jsx)(n.a,{href:"#section",children:(0,d.jsx)(n.code,{children:"section"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pages"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An array additional config files, used for multi-page dashboards. See ",(0,d.jsx)(n.a,{href:"#pages-optional",children:(0,d.jsx)(n.code,{children:"pages"})})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsx)(n.h2,{id:"pageinfo",children:(0,d.jsx)(n.code,{children:"PageInfo"})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"title"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"Your dashboard title, displayed in the header and browser tab"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"description"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Description of your dashboard, also displayed as a subtitle"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"navLinks"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Optional list of a maximum of 6 links, which will be displayed in the navigation bar. See ",(0,d.jsx)(n.a,{href:"#pageinfonavlinks-optional",children:(0,d.jsx)(n.code,{children:"navLinks"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"footer"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Text to display in the footer. When omitted, no footer is rendered. Supports inline HTML (sanitized before render)"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"logo"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The path to an image to display in the header (to the right of the title). This can be either local, where ",(0,d.jsx)(n.code,{children:"/"})," is the root of ",(0,d.jsx)(n.code,{children:"./public"}),", or any remote image, such as ",(0,d.jsx)(n.code,{children:"https://i.ibb.co/yhbt6CY/dashy.png"}),". It's recommended to scale your image down, so that it doesn't impact load times"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"favicon"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["URL or path to a custom favicon shown in the browser tab. Can be absolute (",(0,d.jsx)(n.code,{children:"https://..."}),"), root-relative (",(0,d.jsx)(n.code,{children:"/icons/x.png"}),"), or a ",(0,d.jsx)(n.code,{children:"data:"})," URI"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"color"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Theme colour applied to the browser chrome (mobile address bar). Any valid CSS color (e.g. ",(0,d.jsx)(n.code,{children:"#ff00a7"}),") is accepted"]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"pageinfonavlinks-optional",children:[(0,d.jsx)(n.code,{children:"pageInfo.navLinks"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"title"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The text to display on the link button"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"path"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["The URL to navigate to when clicked. Can be relative (e.g. ",(0,d.jsx)(n.code,{children:"/about"}),") or absolute (e.g. ",(0,d.jsx)(n.code,{children:"https://example.com"})," or ",(0,d.jsx)(n.code,{children:"http://192.168.1.1"}),")"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"target"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The opening method (external links only). Can be either ",(0,d.jsx)(n.code,{children:"newtab"}),", ",(0,d.jsx)(n.code,{children:"sametab"}),", ",(0,d.jsx)(n.code,{children:"newwindow"}),", ",(0,d.jsx)(n.code,{children:"top"})," or ",(0,d.jsx)(n.code,{children:"parent"}),". Defaults to ",(0,d.jsx)(n.code,{children:"newtab"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"pages-optional",children:[(0,d.jsx)(n.code,{children:"pages[]"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"name"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"A unique name for that page"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"path"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["The path (local or remote) to the config file to use.",(0,d.jsx)(n.br,{}),"For files located within ",(0,d.jsx)(n.code,{children:"/user-data"}),", you only need to specify filename, for externally hosted files you must include the full URL"]})]})]})]}),"\n",(0,d.jsxs)(n.p,{children:["For more info, see the",(0,d.jsx)(n.a,{href:"/docs/pages-and-sections#multi-page-support",children:"Multi-Page docs"})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfig-optional",children:[(0,d.jsx)(n.code,{children:"appConfig"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"language"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The 2 (or 4-digit) ",(0,d.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes",children:"ISO 639-1 code"})," for your language, e.g. ",(0,d.jsx)(n.code,{children:"en"})," or ",(0,d.jsx)(n.code,{children:"en-GB"}),". This must be a language that the app has already been ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/src/assets/locales",children:"translated"})," into. If your language is unavailable, Dashy will fallback to English. By default Dashy will attempt to auto-detect your language, although this may not work on some privacy browsers."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"startingView"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"enum"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Which view to land on when visiting ",(0,d.jsx)(n.code,{children:"/"}),". One of ",(0,d.jsx)(n.code,{children:"home"}),", ",(0,d.jsx)(n.code,{children:"minimal"})," or ",(0,d.jsx)(n.code,{children:"workspace"}),". Defaults to ",(0,d.jsx)(n.code,{children:"home"}),". Applied at runtime, so no rebuild is needed. You can always switch views from the UI. (Legacy value ",(0,d.jsx)(n.code,{children:"default"})," is accepted as an alias for ",(0,d.jsx)(n.code,{children:"home"}),".)"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"defaultOpeningMethod"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"enum"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The default opening method for items, if no ",(0,d.jsx)(n.code,{children:"target"})," is specified for a given item. Can be either ",(0,d.jsx)(n.code,{children:"newtab"}),", ",(0,d.jsx)(n.code,{children:"sametab"}),", ",(0,d.jsx)(n.code,{children:"modal"}),", ",(0,d.jsx)(n.code,{children:"workspace"}),", ",(0,d.jsx)(n.code,{children:"clipboard"}),", ",(0,d.jsx)(n.code,{children:"top"})," or ",(0,d.jsx)(n.code,{children:"parent"}),". Defaults to ",(0,d.jsx)(n.code,{children:"newtab"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheck"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", Dashy will ping each of your services and display their status as a dot next to each item. This can be overridden by setting ",(0,d.jsx)(n.code,{children:"statusCheck"})," under each item. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckInterval"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of seconds between checks. If set to ",(0,d.jsx)(n.code,{children:"0"})," then service will only be checked on initial page load, which is usually the desired functionality. If value is less than ",(0,d.jsx)(n.code,{children:"10"})," you may experience a hit in performance. Defaults to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckAccessibility"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", status indicators will use distinct shapes to indicate status for color-blind users. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckEnabled"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", Dashy will make a real ICMP ping to each of your hosts and display their status as a dot next to each item. This can be overridden by setting ",(0,d.jsx)(n.code,{children:"pingCheckEnabled"})," under each item. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckInterval"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of seconds between checks. If set to ",(0,d.jsx)(n.code,{children:"0"})," then service will only be checked on initial page load. The value is limited to ",(0,d.jsx)(n.code,{children:"5"})," seconds min to prevent performance issues. Defaults to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckAccessibility"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", status indicators will use distinct shapes to indicate status for color-blind users. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckCount"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of ping ICMP packets sent to the host per check. This can be overridden by setting ",(0,d.jsx)(n.code,{children:"pingCheckCount"})," under each item. If set to ",(0,d.jsx)(n.code,{children:"0"})," then ",(0,d.jsx)(n.code,{children:"3"})," packets will be sent. The value is limited to ",(0,d.jsx)(n.code,{children:"5"})," packets max to prevent performance issues. Defaults to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckTimeout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of milliseconds to wait for a ping to complete. This can be overriden by setting ",(0,d.jsx)(n.code,{children:"pingCheckTimeout"})," under each item. If set to ",(0,d.jsx)(n.code,{children:"0"})," then it uses the same value as ",(0,d.jsx)(n.code,{children:"pingCheckInterval"})," but converted in milliseconds. The value is limited to 1000 ms per ",(0,d.jsx)(n.code,{children:"pingCheckCount"})," to prevent performance issues. Defaults to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"webSearch"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Configuration options for the web search feature, set your default search engine, opening method or disable web search. See ",(0,d.jsx)(n.a,{href:"#appconfigwebsearch-optional",children:(0,d.jsx)(n.code,{children:"webSearch"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"backgroundImg"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Path to an optional full-screen app background image. This can be either remote (http) or local (relative to /app/public/item-icons/ inside the container). Note that this will slow down initial load"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableFontAwesome"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"})," font-awesome will be loaded, if set to ",(0,d.jsx)(n.code,{children:"false"})," they will not be. if left blank font-awesome will be enabled only if required by 1 or more icons"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableMaterialDesignIcons"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"})," mdi icons will be loaded, if set to ",(0,d.jsx)(n.code,{children:"false"})," they will not be. Where ",(0,d.jsx)(n.code,{children:"true"})," is enabled, if left blank material design icons will be enabled only if required by 1 or more icons"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"fontAwesomeKey"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If you have a font-awesome key, then you can use it here and make use of premium icons. It is a 10-digit alpha-numeric string from you're FA kit URL (e.g. ",(0,d.jsx)(n.code,{children:"13014ae648"}),")"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"faviconApi"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"enum"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Only applicable if you are using favicons for item icons. Specifies which service to use to resolve favicons. Set to ",(0,d.jsx)(n.code,{children:"local"})," to do this locally, without using an API. Services running locally will use this option always. Available options are: ",(0,d.jsx)(n.code,{children:"local"}),", ",(0,d.jsx)(n.code,{children:"allesedv"}),", ",(0,d.jsx)(n.code,{children:"iconhorse"}),", ",(0,d.jsx)(n.code,{children:"faviconkit"}),", ",(0,d.jsx)(n.code,{children:"duckduckgo"}),", ",(0,d.jsx)(n.code,{children:"yandex"}),", ",(0,d.jsx)(n.code,{children:"google"}),", ",(0,d.jsx)(n.code,{children:"besticon"}),", ",(0,d.jsx)(n.code,{children:"webmasterapi"})," and ",(0,d.jsx)(n.code,{children:"mcapi"}),". Defaults to ",(0,d.jsx)(n.code,{children:"allesedv"}),". See ",(0,d.jsx)(n.a,{href:"/docs/icons#favicons",children:"Icons"})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"auth"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["All settings relating to user authentication. See ",(0,d.jsx)(n.a,{href:"#appconfigauth-optional",children:(0,d.jsx)(n.code,{children:"auth"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"defaultIcon"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An icon to be applied to any items, which don't already have an icon set. See ",(0,d.jsx)(n.a,{href:"/docs/icons#default-icon",children:"Icon Docs"})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"layout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"enum"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Layout for homepage, one of ",(0,d.jsx)(n.code,{children:"auto"}),", ",(0,d.jsx)(n.code,{children:"horizontal"}),", ",(0,d.jsx)(n.code,{children:"vertical"})," or ",(0,d.jsx)(n.code,{children:"masonry"}),". Defaults to ",(0,d.jsx)(n.code,{children:"auto"}),". Specifies the layout and direction of how sections are positioned on the home screen. ",(0,d.jsx)(n.code,{children:"auto"})," uses a responsive CSS grid where each section's footprint is controlled by ",(0,d.jsx)(n.code,{children:"displayData.cols"})," and ",(0,d.jsx)(n.code,{children:"displayData.rows"}),". ",(0,d.jsx)(n.code,{children:"masonry"})," uses a responsive grid where heights follow content, so shorter sections flow into the gaps left by taller neighbours (",(0,d.jsx)(n.code,{children:"rows"})," is ignored, ",(0,d.jsx)(n.code,{children:"cols"})," still controls width). This can also be modified and overridden from the UI."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"iconSize"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"enum"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The size of link items / icons. Can be either ",(0,d.jsx)(n.code,{children:"small"}),", ",(0,d.jsx)(n.code,{children:"medium,"})," or ",(0,d.jsx)(n.code,{children:"large"}),". Defaults to ",(0,d.jsx)(n.code,{children:"medium"}),". This can also be set directly from the UI."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"colCount"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of columns of sections displayed on the homepage, using the default view. Should be in integer between ",(0,d.jsx)(n.code,{children:"1"})," and ",(0,d.jsx)(n.code,{children:"8"}),". Note that by default this is applied responsively, based on current screen size, and specifying a value here will override this behavior, which may not be desirable."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"contentMaxWidth"})})}),(0,d.jsxs)(n.td,{children:[(0,d.jsx)(n.code,{children:"string"})," or ",(0,d.jsx)(n.code,{children:"number"})]}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Sets the max width of the main sections area on the homepage, overriding the responsive default. Can be a percentage, or any CSS unit"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"theme"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The default theme for first load (you can change this later from the UI)"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"dayTheme"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Theme to apply when the OS is set to light mode. Overrides ",(0,d.jsx)(n.code,{children:"theme"})," on initial load"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"nightTheme"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Theme to apply when the OS is set to dark mode. Overrides ",(0,d.jsx)(n.code,{children:"theme"})," on initial load"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"cssThemes"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"An array of custom theme names which can be used in the theme switcher dropdown"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"customColors"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Enables you to apply a custom color palette to any given theme. Use the theme name (lowercase) as the key, for an object including key-value-pairs, with the color variable name as keys, and 6-digit hex code as value. See ",(0,d.jsx)(n.a,{href:"/docs/theming#modifying-theme-colors",children:"Theming"})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"externalStyleSheet"})})}),(0,d.jsxs)(n.td,{children:[(0,d.jsx)(n.code,{children:"string"})," or ",(0,d.jsx)(n.code,{children:"string[]"})]}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Either a URL to an external stylesheet or an array or URLs, which can be applied as themes within the UI"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"customCss"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Raw CSS that will be applied to the page. This can also be set from the UI. Please minify it first."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideComponents"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["A list of key page components (header, nav, search, settings) that are present by default, but can be removed using this option. See ",(0,d.jsx)(n.a,{href:"#appconfighidecomponents-optional",children:(0,d.jsx)(n.code,{children:"appConfig.hideComponents"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableMultiTasking"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"If set to true, will keep apps open in the background when in the workspace view. Useful for quickly switching between multiple sites, and preserving their state, but comes at the cost of performance."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"workspaceLandingUrl"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The URL or an app, service or website to launch when the workspace view is opened, before another service has been launched"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"preventWriteToDisk"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", users will be prevented from saving config changes to disk through the UI"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"preventLocalSave"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", users will be prevented from applying config changes to local storage"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableConfiguration"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"If set to true, no users will be able to view or edit the config through the UI"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableConfigurationForNonAdmin"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"If set to true, only admin users will be able to view or edit the config through the UI. disableConfiguration must not be set to true."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"widgetsAlwaysUseProxy"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", requests made by widgets will always be proxied, same as setting ",(0,d.jsx)(n.code,{children:"useProxy: true"})," on each widget. Note that this may break some widgets."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"showSplashScreen"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", a loading screen will be shown. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableErrorReporting"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Enable reporting of unexpected errors and crashes. This is off by default, and ",(0,d.jsx)(n.strong,{children:"no data will ever be captured unless you explicitly enable it"}),". Turning on error reporting helps previously unknown bugs get discovered and fixed. Dashy uses ",(0,d.jsx)(n.a,{href:"https://github.com/getsentry/sentry",children:"Sentry"})," for error reporting. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"sentryDsn"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If you need to monitor errors in your instance, then you can use Sentry to collect and process bug reports. Sentry can be self-hosted, or used as SaaS, once your instance is setup, then all you need to do is pass in the DSN here, and enable error reporting. You can learn more on the ",(0,d.jsx)(n.a,{href:"https://docs.sentry.io/product/sentry-basics/dsn-explainer/",children:"Sentry DSN Docs"}),". Note that this will only ever be used if ",(0,d.jsx)(n.code,{children:"enableErrorReporting"})," is explicitly enabled."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableSmartSort"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["For the most-used and last-used app sort functions to work, a basic open-count is stored in local storage. If you do not want this to happen, then disable smart sort here, but you wil no longer be able to use these sort options. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableUpdateChecks"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to true, Dashy will not check for updates. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableServiceWorker"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Service workers cache web applications to improve load times and offer basic offline functionality, and are disabled by default in Dashy. The service worker can sometimes cause older content to be cached, requiring the app to be hard-refreshed. If you do not want SW functionality, or are having issues with caching, set this property to ",(0,d.jsx)(n.code,{children:"false"})," to disable all service workers."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableAuthProxyCompat"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Only relevant when ",(0,d.jsx)(n.code,{children:"enableServiceWorker"})," is ",(0,d.jsx)(n.code,{children:"true"})," and when Dashy sits behind an authentication proxy (such as Cloudflare Zero Trust or Authelia). Set this to ",(0,d.jsx)(n.code,{children:"true"})," to detect that redirect on load, unregister the service worker, and reload so re-authentication can proceed. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableContextMenu"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", the custom right-click context menu will be disabled. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigauth-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.auth"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n",(0,d.jsx)(n.admonition,{type:"note",children:(0,d.jsxs)(n.p,{children:["Since the auth is initiated in the main app entry point (for security), a rebuild is required to apply changes to the auth configuration.\nRun ",(0,d.jsx)(n.code,{children:"yarn build"})," in the root directory, then restart the server."]})}),"\n",(0,d.jsx)(n.admonition,{type:"warning",children:(0,d.jsxs)(n.p,{children:["Built-in auth should ",(0,d.jsx)(n.strong,{children:"not be used"})," for security-critical applications, or if your Dashy instance is publicly accessible.\nFor these, it is recommended to use an ",(0,d.jsx)(n.a,{href:"/docs/authentication#alternative-authentication-methods",children:"alternate authentication method"}),"."]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"users"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An array of objects containing usernames and hashed passwords. If this is not provided, then authentication will be off by default, and you will not need any credentials to access the app. See ",(0,d.jsx)(n.a,{href:"#appconfigauthusers-optional",children:(0,d.jsx)(n.code,{children:"appConfig.auth.users"})}),". ",(0,d.jsx)(n.br,{}),(0,d.jsx)(n.strong,{children:"Note"})," this method of authentication is handled on the client side, so for security critical situations, it is recommended to use an ",(0,d.jsx)(n.a,{href:"/docs/authentication#alternative-authentication-methods",children:"alternate authentication method"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableKeycloak"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", then authentication using Keycloak will be enabled. Note that you need to have an instance running, and have also configured ",(0,d.jsx)(n.code,{children:"auth.keycloak"}),". Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"keycloak"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Config options to point Dashy to your Keycloak server. Requires ",(0,d.jsx)(n.code,{children:"enableKeycloak: true"}),". See ",(0,d.jsx)(n.a,{href:"#appconfigauthkeycloak-optional",children:(0,d.jsx)(n.code,{children:"auth.keycloak"})})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableHeaderAuth"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", then authentication using HeaderAuth will be enabled. Note that you need to have your web server/reverse proxy running, and have also configured ",(0,d.jsx)(n.code,{children:"auth.headerAuth"}),". Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"headerAuth"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Config options to point Dashy to your headers for authentication. Requires ",(0,d.jsx)(n.code,{children:"enableHeaderAuth: true"}),". See ",(0,d.jsx)(n.a,{href:"#appconfigauthheaderauth-optional",children:(0,d.jsx)(n.code,{children:"auth.headerAuth"})})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableOidc"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", then authentication using OIDC will be enabled. Note that you need to have a configured OIDC server and configure it with ",(0,d.jsx)(n.code,{children:"auth.oidc"}),". Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"oidc"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Config options to point Dash to your OIDC configuration. Request ",(0,d.jsx)(n.code,{children:"enableOidc: true"}),". See ",(0,d.jsx)(n.a,{href:"#appconfigauthoidc-optional",children:(0,d.jsx)(n.code,{children:"auth.oidc"})})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableGuestAccess"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", an unauthenticated user will be able to access the dashboard, with read-only access, without having to login. Requires ",(0,d.jsx)(n.code,{children:"auth.users"})," to be configured. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]})]})]}),"\n",(0,d.jsxs)(n.p,{children:["For more info, see the ",(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"/docs/authentication",children:"Authentication Docs"})})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigauthusers-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.auth.users"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"user"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"Username to log in with"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hash"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"A SHA-256 hashed password"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"type"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The user type, either admin or normal"})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigauthkeycloak-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.auth.keycloak"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"serverUrl"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The URL (or URL/ IP + Port) where your keycloak server is running"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"realm"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The name of the realm (must already be created) that you want to use"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"clientId"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The Client ID of the client you created for use with Dashy"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"idpHint"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The alias of an Identity Provider configured in your realm. If set, Keycloak will skip its login page and redirect straight to that external IdP"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"legacySupport"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If using Keycloak 17 or older, then set this to ",(0,d.jsx)(n.code,{children:"true"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigauthheaderauth-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.auth.headerAuth"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"userHeader"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The HTTP header name containing the authenticated username (default: ",(0,d.jsx)(n.code,{children:"Remote-User"}),"). Case insensitive"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"proxyWhitelist"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"An array of upstream proxy server IPs to accept authenticated requests from"})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigauthoidc-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.auth.oidc"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"clientId"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The client id registered in the OIDC server"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"endpoint"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The URL of the OIDC server that should be used."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"adminRole"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The role that will be considered as admin."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"adminGroup"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The group that will be considered as admin."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"scope"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The scope(s) to request from the OIDC provider"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableSilentRenew"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", your session is silently renewed in the background before it expires (only works for providers which support the ",(0,d.jsx)(n.code,{children:"offline_access"})," scope)"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"allowedIssuers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["List of issuer URLs to accept tokens from. Needed for multi-tenant providers (e.g. Microsoft Entra) where the token issuer differs from the configured ",(0,d.jsx)(n.code,{children:"endpoint"}),". If unset, the issuer from the discovery document is used"]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigwebsearch-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.webSearch"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableWebSearch"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Web search is enabled by default, but can be disabled by setting this property to ",(0,d.jsx)(n.code,{children:"true"}),". Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"searchEngine"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Set the key name for your search engine. Can also use a custom engine by setting this property to ",(0,d.jsx)(n.code,{children:"custom"}),". See the ",(0,d.jsx)(n.a,{href:"/docs/searching#setting-search-engine",children:"searching docs"})," for the full list of built-in keys. Defaults to ",(0,d.jsx)(n.code,{children:"duckduckgo"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"customSearchEngine"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["You can also use a custom search engine, or your own self-hosted instance. This requires ",(0,d.jsx)(n.code,{children:"searchEngine: custom"})," to be set. Then add the URL of your service, with GET query string included here"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"openingMethod"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Set your preferred opening method for search results: ",(0,d.jsx)(n.code,{children:"newtab"}),", ",(0,d.jsx)(n.code,{children:"sametab"}),", ",(0,d.jsx)(n.code,{children:"workspace"}),". Defaults to ",(0,d.jsx)(n.code,{children:"newtab"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"searchBangs"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["A key-value-pair set of custom search ",(0,d.jsx)(n.em,{children:"bangs"})," for redirecting query to a specific app or search engine. The key of each should be the bang you will type (typically starting with ",(0,d.jsx)(n.code,{children:"/"}),", ",(0,d.jsx)(n.code,{children:"!"})," or ",(0,d.jsx)(n.code,{children:":"}),"), and value is the destination, either as a search engine key (e.g. ",(0,d.jsx)(n.code,{children:"reddit"}),") or a URL with search parameters (e.g. ",(0,d.jsx)(n.code,{children:"https://en.wikipedia.org/w/?search="}),")"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"openUrlsDirectly"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If ",(0,d.jsx)(n.code,{children:"true"}),", queries that look like URLs will be opened directly instead of searched. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfighidecomponents-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.hideComponents"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideHeading"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", the page title & sub-title will not be visible. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideNav"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", the navigation menu will not be visible. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideSearch"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", the search bar will not be visible. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideSettings"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", the settings menu will be initially collapsed. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsx)(n.h2,{id:"section",children:(0,d.jsx)(n.code,{children:"section"})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"name"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The title for the section"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"icon"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An single icon to be displayed next to the title. See ",(0,d.jsx)(n.a,{href:"#sectionicon-and-sectionitemicon",children:(0,d.jsx)(n.code,{children:"section.icon"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"items"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An array of items to be displayed within the section. See ",(0,d.jsx)(n.a,{href:"#sectionitem",children:(0,d.jsx)(n.code,{children:"item"})}),". Sections must include either 1 or more items, or 1 or more widgets."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"widgets"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An array of widgets to be displayed within the section. See ",(0,d.jsx)(n.a,{href:"#sectionwidgets-optional",children:(0,d.jsx)(n.code,{children:"widget"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"displayData"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Meta-data to optionally override display settings for a given section. See ",(0,d.jsx)(n.a,{href:"#sectiondisplaydata-optional",children:(0,d.jsx)(n.code,{children:"displayData"})})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsx)(n.h2,{id:"sectionitem",children:(0,d.jsx)(n.code,{children:"section.item"})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"title"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["The text to display/ title of a given item. Max length ",(0,d.jsx)(n.code,{children:"18"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"description"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Additional info about an item, which is shown in the tooltip on hover, or visible on large tiles"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"url"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The URL / location of web address for when the item is clicked"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"localUrl"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"An alternative URL (e.g. a LAN address) that is preferred whenever it is reachable from your browser"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"localUrlTimeout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Milliseconds to wait for the ",(0,d.jsx)(n.code,{children:"localUrl"})," reachability probe before giving up and using ",(0,d.jsx)(n.code,{children:"url"}),". Between ",(0,d.jsx)(n.code,{children:"300"})," and ",(0,d.jsx)(n.code,{children:"5000"}),". Defaults to ",(0,d.jsx)(n.code,{children:"1500"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"localUrlCheckInterval"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Seconds between background re-checks of ",(0,d.jsx)(n.code,{children:"localUrl"}),". ",(0,d.jsx)(n.code,{children:"0"})," means only check on page load and when the browser tab regains focus. ",(0,d.jsx)(n.code,{children:"300"})," max. Defaults to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"icon"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The icon for a given item. Can be a font-awesome icon, favicon, remote URL or local URL. See ",(0,d.jsx)(n.a,{href:"#sectionicon-and-sectionitemicon",children:(0,d.jsx)(n.code,{children:"item.icon"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"target"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The opening method for when the item is clicked, either ",(0,d.jsx)(n.code,{children:"newtab"}),", ",(0,d.jsx)(n.code,{children:"sametab"}),", ",(0,d.jsx)(n.code,{children:"modal"}),", ",(0,d.jsx)(n.code,{children:"workspace"}),", ",(0,d.jsx)(n.code,{children:"clipboard"}),", ",(0,d.jsx)(n.code,{children:"top"})," or ",(0,d.jsx)(n.code,{children:"parent"}),". Where ",(0,d.jsx)(n.code,{children:"newtab"})," will open the link in a new tab, ",(0,d.jsx)(n.code,{children:"sametab"})," will open it in the current tab, and ",(0,d.jsx)(n.code,{children:"modal"})," will open a pop-up modal, ",(0,d.jsx)(n.code,{children:"workspace"})," will open in the Workspace view and ",(0,d.jsx)(n.code,{children:"clipboard"})," will copy the URL to system clipboard (but not launch app). Defaults to ",(0,d.jsx)(n.code,{children:"newtab"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hotkey"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Give frequently opened applications a numeric hotkey, between ",(0,d.jsx)(n.code,{children:"0 - 9"}),". You can then just press that key to launch that application."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"tags"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"A list of tags, which can be used for improved search"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheck"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", Dashy will ping the URL associated with the current service, and display its status as a dot next to the item. The value here will override ",(0,d.jsx)(n.code,{children:"appConfig.statusCheck"})," so you can turn off or on checks for a given service. Defaults to ",(0,d.jsx)(n.code,{children:"appConfig.statusCheck"}),", falls back to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckUrl"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If you've enabled ",(0,d.jsx)(n.code,{children:"statusCheck"}),", and want to use a different URL to what is defined under the item, then specify it here"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckHeaders"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"If you're endpoint requires any specific headers for the status checking, then define them here"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckAllowInsecure"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["By default, any request to insecure content will be blocked. Setting this option to ",(0,d.jsx)(n.code,{children:"true"})," will disable the ",(0,d.jsx)(n.code,{children:"rejectUnauthorized"})," option, enabling you to ping non-HTTPS services for the current item. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckAcceptCodes"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If your service's response code is anything other than 2xx, then you can opt to specify an alternative success code. E.g. if you expect your server to return 403, but still want the status indicator to be green, set this value to ",(0,d.jsx)(n.code,{children:"403"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckMaxRedirects"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If your service redirects to another page, and you would like status checks to follow redirects, then specify the maximum number of redirects here. Defaults to ",(0,d.jsx)(n.code,{children:"0"})," / will not follow redirects"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckEnabled"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", Dashy will make a real ICMP ping to each of your hosts and display their status as a dot next to each item. The value here will override ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckEnabled"})," so you can turn off or on pings for a given host. Defaults to ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckEnabled"}),", falls back to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckHost"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["FQDN hostname or IPV4 address of the host to ping. If empty or not defined then it uses the item's url host and if it is also invalid then no ping is performed. Defaults to ",(0,d.jsx)(n.code,{children:"undefined"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckInterval"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of seconds between checks. The value will override ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckInterval"})," so you can adjust the ping interval per host. Defaults to ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckInterval"}),", falls back to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckCount"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of ping ICMP packets sent to the host per check. The value will override ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckCount"})," so you can decide to send a different number of ping ICMP packets per host. Defaults to ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckCount"}),", falls back to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckTimeout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of milliseconds to wait for a ping to complete. The value will override ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckTimeout"})," so you can adjust the timeout per host. Defaults to ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckTimeout"}),", falls back to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"color"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"An optional color for the text and font-awesome icon to be displayed in. Note that this will override the current theme and so may not display well"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"rel"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The value of the ",(0,d.jsx)(n.code,{children:"rel"})," attribute for the link. Useful for specifying the relationship between the target link/document and Dashy. Defaults to ",(0,d.jsx)(n.code,{children:"noopener noreferrer"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"backgroundColor"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"An optional background fill color for the that given item. Again, this will override the current theme and so might not display well against the background"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"provider"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The name of the provider for a given service, useful for when including hosted apps. In some themes, this is visible under the item name"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"displayData"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Meta-data to optionally override display settings for a given item. See ",(0,d.jsx)(n.a,{href:"#itemdisplaydata-optional",children:(0,d.jsx)(n.code,{children:"displayData"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"subItems"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An optional list of nested sub-items, rendered as smaller icons within the parent. Each sub-item supports ",(0,d.jsx)(n.code,{children:"title"}),", ",(0,d.jsx)(n.code,{children:"url"}),", ",(0,d.jsx)(n.code,{children:"icon"}),", ",(0,d.jsx)(n.code,{children:"target"}),", ",(0,d.jsx)(n.code,{children:"color"})," and ",(0,d.jsx)(n.code,{children:"backgroundColor"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"itemdisplaydata-optional",children:[(0,d.jsx)(n.code,{children:"item.displayData"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current item will be visible to all users, except for those specified in this list"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"showForUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current item will be hidden from all users, except for those specified in this list"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForGuests"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current item will be visible for logged in users, but not for guests (see ",(0,d.jsx)(n.code,{children:"appConfig.enableGuestAccess"}),"). Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForKeycloakUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current item will be visible to all keycloak users, except for those configured via these groups and roles. See ",(0,d.jsx)(n.code,{children:"hideForKeycloakUsers"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"showForKeycloakUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current item will be hidden from all keycloak users, except for those configured via these groups and roles. See ",(0,d.jsx)(n.code,{children:"showForKeycloakUsers"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideFromWorkspace"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current item will be visible in the default view but not in the Workspace view sidebar. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideFromHomepage"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If ",(0,d.jsx)(n.code,{children:"true"}),", item is hidden from the home and minimal views until matched by a search. Still visible in workspace, edit mode and single-section view. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"sectionwidgets-optional",children:[(0,d.jsx)(n.code,{children:"section.widgets"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"type"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["The widget type. See ",(0,d.jsx)(n.a,{href:"/docs/widgets",children:"Widget Docs"})," for full list of supported widgets"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"options"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Some widgets accept either optional or required additional options. Again, see the ",(0,d.jsx)(n.a,{href:"/docs/widgets",children:"Widget Docs"})," for full list of options"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"updateInterval"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["You can keep a widget constantly updated by specifying an update interval, in seconds. See ",(0,d.jsx)(n.a,{href:"/docs/widgets#continuous-updates",children:"Continuous Updates Docs"})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"useProxy"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Some widgets make API requests to services that are not CORS-enabled. For these instances, you will need to route requests through a proxy, Dashy has a built in CORS-proxy, which you can use by setting this option to ",(0,d.jsx)(n.code,{children:"true"}),". Defaults to ",(0,d.jsx)(n.code,{children:"false"}),". See the ",(0,d.jsx)(n.a,{href:"/docs/widgets#proxying-requests",children:"Proxying Requests Docs"})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"timeout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Request timeout in milliseconds, defaults to \xbd a second (",(0,d.jsx)(n.code,{children:"500"}),")"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"ignoreErrors"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Prevent an error message being displayed, if a network request or something else fails. Useful for false-positives"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"label"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Add custom label to a given widget. Useful for identification, if there are multiple of the same type of widget in a single section"})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"sectiondisplaydata-optional",children:[(0,d.jsx)(n.code,{children:"section.displayData"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"sortBy"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The sort order for items within the current section. By default items are displayed in the order in which they are listed in within the config. The following sort options are supported: ",(0,d.jsx)(n.code,{children:"most-used"})," (most opened apps first), ",(0,d.jsx)(n.code,{children:"last-used"})," (the most recently used apps), ",(0,d.jsx)(n.code,{children:"alphabetical"}),", ",(0,d.jsx)(n.code,{children:"reverse-alphabetical"}),", ",(0,d.jsx)(n.code,{children:"random"})," and ",(0,d.jsx)(n.code,{children:"default"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"collapsed"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If true, the section will be collapsed initially, and will need to be clicked to open. Useful for less regularly used, or very long sections. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"cutToHeight"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"By default, sections will fill available space. Set this option to true to match section height with content height"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"rows"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Height of the section, specified as the number of rows it should span vertically, e.g. ",(0,d.jsx)(n.code,{children:"2"}),". Defaults to ",(0,d.jsx)(n.code,{children:"1"}),". Max is ",(0,d.jsx)(n.code,{children:"5"}),". Applies to the default ",(0,d.jsx)(n.code,{children:"auto"})," layout; ignored by ",(0,d.jsx)(n.code,{children:"masonry"})," (where heights follow content) and the flex-based ",(0,d.jsx)(n.code,{children:"horizontal"}),"/",(0,d.jsx)(n.code,{children:"vertical"})," layouts."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"cols"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Width of the section, specified as the number of columns the section should span horizontally, e.g. ",(0,d.jsx)(n.code,{children:"2"}),". Defaults to ",(0,d.jsx)(n.code,{children:"1"}),". Max is ",(0,d.jsx)(n.code,{children:"5"}),". Will be clamped to the page's active column count so that a section never exceeds the available grid width."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"itemSize"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Specify the size for items within this group, either ",(0,d.jsx)(n.code,{children:"small"}),", ",(0,d.jsx)(n.code,{children:"medium"})," or ",(0,d.jsx)(n.code,{children:"large"}),". Note that this will override any settings specified through the UI"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"color"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["A custom accent color for the section, as a hex code or HTML color (e.g. ",(0,d.jsx)(n.code,{children:"#fff"}),")"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"customStyles"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Custom CSS properties that should be applied to that section, e.g. ",(0,d.jsx)(n.code,{children:"border: 2px dashed #ff0000;"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"sectionLayout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Specify which CSS layout will be used to responsively place items. Can be either ",(0,d.jsx)(n.code,{children:"auto"})," (which uses flex layout), or ",(0,d.jsx)(n.code,{children:"grid"}),". Defaults to ",(0,d.jsx)(n.code,{children:"auto"}),". Setting ",(0,d.jsx)(n.code,{children:"itemCountX"})," or ",(0,d.jsx)(n.code,{children:"itemCountY"})," below will also switch the section to grid layout automatically"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"itemCountX"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Number of items per row / horizontally. If not set, it will be calculated automatically based on available space. Setting this switches the section to grid layout. Must be a whole number between ",(0,d.jsx)(n.code,{children:"1"})," and ",(0,d.jsx)(n.code,{children:"12"}),"; values above ",(0,d.jsx)(n.code,{children:"8"})," rely on the grid's responsive column sizing (no explicit minimum-width rule)"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"itemCountY"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Number of explicit rows before items flow into implicit rows. Setting this switches the section to grid layout. Must be a whole number between ",(0,d.jsx)(n.code,{children:"1"})," and ",(0,d.jsx)(n.code,{children:"12"}),". Row heights size to their content (section heights follow content in the masonry layout)"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current section will be visible to all users, except for those specified in this list"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"showForUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current section will be hidden from all users, except for those specified in this list"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForGuests"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current section will be visible for logged in users, but not for guests (see ",(0,d.jsx)(n.code,{children:"appConfig.enableGuestAccess"}),"). Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForKeycloakUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current section will be visible to all keycloak users, except for those configured via these groups and roles. See ",(0,d.jsx)(n.code,{children:"hideForKeycloakUsers"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"showForKeycloakUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current section will be hidden from all keycloak users, except for those configured via these groups and roles. See ",(0,d.jsx)(n.code,{children:"showForKeycloakUsers"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideFromWorkspace"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current section will be visible in the default view but not in the Workspace view sidebar. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"sectionicon-and-sectionitemicon",children:[(0,d.jsx)(n.code,{children:"section.icon"})," and ",(0,d.jsx)(n.code,{children:"section.item.icon"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsx)(n.tbody,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"icon"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The icon for a given item or section. ",(0,d.jsx)(n.br,{}),"See ",(0,d.jsx)(n.a,{href:"/docs/icons",children:"Icon Docs"})," for all available supported icon types, including: auto-fetched favicons, generative icons, emoji icons, home-lab service logos, font-awesome, simple-icons, material icons, selfh.st icons, and icons specified by URL"]})]})})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"sectiondisplaydatahideforkeycloakusers-sectiondisplaydatashowforkeycloakusers-itemdisplaydatahideforkeycloakusers-and-itemdisplaydatashowforkeycloakusers",children:[(0,d.jsx)(n.code,{children:"section.displayData.hideForKeycloakUsers"}),", ",(0,d.jsx)(n.code,{children:"section.displayData.showForKeycloakUsers"}),", ",(0,d.jsx)(n.code,{children:"item.displayData.hideForKeycloakUsers"})," and ",(0,d.jsx)(n.code,{children:"item.displayData.showForKeycloakUsers"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"groups"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current Section or Item will be hidden or shown based on the user having any of the groups in this list"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"roles"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current Section or Item will be hidden or shown based on the user having any of the roles in this list"})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsx)(n.hr,{}),"\n",(0,d.jsx)(n.h2,{id:"notes",children:"Notes"}),"\n",(0,d.jsx)(n.h3,{id:"editing-config-through-the-ui",children:"Editing Config through the UI"}),"\n",(0,d.jsxs)(n.p,{children:["Config can be modified directly through the UI, and then written to disk, or applied locally. This can be done wither with the raw config editor (introduced in V 0.6.5 / ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/pull/3",children:"#3"}),"), or the interactive editor (introduced in V 1.8.9 / ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/pull/298",children:"#298"}),")."]}),"\n ",(0,d.jsxs)(n.a,{href:"https://ibb.co/CzkyMNb",children:["\n ",(0,d.jsx)(n.b,{children:"Interactive Editor"}),(0,d.jsx)(n.br,{}),"\n ",(0,d.jsx)(n.img,{alt:"Interactive Editor demo",src:"https://user-images.githubusercontent.com/1862727/139543020-b0576d28-0830-476f-afc8-a815d4de6def.gif",width:"600"}),"\n "]}),"\n ",(0,d.jsx)(n.br,{}),"\n ",(0,d.jsxs)(n.a,{href:"https://ibb.co/zRv542H",children:["\n ",(0,d.jsx)(n.b,{children:"JSON Editor"}),(0,d.jsx)(n.br,{}),"\n ",(0,d.jsx)(n.img,{alt:"Config Editor demo",src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/assets/config-editor-demo.gif",width:"600"}),"\n "]}),"\n",(0,d.jsx)(n.h3,{id:"about-yaml",children:"About YAML"}),"\n",(0,d.jsxs)(n.p,{children:["If you're new to YAML, it's pretty straight-forward. The format is exactly the same as that of JSON, but instead of using curly braces, structure is denoted using whitespace. This ",(0,d.jsx)(n.a,{href:"https://linuxhandbook.com/yaml-basics/",children:"quick guide"})," should get you up to speed in a few minutes, for more advanced topics take a look at this ",(0,d.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/YAML",children:"Wikipedia article"}),"."]}),"\n",(0,d.jsx)(n.h3,{id:"config-saving-methods",children:"Config Saving Methods"}),"\n",(0,d.jsxs)(n.p,{children:["When updating the config through the JSON editor in the UI, you have two save options: ",(0,d.jsx)(n.strong,{children:"Local"})," or ",(0,d.jsx)(n.strong,{children:"Write to Disk"}),"."]}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsx)(n.li,{children:"Changes saved locally will only be applied to the current user through the browser, and will not apply to other instances - you either need to use the cloud sync feature, or manually update the conf.yml file."}),"\n",(0,d.jsxs)(n.li,{children:["On the other-hand, if you choose to write changes to disk, then your main ",(0,d.jsx)(n.code,{children:"conf.yml"})," file will be updated, and changes will be applied to all users, and visible across all devices. For this functionality to work, you must be running Dashy with using the Docker container, or the Node server. A backup of your current configuration will also be saved in the same directory."]}),"\n"]}),"\n",(0,d.jsx)(n.h3,{id:"preventing-changes",children:"Preventing Changes"}),"\n",(0,d.jsxs)(n.p,{children:["If you have authentication set up, then any user who is not an admin (with ",(0,d.jsx)(n.code,{children:"type: admin"}),") will not be able to write changes to disk."]}),"\n",(0,d.jsxs)(n.p,{children:["You can also prevent changes from any user being written to disk, using ",(0,d.jsx)(n.code,{children:"preventWriteToDisk"}),". Or prevent any changes from being saved locally in browser storage, using ",(0,d.jsx)(n.code,{children:"preventLocalSave"}),"."]}),"\n",(0,d.jsxs)(n.p,{children:["To disable all UI config features, set ",(0,d.jsx)(n.code,{children:"disableConfiguration"}),". Alternatively you can disable UI config features for all non Admin users by setting ",(0,d.jsx)(n.code,{children:"disableConfigurationForNonAdmin"})," to true."]}),"\n",(0,d.jsx)(n.h3,{id:"example",children:"Example"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-yaml",children:"---\npageInfo:\n title: Home Lab\nsections: # An array of sections\n- name: Section 1 - Getting Started\n items: # An array of items\n - title: GitHub\n description: Source code and documentation on GitHub\n icon: fab fa-github\n url: https://github.com/Lissy93/dashy\n - title: Issues\n description: View currently open issues, or raise a new one\n icon: fas fa-bug\n url: https://github.com/Lissy93/dashy/issues\n - title: Demo\n description: A live demo\n icon: far fa-rocket\n url: https://dashy-demo-1.netlify.app\n- name: Section 2 - Local Services\n items:\n - title: Firewall\n icon: favicon\n url: http://192.168.1.1/\n - title: Game Server\n icon: https://i.ibb.co/710B3Yc/space-invader-x256.png\n url: http://192.168.130.1/\n"})}),"\n",(0,d.jsxs)(n.p,{children:["For more example config files, see: ",(0,d.jsx)(n.a,{href:"https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10",children:"this gist"})]}),"\n",(0,d.jsxs)(n.p,{children:["If you need any help, feel free to ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/new?assignees=Lissy93&labels=%F0%9F%A4%B7%E2%80%8D%E2%99%82%EF%B8%8F+Question&template=question.md&title=%5BQUESTION%5D",children:"Raise an Issue"})," or ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/discussions",children:"Start a Discussion"})]}),"\n",(0,d.jsx)(n.p,{children:"Happy Configuring \ud83e\udd13\ud83d\udd27"}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})})]})}function a(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,d.jsx)(n,{...e,children:(0,d.jsx)(h,{...e})}):h(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>c});var i=s(96540);const d={},r=i.createContext(d);function t(e){const n=i.useContext(r);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(d):e.components||d:t(e.components),i.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[5496],{47395(e,n,s){s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>c,default:()=>a,frontMatter:()=>t,metadata:()=>i,toc:()=>o});const i=JSON.parse('{"id":"configuring","title":"Configuring","description":"All app configuration is specified in /user-data/conf.yml which is in YAML Format format. If you\'re using Docker, this file can be passed in as a volume. Changes can either be made directly to this file, or done through the UI. From the UI you can also export, backup, reset, validate and download your configuration file.","source":"@site/docs/configuring.md","sourceDirName":".","slug":"/configuring","permalink":"/docs/configuring","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/configuring.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Deployment","permalink":"/docs/deployment"},"next":{"title":"App Management","permalink":"/docs/management"}}');var d=s(74848),r=s(28453);const t={},c="Configuring",l={},o=[{value:"There are three ways to edit the config",id:"there-are-three-ways-to-edit-the-config",level:2},{value:"Tips",id:"tips",level:2},{value:"Contents",id:"contents",level:2},{value:"Top-Level Fields",id:"top-level-fields",level:2},{value:"PageInfo",id:"pageinfo",level:2},{value:"pageInfo.navLinks (optional)",id:"pageinfonavlinks-optional",level:2},{value:"pages[] (optional)",id:"pages-optional",level:2},{value:"appConfig (optional)",id:"appconfig-optional",level:2},{value:"appConfig.auth (optional)",id:"appconfigauth-optional",level:2},{value:"appConfig.auth.users (optional)",id:"appconfigauthusers-optional",level:2},{value:"appConfig.auth.keycloak (optional)",id:"appconfigauthkeycloak-optional",level:2},{value:"appConfig.auth.headerAuth (optional)",id:"appconfigauthheaderauth-optional",level:2},{value:"appConfig.auth.oidc (optional)",id:"appconfigauthoidc-optional",level:2},{value:"appConfig.webSearch (optional)",id:"appconfigwebsearch-optional",level:2},{value:"appConfig.hideComponents (optional)",id:"appconfighidecomponents-optional",level:2},{value:"section",id:"section",level:2},{value:"section.item",id:"sectionitem",level:2},{value:"item.displayData (optional)",id:"itemdisplaydata-optional",level:2},{value:"section.widgets (optional)",id:"sectionwidgets-optional",level:2},{value:"section.displayData (optional)",id:"sectiondisplaydata-optional",level:2},{value:"section.icon and section.item.icon",id:"sectionicon-and-sectionitemicon",level:2},{value:"section.displayData.hideForKeycloakUsers, section.displayData.showForKeycloakUsers, item.displayData.hideForKeycloakUsers and item.displayData.showForKeycloakUsers",id:"sectiondisplaydatahideforkeycloakusers-sectiondisplaydatashowforkeycloakusers-itemdisplaydatahideforkeycloakusers-and-itemdisplaydatashowforkeycloakusers",level:2},{value:"Notes",id:"notes",level:2},{value:"Editing Config through the UI",id:"editing-config-through-the-ui",level:3},{value:"About YAML",id:"about-yaml",level:3},{value:"Config Saving Methods",id:"config-saving-methods",level:3},{value:"Preventing Changes",id:"preventing-changes",level:3},{value:"Example",id:"example",level:3}];function h(e){const n={a:"a",admonition:"admonition",b:"b",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(n.header,{children:(0,d.jsx)(n.h1,{id:"configuring",children:"Configuring"})}),"\n",(0,d.jsxs)(n.p,{children:["All app configuration is specified in ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/user-data/conf.yml",children:(0,d.jsx)(n.code,{children:"/user-data/conf.yml"})})," which is in ",(0,d.jsx)(n.a,{href:"https://yaml.org/",children:"YAML Format"})," format. If you're using Docker, this file can be passed in as a volume. Changes can either be made directly to this file, or done ",(0,d.jsx)(n.a,{href:"#editing-config-through-the-ui",children:"through the UI"}),". From the UI you can also export, backup, reset, validate and download your configuration file."]}),"\n",(0,d.jsx)(n.h2,{id:"there-are-three-ways-to-edit-the-config",children:"There are three ways to edit the config"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.strong,{children:"Directly in the YAML file"})," ",(0,d.jsx)(n.em,{children:"(5/5 reliability, 3/5 usability)"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:["Write changes directly to the conf.yml file, optionally using one of the templates provided. This can be done in your favorite editor and uploading to your server, or directly editing the file via SSH, but the easiest method would be to use ",(0,d.jsx)(n.a,{href:"https://github.com/coder/code-server",children:"Code Server"})]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.strong,{children:"UI JSON Editor"})," ",(0,d.jsx)(n.em,{children:"(4/5 reliability, 4/5 usability)"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsx)(n.li,{children:"From the UI, under the config menu there is a JSON editor, with built-in validation, documentation and advanced options"}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.strong,{children:"UI Visual Editor"})," ",(0,d.jsx)(n.em,{children:"(3/5 reliability, 5/5 usability)"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsx)(n.li,{children:"From the UI, enter the Interactive Edit Mode, then click any part of the page to edit. Changes are previewed live, and then saved to disk"}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.strong,{children:"REST API"})," ",(0,d.jsxs)(n.em,{children:["(see ",(0,d.jsx)(n.a,{href:"/docs/api",children:"API docs"}),")"]}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsx)(n.li,{children:"Programmatically edit config either through the command line, using a script or a third-party application"}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,d.jsx)(n.h2,{id:"tips",children:"Tips"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:["You may find it helpful to look at some sample config files to get you started, a collection of which can be found ",(0,d.jsx)(n.a,{href:"https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10",children:"here"})]}),"\n",(0,d.jsxs)(n.li,{children:["You can check that your config file fits the schema, by running ",(0,d.jsx)(n.code,{children:"yarn validate-config"})]}),"\n",(0,d.jsxs)(n.li,{children:["After modifying your config, the app needs to be recompiled, by running ",(0,d.jsx)(n.code,{children:"yarn build"})," - this happens automatically if you're using Docker"]}),"\n",(0,d.jsxs)(n.li,{children:["It is recommended to keep a backup of your config file. You can download it under Config menu, or use the ",(0,d.jsx)(n.a,{href:"/docs/backup-restore",children:"Cloud Backup"})," feature."]}),"\n",(0,d.jsx)(n.li,{children:"You can make use of YAML features, like anchors, comments, multi-line strings, etc to reuse attributes and keep your config file readable"}),"\n",(0,d.jsxs)(n.li,{children:["Once you have finished configuring your dashboard, you can choose to ",(0,d.jsx)(n.a,{href:"#preventing-changes",children:"disable UI config"})," if you wish"]}),"\n",(0,d.jsx)(n.li,{children:"All fields are optional, unless otherwise stated."}),"\n"]}),"\n",(0,d.jsx)(n.p,{children:"The following file provides a reference of all supported configuration options."}),"\n",(0,d.jsx)(n.h2,{id:"contents",children:"Contents"}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#pageinfo",children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pageInfo"})})})," - Header text, footer, title, navigation, etc\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#pageinfonavlinks-optional",children:(0,d.jsx)(n.code,{children:"navLinks"})})," - Links to display in the navigation bar"]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#pages-optional",children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pages"})})})," - List of additional config files, for multi-page dashboards"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfig-optional",children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"appConfig"})})})," - Main application settings\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfigwebsearch-optional",children:(0,d.jsx)(n.code,{children:"webSearch"})})," - Configure web search engine options"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfighidecomponents-optional",children:(0,d.jsx)(n.code,{children:"hideComponents"})})," - Show/ hide page components"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfigauth-optional",children:(0,d.jsx)(n.code,{children:"auth"})})," - Built-in authentication setup\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfigauthusers-optional",children:(0,d.jsx)(n.code,{children:"users"})})," - List or users (for simple auth)"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfigauthkeycloak-optional",children:(0,d.jsx)(n.code,{children:"keycloak"})})," - Auth config for Keycloak"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#appconfigauthheaderauth-optional",children:(0,d.jsx)(n.code,{children:"headerAuth"})})," - Auth config for HeaderAuth"]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#section",children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"sections"})})})," - List of sections\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectiondisplaydata-optional",children:(0,d.jsx)(n.code,{children:"displayData"})})," - Section display settings\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectiondisplaydatahideforkeycloakusers-sectiondisplaydatashowforkeycloakusers-itemdisplaydatahideforkeycloakusers-and-itemdisplaydatashowforkeycloakusers",children:(0,d.jsx)(n.code,{children:"show/hideForKeycloakUsers"})})," - Set user controls"]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectionicon-and-sectionitemicon",children:(0,d.jsx)(n.code,{children:"icon"})})," - Icon for a section"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectionitem",children:(0,d.jsx)(n.code,{children:"items"})})," - List of items\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectionicon-and-sectionitemicon",children:(0,d.jsx)(n.code,{children:"icon"})})," - Icon for an item"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#itemdisplaydata-optional",children:(0,d.jsx)(n.code,{children:"displayData"})})," - Item display settings\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectiondisplaydatahideforkeycloakusers-sectiondisplaydatashowforkeycloakusers-itemdisplaydatahideforkeycloakusers-and-itemdisplaydatashowforkeycloakusers",children:(0,d.jsx)(n.code,{children:"show/hideForKeycloakUsers"})})," - Set user controls"]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#sectionwidgets-optional",children:(0,d.jsx)(n.code,{children:"widgets"})})," - List of widgets"]}),"\n"]}),"\n"]}),"\n",(0,d.jsxs)(n.li,{children:[(0,d.jsx)(n.a,{href:"#notes",children:(0,d.jsx)(n.strong,{children:"Notes"})}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsx)(n.li,{children:(0,d.jsx)(n.a,{href:"#editing-config-through-the-ui",children:"Editing Config through the UI"})}),"\n",(0,d.jsx)(n.li,{children:(0,d.jsx)(n.a,{href:"#about-yaml",children:"About YAML"})}),"\n",(0,d.jsx)(n.li,{children:(0,d.jsx)(n.a,{href:"#config-saving-methods",children:"Config Saving Methods"})}),"\n",(0,d.jsx)(n.li,{children:(0,d.jsx)(n.a,{href:"#preventing-changes",children:"Preventing Changes"})}),"\n",(0,d.jsx)(n.li,{children:(0,d.jsx)(n.a,{href:"#example",children:"Example"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,d.jsx)(n.hr,{}),"\n",(0,d.jsx)(n.h2,{id:"top-level-fields",children:"Top-Level Fields"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pageInfo"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["Basic meta data like title, description, nav bar links, footer text. See ",(0,d.jsx)(n.a,{href:"#pageinfo",children:(0,d.jsx)(n.code,{children:"pageInfo"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"appConfig"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Settings related to how the app functions, including API keys and global styles. See ",(0,d.jsx)(n.a,{href:"#appconfig-optional",children:(0,d.jsx)(n.code,{children:"appConfig"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"sections"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["An array of sections, each containing an array of items, which will be displayed as links. See ",(0,d.jsx)(n.a,{href:"#section",children:(0,d.jsx)(n.code,{children:"section"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pages"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An array additional config files, used for multi-page dashboards. See ",(0,d.jsx)(n.a,{href:"#pages-optional",children:(0,d.jsx)(n.code,{children:"pages"})})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsx)(n.h2,{id:"pageinfo",children:(0,d.jsx)(n.code,{children:"PageInfo"})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"title"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"Your dashboard title, displayed in the header and browser tab"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"description"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Description of your dashboard, also displayed as a subtitle"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"navLinks"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Optional list of a maximum of 6 links, which will be displayed in the navigation bar. See ",(0,d.jsx)(n.a,{href:"#pageinfonavlinks-optional",children:(0,d.jsx)(n.code,{children:"navLinks"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"footer"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Text to display in the footer. When omitted, no footer is rendered. Supports inline HTML (sanitized before render)"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"logo"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The path to an image to display in the header (to the right of the title). This can be either local, where ",(0,d.jsx)(n.code,{children:"/"})," is the root of ",(0,d.jsx)(n.code,{children:"./public"}),", or any remote image, such as ",(0,d.jsx)(n.code,{children:"https://i.ibb.co/yhbt6CY/dashy.png"}),". It's recommended to scale your image down, so that it doesn't impact load times"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"favicon"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["URL or path to a custom favicon shown in the browser tab. Can be absolute (",(0,d.jsx)(n.code,{children:"https://..."}),"), root-relative (",(0,d.jsx)(n.code,{children:"/icons/x.png"}),"), or a ",(0,d.jsx)(n.code,{children:"data:"})," URI"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"color"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Theme colour applied to the browser chrome (mobile address bar). Any valid CSS color (e.g. ",(0,d.jsx)(n.code,{children:"#ff00a7"}),") is accepted"]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"pageinfonavlinks-optional",children:[(0,d.jsx)(n.code,{children:"pageInfo.navLinks"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"title"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The text to display on the link button"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"path"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["The URL to navigate to when clicked. Can be relative (e.g. ",(0,d.jsx)(n.code,{children:"/about"}),") or absolute (e.g. ",(0,d.jsx)(n.code,{children:"https://example.com"})," or ",(0,d.jsx)(n.code,{children:"http://192.168.1.1"}),")"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"target"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The opening method (external links only). Can be either ",(0,d.jsx)(n.code,{children:"newtab"}),", ",(0,d.jsx)(n.code,{children:"sametab"}),", ",(0,d.jsx)(n.code,{children:"newwindow"}),", ",(0,d.jsx)(n.code,{children:"top"})," or ",(0,d.jsx)(n.code,{children:"parent"}),". Defaults to ",(0,d.jsx)(n.code,{children:"newtab"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"pages-optional",children:[(0,d.jsx)(n.code,{children:"pages[]"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"name"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"A unique name for that page"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"path"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["The path (local or remote) to the config file to use.",(0,d.jsx)(n.br,{}),"For files located within ",(0,d.jsx)(n.code,{children:"/user-data"}),", you only need to specify filename, for externally hosted files you must include the full URL"]})]})]})]}),"\n",(0,d.jsxs)(n.p,{children:["For more info, see the",(0,d.jsx)(n.a,{href:"/docs/pages-and-sections#multi-page-support",children:"Multi-Page docs"})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfig-optional",children:[(0,d.jsx)(n.code,{children:"appConfig"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"language"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The 2 (or 4-digit) ",(0,d.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes",children:"ISO 639-1 code"})," for your language, e.g. ",(0,d.jsx)(n.code,{children:"en"})," or ",(0,d.jsx)(n.code,{children:"en-GB"}),". This must be a language that the app has already been ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/src/assets/locales",children:"translated"})," into. If your language is unavailable, Dashy will fallback to English. By default Dashy will attempt to auto-detect your language, although this may not work on some privacy browsers."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"startingView"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"enum"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Which view to land on when visiting ",(0,d.jsx)(n.code,{children:"/"}),". One of ",(0,d.jsx)(n.code,{children:"home"}),", ",(0,d.jsx)(n.code,{children:"minimal"})," or ",(0,d.jsx)(n.code,{children:"workspace"}),". Defaults to ",(0,d.jsx)(n.code,{children:"home"}),". Applied at runtime, so no rebuild is needed. You can always switch views from the UI. (Legacy value ",(0,d.jsx)(n.code,{children:"default"})," is accepted as an alias for ",(0,d.jsx)(n.code,{children:"home"}),".)"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"defaultOpeningMethod"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"enum"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The default opening method for items, if no ",(0,d.jsx)(n.code,{children:"target"})," is specified for a given item. Can be either ",(0,d.jsx)(n.code,{children:"newtab"}),", ",(0,d.jsx)(n.code,{children:"sametab"}),", ",(0,d.jsx)(n.code,{children:"modal"}),", ",(0,d.jsx)(n.code,{children:"workspace"}),", ",(0,d.jsx)(n.code,{children:"clipboard"}),", ",(0,d.jsx)(n.code,{children:"top"})," or ",(0,d.jsx)(n.code,{children:"parent"}),". Defaults to ",(0,d.jsx)(n.code,{children:"newtab"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheck"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", Dashy will ping each of your services and display their status as a dot next to each item. This can be overridden by setting ",(0,d.jsx)(n.code,{children:"statusCheck"})," under each item. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckInterval"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of seconds between checks. If set to ",(0,d.jsx)(n.code,{children:"0"})," then service will only be checked on initial page load, which is usually the desired functionality. If value is less than ",(0,d.jsx)(n.code,{children:"10"})," you may experience a hit in performance. Defaults to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckAccessibility"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", status indicators will use distinct shapes to indicate status for color-blind users. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckEnabled"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", Dashy will make a real ICMP ping to each of your hosts and display their status as a dot next to each item. This can be overridden by setting ",(0,d.jsx)(n.code,{children:"pingCheckEnabled"})," under each item. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckInterval"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of seconds between checks. If set to ",(0,d.jsx)(n.code,{children:"0"})," then service will only be checked on initial page load. The value is limited to ",(0,d.jsx)(n.code,{children:"5"})," seconds min to prevent performance issues. Defaults to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckAccessibility"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", status indicators will use distinct shapes to indicate status for color-blind users. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckCount"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of ping ICMP packets sent to the host per check. This can be overridden by setting ",(0,d.jsx)(n.code,{children:"pingCheckCount"})," under each item. If set to ",(0,d.jsx)(n.code,{children:"0"})," then ",(0,d.jsx)(n.code,{children:"3"})," packets will be sent. The value is limited to ",(0,d.jsx)(n.code,{children:"5"})," packets max to prevent performance issues. Defaults to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckTimeout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of milliseconds to wait for a ping to complete. This can be overriden by setting ",(0,d.jsx)(n.code,{children:"pingCheckTimeout"})," under each item. If set to ",(0,d.jsx)(n.code,{children:"0"})," then it uses the same value as ",(0,d.jsx)(n.code,{children:"pingCheckInterval"})," but converted in milliseconds. The value is limited to 1000 ms per ",(0,d.jsx)(n.code,{children:"pingCheckCount"})," to prevent performance issues. Defaults to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"webSearch"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Configuration options for the web search feature, set your default search engine, opening method or disable web search. See ",(0,d.jsx)(n.a,{href:"#appconfigwebsearch-optional",children:(0,d.jsx)(n.code,{children:"webSearch"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"backgroundImg"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Path to an optional full-screen app background image. This can be either remote (http) or local (relative to /app/public/item-icons/ inside the container). Note that this will slow down initial load"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableFontAwesome"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"})," font-awesome will be loaded, if set to ",(0,d.jsx)(n.code,{children:"false"})," they will not be. if left blank font-awesome will be enabled only if required by 1 or more icons"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableMaterialDesignIcons"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"})," mdi icons will be loaded, if set to ",(0,d.jsx)(n.code,{children:"false"})," they will not be. Where ",(0,d.jsx)(n.code,{children:"true"})," is enabled, if left blank material design icons will be enabled only if required by 1 or more icons"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"fontAwesomeKey"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If you have a font-awesome key, then you can use it here and make use of premium icons. It is a 10-digit alpha-numeric string from you're FA kit URL (e.g. ",(0,d.jsx)(n.code,{children:"13014ae648"}),")"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"faviconApi"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"enum"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Only applicable if you are using favicons for item icons. Specifies which service to use to resolve favicons. Set to ",(0,d.jsx)(n.code,{children:"local"})," to do this locally, without using an API. Services running locally will use this option always. Available options are: ",(0,d.jsx)(n.code,{children:"local"}),", ",(0,d.jsx)(n.code,{children:"allesedv"}),", ",(0,d.jsx)(n.code,{children:"iconhorse"}),", ",(0,d.jsx)(n.code,{children:"faviconkit"}),", ",(0,d.jsx)(n.code,{children:"duckduckgo"}),", ",(0,d.jsx)(n.code,{children:"yandex"}),", ",(0,d.jsx)(n.code,{children:"google"}),", ",(0,d.jsx)(n.code,{children:"besticon"}),", ",(0,d.jsx)(n.code,{children:"webmasterapi"})," and ",(0,d.jsx)(n.code,{children:"mcapi"}),". Defaults to ",(0,d.jsx)(n.code,{children:"allesedv"}),". See ",(0,d.jsx)(n.a,{href:"/docs/icons#favicons",children:"Icons"})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"auth"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["All settings relating to user authentication. See ",(0,d.jsx)(n.a,{href:"#appconfigauth-optional",children:(0,d.jsx)(n.code,{children:"auth"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"defaultIcon"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An icon to be applied to any items, which don't already have an icon set. See ",(0,d.jsx)(n.a,{href:"/docs/icons#default-icon",children:"Icon Docs"})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"layout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"enum"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Layout for homepage, one of ",(0,d.jsx)(n.code,{children:"auto"}),", ",(0,d.jsx)(n.code,{children:"horizontal"}),", ",(0,d.jsx)(n.code,{children:"vertical"})," or ",(0,d.jsx)(n.code,{children:"masonry"}),". Defaults to ",(0,d.jsx)(n.code,{children:"auto"}),". Specifies the layout and direction of how sections are positioned on the home screen. ",(0,d.jsx)(n.code,{children:"auto"})," uses a responsive CSS grid where each section's footprint is controlled by ",(0,d.jsx)(n.code,{children:"displayData.cols"})," and ",(0,d.jsx)(n.code,{children:"displayData.rows"}),". ",(0,d.jsx)(n.code,{children:"masonry"})," uses a responsive grid where heights follow content, so shorter sections flow into the gaps left by taller neighbours (",(0,d.jsx)(n.code,{children:"rows"})," is ignored, ",(0,d.jsx)(n.code,{children:"cols"})," still controls width). This can also be modified and overridden from the UI."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"iconSize"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"enum"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The size of link items / icons. Can be either ",(0,d.jsx)(n.code,{children:"small"}),", ",(0,d.jsx)(n.code,{children:"medium,"})," or ",(0,d.jsx)(n.code,{children:"large"}),". Defaults to ",(0,d.jsx)(n.code,{children:"medium"}),". This can also be set directly from the UI."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"colCount"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of columns of sections displayed on the homepage, using the default view. Should be in integer between ",(0,d.jsx)(n.code,{children:"1"})," and ",(0,d.jsx)(n.code,{children:"8"}),". Note that by default this is applied responsively, based on current screen size, and specifying a value here will override this behavior, which may not be desirable."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"contentMaxWidth"})})}),(0,d.jsxs)(n.td,{children:[(0,d.jsx)(n.code,{children:"string"})," or ",(0,d.jsx)(n.code,{children:"number"})]}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Sets the max width of the main sections area on the homepage, overriding the responsive default. Can be a percentage, or any CSS unit"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"theme"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The default theme for first load (you can change this later from the UI)"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"dayTheme"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Theme to apply when the OS is set to light mode. Overrides ",(0,d.jsx)(n.code,{children:"theme"})," on initial load"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"nightTheme"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Theme to apply when the OS is set to dark mode. Overrides ",(0,d.jsx)(n.code,{children:"theme"})," on initial load"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"cssThemes"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"An array of custom theme names which can be used in the theme switcher dropdown"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"customColors"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Enables you to apply a custom color palette to any given theme. Use the theme name (lowercase) as the key, for an object including key-value-pairs, with the color variable name as keys, and 6-digit hex code as value. See ",(0,d.jsx)(n.a,{href:"/docs/theming#modifying-theme-colors",children:"Theming"})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"externalStyleSheet"})})}),(0,d.jsxs)(n.td,{children:[(0,d.jsx)(n.code,{children:"string"})," or ",(0,d.jsx)(n.code,{children:"string[]"})]}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Either a URL to an external stylesheet or an array or URLs, which can be applied as themes within the UI"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"customCss"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Raw CSS that will be applied to the page. This can also be set from the UI. Please minify it first."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideComponents"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["A list of key page components (header, nav, search, settings) that are present by default, but can be removed using this option. See ",(0,d.jsx)(n.a,{href:"#appconfighidecomponents-optional",children:(0,d.jsx)(n.code,{children:"appConfig.hideComponents"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableMultiTasking"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"If set to true, will keep apps open in the background when in the workspace view. Useful for quickly switching between multiple sites, and preserving their state, but comes at the cost of performance."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"workspaceLandingUrl"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The URL or an app, service or website to launch when the workspace view is opened, before another service has been launched"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"preventWriteToDisk"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", users will be prevented from saving config changes to disk through the UI"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"preventLocalSave"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", users will be prevented from applying config changes to local storage"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableConfiguration"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"If set to true, no users will be able to view or edit the config through the UI"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableConfigurationForNonAdmin"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"If set to true, only admin users will be able to view or edit the config through the UI. disableConfiguration must not be set to true."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"widgetsAlwaysUseProxy"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", requests made by widgets will always be proxied, same as setting ",(0,d.jsx)(n.code,{children:"useProxy: true"})," on each widget. Note that this may break some widgets."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"showSplashScreen"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", a loading screen will be shown. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableErrorReporting"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Enable reporting of unexpected errors and crashes. This is off by default, and ",(0,d.jsx)(n.strong,{children:"no data will ever be captured unless you explicitly enable it"}),". Turning on error reporting helps previously unknown bugs get discovered and fixed. Dashy uses ",(0,d.jsx)(n.a,{href:"https://github.com/getsentry/sentry",children:"Sentry"})," for error reporting. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"sentryDsn"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If you need to monitor errors in your instance, then you can use Sentry to collect and process bug reports. Sentry can be self-hosted, or used as SaaS, once your instance is setup, then all you need to do is pass in the DSN here, and enable error reporting. You can learn more on the ",(0,d.jsx)(n.a,{href:"https://docs.sentry.io/product/sentry-basics/dsn-explainer/",children:"Sentry DSN Docs"}),". Note that this will only ever be used if ",(0,d.jsx)(n.code,{children:"enableErrorReporting"})," is explicitly enabled."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableSmartSort"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["For the most-used and last-used app sort functions to work, a basic open-count is stored in local storage. If you do not want this to happen, then disable smart sort here, but you wil no longer be able to use these sort options. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableUpdateChecks"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to true, Dashy will not check for updates. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableServiceWorker"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Service workers cache web applications to improve load times and offer basic offline functionality, and are disabled by default in Dashy. The service worker can sometimes cause older content to be cached, requiring the app to be hard-refreshed. If you do not want SW functionality, or are having issues with caching, set this property to ",(0,d.jsx)(n.code,{children:"false"})," to disable all service workers."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableAuthProxyCompat"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Only relevant when ",(0,d.jsx)(n.code,{children:"enableServiceWorker"})," is ",(0,d.jsx)(n.code,{children:"true"})," and when Dashy sits behind an authentication proxy (such as Cloudflare Zero Trust or Authelia). Set this to ",(0,d.jsx)(n.code,{children:"true"})," to detect that redirect on load, unregister the service worker, and reload so re-authentication can proceed. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableContextMenu"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", the custom right-click context menu will be disabled. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigauth-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.auth"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n",(0,d.jsx)(n.admonition,{type:"note",children:(0,d.jsxs)(n.p,{children:["Since the auth is initiated in the main app entry point (for security), a rebuild is required to apply changes to the auth configuration.\nRun ",(0,d.jsx)(n.code,{children:"yarn build"})," in the root directory, then restart the server."]})}),"\n",(0,d.jsx)(n.admonition,{type:"warning",children:(0,d.jsxs)(n.p,{children:["Built-in auth should ",(0,d.jsx)(n.strong,{children:"not be used"})," for security-critical applications, or if your Dashy instance is publicly accessible.\nFor these, it is recommended to use an ",(0,d.jsx)(n.a,{href:"/docs/authentication#alternative-authentication-methods",children:"alternate authentication method"}),"."]})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"users"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An array of objects containing usernames and hashed passwords. If this is not provided, then authentication will be off by default, and you will not need any credentials to access the app. See ",(0,d.jsx)(n.a,{href:"#appconfigauthusers-optional",children:(0,d.jsx)(n.code,{children:"appConfig.auth.users"})}),". ",(0,d.jsx)(n.br,{}),(0,d.jsx)(n.strong,{children:"Note"})," this method of authentication is handled on the client side, so for security critical situations, it is recommended to use an ",(0,d.jsx)(n.a,{href:"/docs/authentication#alternative-authentication-methods",children:"alternate authentication method"}),"."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableKeycloak"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", then authentication using Keycloak will be enabled. Note that you need to have an instance running, and have also configured ",(0,d.jsx)(n.code,{children:"auth.keycloak"}),". Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"keycloak"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Config options to point Dashy to your Keycloak server. Requires ",(0,d.jsx)(n.code,{children:"enableKeycloak: true"}),". See ",(0,d.jsx)(n.a,{href:"#appconfigauthkeycloak-optional",children:(0,d.jsx)(n.code,{children:"auth.keycloak"})})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableHeaderAuth"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", then authentication using HeaderAuth will be enabled. Note that you need to have your web server/reverse proxy running, and have also configured ",(0,d.jsx)(n.code,{children:"auth.headerAuth"}),". Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"headerAuth"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Config options to point Dashy to your headers for authentication. Requires ",(0,d.jsx)(n.code,{children:"enableHeaderAuth: true"}),". See ",(0,d.jsx)(n.a,{href:"#appconfigauthheaderauth-optional",children:(0,d.jsx)(n.code,{children:"auth.headerAuth"})})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableOidc"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", then authentication using OIDC will be enabled. Note that you need to have a configured OIDC server and configure it with ",(0,d.jsx)(n.code,{children:"auth.oidc"}),". Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"oidc"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Config options to point Dash to your OIDC configuration. Request ",(0,d.jsx)(n.code,{children:"enableOidc: true"}),". See ",(0,d.jsx)(n.a,{href:"#appconfigauthoidc-optional",children:(0,d.jsx)(n.code,{children:"auth.oidc"})})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableGuestAccess"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", an unauthenticated user will be able to access the dashboard, with read-only access, without having to login. Requires ",(0,d.jsx)(n.code,{children:"auth.users"})," to be configured. Defaults to ",(0,d.jsx)(n.code,{children:"false"}),"."]})]})]})]}),"\n",(0,d.jsxs)(n.p,{children:["For more info, see the ",(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"/docs/authentication",children:"Authentication Docs"})})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigauthusers-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.auth.users"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"user"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"Username to log in with"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hash"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"A SHA-256 hashed password"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"type"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The user type, either admin or normal"})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigauthkeycloak-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.auth.keycloak"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"serverUrl"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The URL (or URL/ IP + Port) where your keycloak server is running"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"realm"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The name of the realm (must already be created) that you want to use"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"clientId"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The Client ID of the client you created for use with Dashy"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"idpHint"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The alias of an Identity Provider configured in your realm. If set, Keycloak will skip its login page and redirect straight to that external IdP"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"legacySupport"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If using Keycloak 17 or older, then set this to ",(0,d.jsx)(n.code,{children:"true"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigauthheaderauth-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.auth.headerAuth"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"userHeader"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The HTTP header name containing the authenticated username (default: ",(0,d.jsx)(n.code,{children:"Remote-User"}),"). Case insensitive"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"proxyWhitelist"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"An array of upstream proxy server IPs to accept authenticated requests from"})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigauthoidc-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.auth.oidc"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"clientId"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The client id registered in the OIDC server"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"endpoint"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The URL of the OIDC server that should be used."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"adminRole"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The role that will be considered as admin."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"adminGroup"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The group that will be considered as admin."})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"scope"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The scope(s) to request from the OIDC provider"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"enableSilentRenew"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", your session is silently renewed in the background before it expires (only works for providers which support the ",(0,d.jsx)(n.code,{children:"offline_access"})," scope)"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"allowedIssuers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["List of issuer URLs to accept tokens from. Needed for multi-tenant providers (e.g. Microsoft Entra) where the token issuer differs from the configured ",(0,d.jsx)(n.code,{children:"endpoint"}),". If unset, the issuer from the discovery document is used"]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfigwebsearch-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.webSearch"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"disableWebSearch"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Web search is enabled by default, but can be disabled by setting this property to ",(0,d.jsx)(n.code,{children:"true"}),". Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"searchEngine"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Set the key name for your search engine. Can also use a custom engine by setting this property to ",(0,d.jsx)(n.code,{children:"custom"}),". See the ",(0,d.jsx)(n.a,{href:"/docs/searching#setting-search-engine",children:"searching docs"})," for the full list of built-in keys. Defaults to ",(0,d.jsx)(n.code,{children:"duckduckgo"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"customSearchEngine"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["You can also use a custom search engine, or your own self-hosted instance. This requires ",(0,d.jsx)(n.code,{children:"searchEngine: custom"})," to be set. Then add the URL of your service, with GET query string included here"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"openingMethod"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Set your preferred opening method for search results: ",(0,d.jsx)(n.code,{children:"newtab"}),", ",(0,d.jsx)(n.code,{children:"sametab"}),", ",(0,d.jsx)(n.code,{children:"workspace"}),". Defaults to ",(0,d.jsx)(n.code,{children:"newtab"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"searchBangs"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["A key-value-pair set of custom search ",(0,d.jsx)(n.em,{children:"bangs"})," for redirecting query to a specific app or search engine. The key of each should be the bang you will type (typically starting with ",(0,d.jsx)(n.code,{children:"/"}),", ",(0,d.jsx)(n.code,{children:"!"})," or ",(0,d.jsx)(n.code,{children:":"}),"), and value is the destination, either as a search engine key (e.g. ",(0,d.jsx)(n.code,{children:"reddit"}),") or a URL with search parameters (e.g. ",(0,d.jsx)(n.code,{children:"https://en.wikipedia.org/w/?search="}),")"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"openUrlsDirectly"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If ",(0,d.jsx)(n.code,{children:"true"}),", queries that look like URLs will be opened directly instead of searched. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"appconfighidecomponents-optional",children:[(0,d.jsx)(n.code,{children:"appConfig.hideComponents"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideHeading"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", the page title & sub-title will not be visible. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideNav"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", the navigation menu will not be visible. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideSearch"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", the search bar will not be visible. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideSettings"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If set to ",(0,d.jsx)(n.code,{children:"true"}),", the settings menu will be initially collapsed. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsx)(n.h2,{id:"section",children:(0,d.jsx)(n.code,{children:"section"})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"name"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsx)(n.td,{children:"The title for the section"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"icon"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An single icon to be displayed next to the title. See ",(0,d.jsx)(n.a,{href:"#sectionicon-and-sectionitemicon",children:(0,d.jsx)(n.code,{children:"section.icon"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"items"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An array of items to be displayed within the section. See ",(0,d.jsx)(n.a,{href:"#sectionitem",children:(0,d.jsx)(n.code,{children:"item"})}),". Sections must include either 1 or more items, or 1 or more widgets."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"widgets"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An array of widgets to be displayed within the section. See ",(0,d.jsx)(n.a,{href:"#sectionwidgets-optional",children:(0,d.jsx)(n.code,{children:"widget"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"displayData"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Meta-data to optionally override display settings for a given section. See ",(0,d.jsx)(n.a,{href:"#sectiondisplaydata-optional",children:(0,d.jsx)(n.code,{children:"displayData"})})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsx)(n.h2,{id:"sectionitem",children:(0,d.jsx)(n.code,{children:"section.item"})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"title"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["The text to display/ title of a given item. Max length ",(0,d.jsx)(n.code,{children:"18"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"description"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Additional info about an item, which is shown in the tooltip on hover, or visible on large tiles"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"url"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The URL / location of web address for when the item is clicked"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"localUrl"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"An alternative URL (e.g. a LAN address) that is preferred whenever it is reachable from your browser"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"localUrlTimeout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Milliseconds to wait for the ",(0,d.jsx)(n.code,{children:"localUrl"})," reachability probe before giving up and using ",(0,d.jsx)(n.code,{children:"url"}),". Between ",(0,d.jsx)(n.code,{children:"300"})," and ",(0,d.jsx)(n.code,{children:"5000"}),". Defaults to ",(0,d.jsx)(n.code,{children:"1500"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"localUrlCheckInterval"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Seconds between background re-checks of ",(0,d.jsx)(n.code,{children:"localUrl"}),". ",(0,d.jsx)(n.code,{children:"0"})," means only check on page load and when the browser tab regains focus. ",(0,d.jsx)(n.code,{children:"300"})," max. Defaults to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"icon"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The icon for a given item. Can be a font-awesome icon, favicon, remote URL or local URL. See ",(0,d.jsx)(n.a,{href:"#sectionicon-and-sectionitemicon",children:(0,d.jsx)(n.code,{children:"item.icon"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"target"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The opening method for when the item is clicked, either ",(0,d.jsx)(n.code,{children:"newtab"}),", ",(0,d.jsx)(n.code,{children:"sametab"}),", ",(0,d.jsx)(n.code,{children:"modal"}),", ",(0,d.jsx)(n.code,{children:"workspace"}),", ",(0,d.jsx)(n.code,{children:"clipboard"}),", ",(0,d.jsx)(n.code,{children:"top"})," or ",(0,d.jsx)(n.code,{children:"parent"}),". Where ",(0,d.jsx)(n.code,{children:"newtab"})," will open the link in a new tab, ",(0,d.jsx)(n.code,{children:"sametab"})," will open it in the current tab, and ",(0,d.jsx)(n.code,{children:"modal"})," will open a pop-up modal, ",(0,d.jsx)(n.code,{children:"workspace"})," will open in the Workspace view and ",(0,d.jsx)(n.code,{children:"clipboard"})," will copy the URL to system clipboard (but not launch app). Defaults to ",(0,d.jsx)(n.code,{children:"newtab"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hotkey"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Give frequently opened applications a numeric hotkey, between ",(0,d.jsx)(n.code,{children:"0 - 9"}),". You can then just press that key to launch that application."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"tags"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"A list of tags, which can be used for improved search"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheck"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", Dashy will ping the URL associated with the current service, and display its status as a dot next to the item. The value here will override ",(0,d.jsx)(n.code,{children:"appConfig.statusCheck"})," so you can turn off or on checks for a given service. Defaults to ",(0,d.jsx)(n.code,{children:"appConfig.statusCheck"}),", falls back to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckUrl"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If you've enabled ",(0,d.jsx)(n.code,{children:"statusCheck"}),", and want to use a different URL to what is defined under the item, then specify it here"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckHeaders"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"If you're endpoint requires any specific headers for the status checking, then define them here"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckAllowInsecure"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["By default, any request to insecure content will be blocked. Setting this option to ",(0,d.jsx)(n.code,{children:"true"})," will disable the ",(0,d.jsx)(n.code,{children:"rejectUnauthorized"})," option, enabling you to ping non-HTTPS services for the current item. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckAcceptCodes"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If your service's response code is anything other than 2xx, then you can opt to specify an alternative success code. E.g. if you expect your server to return 403, but still want the status indicator to be green, set this value to ",(0,d.jsx)(n.code,{children:"403"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"statusCheckMaxRedirects"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If your service redirects to another page, and you would like status checks to follow redirects, then specify the maximum number of redirects here. Defaults to ",(0,d.jsx)(n.code,{children:"0"})," / will not follow redirects"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckEnabled"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["When set to ",(0,d.jsx)(n.code,{children:"true"}),", Dashy will make a real ICMP ping to each of your hosts and display their status as a dot next to each item. The value here will override ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckEnabled"})," so you can turn off or on pings for a given host. Defaults to ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckEnabled"}),", falls back to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckHost"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["FQDN hostname or IPV4 address of the host to ping. If empty or not defined then it uses the item's url host and if it is also invalid then no ping is performed. Defaults to ",(0,d.jsx)(n.code,{children:"undefined"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckInterval"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of seconds between checks. The value will override ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckInterval"})," so you can adjust the ping interval per host. Defaults to ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckInterval"}),", falls back to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckCount"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of ping ICMP packets sent to the host per check. The value will override ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckCount"})," so you can decide to send a different number of ping ICMP packets per host. Defaults to ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckCount"}),", falls back to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"pingCheckTimeout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The number of milliseconds to wait for a ping to complete. The value will override ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckTimeout"})," so you can adjust the timeout per host. Defaults to ",(0,d.jsx)(n.code,{children:"appConfig.pingCheckTimeout"}),", falls back to ",(0,d.jsx)(n.code,{children:"0"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"color"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"An optional color for the text and font-awesome icon to be displayed in. Note that this will override the current theme and so may not display well"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"rel"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The value of the ",(0,d.jsx)(n.code,{children:"rel"})," attribute for the link. Useful for specifying the relationship between the target link/document and Dashy. Defaults to ",(0,d.jsx)(n.code,{children:"noopener noreferrer"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"backgroundColor"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"An optional background fill color for the that given item. Again, this will override the current theme and so might not display well against the background"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"provider"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"The name of the provider for a given service, useful for when including hosted apps. In some themes, this is visible under the item name"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"displayData"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Meta-data to optionally override display settings for a given item. See ",(0,d.jsx)(n.a,{href:"#itemdisplaydata-optional",children:(0,d.jsx)(n.code,{children:"displayData"})})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"subItems"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"array"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["An optional list of nested sub-items, rendered as smaller icons within the parent. Each sub-item supports ",(0,d.jsx)(n.code,{children:"title"}),", ",(0,d.jsx)(n.code,{children:"url"}),", ",(0,d.jsx)(n.code,{children:"icon"}),", ",(0,d.jsx)(n.code,{children:"target"}),", ",(0,d.jsx)(n.code,{children:"color"})," and ",(0,d.jsx)(n.code,{children:"backgroundColor"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"itemdisplaydata-optional",children:[(0,d.jsx)(n.code,{children:"item.displayData"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current item will be visible to all users, except for those specified in this list"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"showForUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current item will be hidden from all users, except for those specified in this list"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForGuests"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current item will be visible for logged in users, but not for guests (see ",(0,d.jsx)(n.code,{children:"appConfig.enableGuestAccess"}),"). Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForKeycloakUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current item will be visible to all keycloak users, except for those configured via these groups and roles. See ",(0,d.jsx)(n.code,{children:"hideForKeycloakUsers"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"showForKeycloakUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current item will be hidden from all keycloak users, except for those configured via these groups and roles. See ",(0,d.jsx)(n.code,{children:"showForKeycloakUsers"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideFromWorkspace"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current item will be visible in the default view but not in the Workspace view sidebar. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideFromHomepage"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If ",(0,d.jsx)(n.code,{children:"true"}),", item is hidden from the home and minimal views until matched by a search. Still visible in workspace, edit mode and single-section view. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"sectionwidgets-optional",children:[(0,d.jsx)(n.code,{children:"section.widgets"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"type"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:"Required"}),(0,d.jsxs)(n.td,{children:["The widget type. See ",(0,d.jsx)(n.a,{href:"/docs/widgets",children:"Widget Docs"})," for full list of supported widgets"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"options"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Some widgets accept either optional or required additional options. Again, see the ",(0,d.jsx)(n.a,{href:"/docs/widgets",children:"Widget Docs"})," for full list of options"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"updateInterval"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["You can keep a widget constantly updated by specifying an update interval, in seconds. See ",(0,d.jsx)(n.a,{href:"/docs/widgets#continuous-updates",children:"Continuous Updates Docs"})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"useProxy"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Some widgets make API requests to services that are not CORS-enabled. For these instances, you will need to route requests through a proxy, Dashy has a built in CORS-proxy, which you can use by setting this option to ",(0,d.jsx)(n.code,{children:"true"}),". Defaults to ",(0,d.jsx)(n.code,{children:"false"}),". See the ",(0,d.jsx)(n.a,{href:"/docs/widgets#proxying-requests",children:"Proxying Requests Docs"})," for more info"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"timeout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Request timeout in milliseconds, defaults to \xbd a second (",(0,d.jsx)(n.code,{children:"500"}),")"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"ignoreErrors"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Prevent an error message being displayed, if a network request or something else fails. Useful for false-positives"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"label"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Add custom label to a given widget. Useful for identification, if there are multiple of the same type of widget in a single section"})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"sectiondisplaydata-optional",children:[(0,d.jsx)(n.code,{children:"section.displayData"})," ",(0,d.jsx)(n.em,{children:"(optional)"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"sortBy"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The sort order for items within the current section. By default items are displayed in the order in which they are listed in within the config. The following sort options are supported: ",(0,d.jsx)(n.code,{children:"most-used"})," (most opened apps first), ",(0,d.jsx)(n.code,{children:"last-used"})," (the most recently used apps), ",(0,d.jsx)(n.code,{children:"alphabetical"}),", ",(0,d.jsx)(n.code,{children:"reverse-alphabetical"}),", ",(0,d.jsx)(n.code,{children:"random"})," and ",(0,d.jsx)(n.code,{children:"default"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"collapsed"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["If true, the section will be collapsed initially, and will need to be clicked to open. Useful for less regularly used, or very long sections. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"cutToHeight"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"By default, sections will fill available space. Set this option to true to match section height with content height"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"rows"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Height of the section, specified as the number of rows it should span vertically, e.g. ",(0,d.jsx)(n.code,{children:"2"}),". Defaults to ",(0,d.jsx)(n.code,{children:"1"}),". Max is ",(0,d.jsx)(n.code,{children:"5"}),". Applies to the default ",(0,d.jsx)(n.code,{children:"auto"})," layout; ignored by ",(0,d.jsx)(n.code,{children:"masonry"})," (where heights follow content) and the flex-based ",(0,d.jsx)(n.code,{children:"horizontal"}),"/",(0,d.jsx)(n.code,{children:"vertical"})," layouts."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"cols"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Width of the section, specified as the number of columns the section should span horizontally, e.g. ",(0,d.jsx)(n.code,{children:"2"}),". Defaults to ",(0,d.jsx)(n.code,{children:"1"}),". Max is ",(0,d.jsx)(n.code,{children:"5"}),". Will be clamped to the page's active column count so that a section never exceeds the available grid width."]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"itemSize"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Specify the size for items within this group, either ",(0,d.jsx)(n.code,{children:"small"}),", ",(0,d.jsx)(n.code,{children:"medium"})," or ",(0,d.jsx)(n.code,{children:"large"}),". Note that this will override any settings specified through the UI"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"color"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["A custom accent color for the section, as a hex code or HTML color (e.g. ",(0,d.jsx)(n.code,{children:"#fff"}),")"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"customStyles"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Custom CSS properties that should be applied to that section, e.g. ",(0,d.jsx)(n.code,{children:"border: 2px dashed #ff0000;"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"sectionLayout"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Specify which CSS layout will be used to responsively place items. Can be either ",(0,d.jsx)(n.code,{children:"auto"})," (which uses flex layout), or ",(0,d.jsx)(n.code,{children:"grid"}),". Defaults to ",(0,d.jsx)(n.code,{children:"auto"}),". Setting ",(0,d.jsx)(n.code,{children:"itemCountX"})," or ",(0,d.jsx)(n.code,{children:"itemCountY"})," below will also switch the section to grid layout automatically"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"itemCountX"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Number of items per row / horizontally. If not set, it will be calculated automatically based on available space. Setting this switches the section to grid layout. Must be a whole number between ",(0,d.jsx)(n.code,{children:"1"})," and ",(0,d.jsx)(n.code,{children:"12"}),"; values above ",(0,d.jsx)(n.code,{children:"8"})," rely on the grid's responsive column sizing (no explicit minimum-width rule)"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"itemCountY"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"number"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Number of explicit rows before items flow into implicit rows. Setting this switches the section to grid layout. Must be a whole number between ",(0,d.jsx)(n.code,{children:"1"})," and ",(0,d.jsx)(n.code,{children:"12"}),". Row heights size to their content (section heights follow content in the masonry layout)"]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current section will be visible to all users, except for those specified in this list"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"showForUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current section will be hidden from all users, except for those specified in this list"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForGuests"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current section will be visible for logged in users, but not for guests (see ",(0,d.jsx)(n.code,{children:"appConfig.enableGuestAccess"}),"). Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideForKeycloakUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current section will be visible to all keycloak users, except for those configured via these groups and roles. See ",(0,d.jsx)(n.code,{children:"hideForKeycloakUsers"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"showForKeycloakUsers"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"object"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current section will be hidden from all keycloak users, except for those configured via these groups and roles. See ",(0,d.jsx)(n.code,{children:"showForKeycloakUsers"})]})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"hideFromWorkspace"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"boolean"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["Current section will be visible in the default view but not in the Workspace view sidebar. Defaults to ",(0,d.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"sectionicon-and-sectionitemicon",children:[(0,d.jsx)(n.code,{children:"section.icon"})," and ",(0,d.jsx)(n.code,{children:"section.item.icon"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsx)(n.tbody,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"icon"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsxs)(n.td,{children:["The icon for a given item or section. ",(0,d.jsx)(n.br,{}),"See ",(0,d.jsx)(n.a,{href:"/docs/icons",children:"Icon Docs"})," for all available supported icon types, including: auto-fetched favicons, generative icons, emoji icons, home-lab service logos, font-awesome, simple-icons, material icons, selfh.st icons, and icons specified by URL"]})]})})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsxs)(n.h2,{id:"sectiondisplaydatahideforkeycloakusers-sectiondisplaydatashowforkeycloakusers-itemdisplaydatahideforkeycloakusers-and-itemdisplaydatashowforkeycloakusers",children:[(0,d.jsx)(n.code,{children:"section.displayData.hideForKeycloakUsers"}),", ",(0,d.jsx)(n.code,{children:"section.displayData.showForKeycloakUsers"}),", ",(0,d.jsx)(n.code,{children:"item.displayData.hideForKeycloakUsers"})," and ",(0,d.jsx)(n.code,{children:"item.displayData.showForKeycloakUsers"})]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,d.jsxs)(n.table,{children:[(0,d.jsx)(n.thead,{children:(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Field"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Type"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Required"})}),(0,d.jsx)(n.th,{children:(0,d.jsx)(n.strong,{children:"Description"})})]})}),(0,d.jsxs)(n.tbody,{children:[(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"groups"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current Section or Item will be hidden or shown based on the user having any of the groups in this list"})]}),(0,d.jsxs)(n.tr,{children:[(0,d.jsx)(n.td,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.code,{children:"roles"})})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.code,{children:"string[]"})}),(0,d.jsx)(n.td,{children:(0,d.jsx)(n.em,{children:"Optional"})}),(0,d.jsx)(n.td,{children:"Current Section or Item will be hidden or shown based on the user having any of the roles in this list"})]})]})]}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})}),"\n",(0,d.jsx)(n.hr,{}),"\n",(0,d.jsx)(n.h2,{id:"notes",children:"Notes"}),"\n",(0,d.jsx)(n.h3,{id:"editing-config-through-the-ui",children:"Editing Config through the UI"}),"\n",(0,d.jsxs)(n.p,{children:["Config can be modified directly through the UI, and then written to disk, or applied locally. This can be done wither with the raw config editor (introduced in V 0.6.5 / ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/pull/3",children:"#3"}),"), or the interactive editor (introduced in V 1.8.9 / ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/pull/298",children:"#298"}),")."]}),"\n ",(0,d.jsxs)(n.a,{href:"https://ibb.co/CzkyMNb",children:["\n ",(0,d.jsx)(n.b,{children:"Interactive Editor"}),(0,d.jsx)(n.br,{}),"\n ",(0,d.jsx)(n.img,{alt:"Interactive Editor demo",src:"https://user-images.githubusercontent.com/1862727/139543020-b0576d28-0830-476f-afc8-a815d4de6def.gif",width:"600"}),"\n "]}),"\n ",(0,d.jsx)(n.br,{}),"\n ",(0,d.jsxs)(n.a,{href:"https://ibb.co/zRv542H",children:["\n ",(0,d.jsx)(n.b,{children:"JSON Editor"}),(0,d.jsx)(n.br,{}),"\n ",(0,d.jsx)(n.img,{alt:"Config Editor demo",src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/assets/config-editor-demo.gif",width:"600"}),"\n "]}),"\n",(0,d.jsx)(n.h3,{id:"about-yaml",children:"About YAML"}),"\n",(0,d.jsxs)(n.p,{children:["If you're new to YAML, it's pretty straight-forward. The format is exactly the same as that of JSON, but instead of using curly braces, structure is denoted using whitespace. This ",(0,d.jsx)(n.a,{href:"https://linuxhandbook.com/yaml-basics/",children:"quick guide"})," should get you up to speed in a few minutes, for more advanced topics take a look at this ",(0,d.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/YAML",children:"Wikipedia article"}),"."]}),"\n",(0,d.jsx)(n.h3,{id:"config-saving-methods",children:"Config Saving Methods"}),"\n",(0,d.jsxs)(n.p,{children:["When updating the config through the JSON editor in the UI, you have two save options: ",(0,d.jsx)(n.strong,{children:"Local"})," or ",(0,d.jsx)(n.strong,{children:"Write to Disk"}),"."]}),"\n",(0,d.jsxs)(n.ul,{children:["\n",(0,d.jsx)(n.li,{children:"Changes saved locally will only be applied to the current user through the browser, and will not apply to other instances - you either need to use the cloud sync feature, or manually update the conf.yml file."}),"\n",(0,d.jsxs)(n.li,{children:["On the other-hand, if you choose to write changes to disk, then your main ",(0,d.jsx)(n.code,{children:"conf.yml"})," file will be updated, and changes will be applied to all users, and visible across all devices. For this functionality to work, you must be running Dashy with using the Docker container, or the Node server. A backup of your current configuration will also be saved in the same directory."]}),"\n"]}),"\n",(0,d.jsx)(n.h3,{id:"preventing-changes",children:"Preventing Changes"}),"\n",(0,d.jsxs)(n.p,{children:["If you have authentication set up, then any user who is not an admin (with ",(0,d.jsx)(n.code,{children:"type: admin"}),") will not be able to write changes to disk."]}),"\n",(0,d.jsxs)(n.p,{children:["You can also prevent changes from any user being written to disk, using ",(0,d.jsx)(n.code,{children:"preventWriteToDisk"}),". Or prevent any changes from being saved locally in browser storage, using ",(0,d.jsx)(n.code,{children:"preventLocalSave"}),"."]}),"\n",(0,d.jsxs)(n.p,{children:["To disable all UI config features, set ",(0,d.jsx)(n.code,{children:"disableConfiguration"}),". Alternatively you can disable UI config features for all non Admin users by setting ",(0,d.jsx)(n.code,{children:"disableConfigurationForNonAdmin"})," to true."]}),"\n",(0,d.jsx)(n.h3,{id:"example",children:"Example"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-yaml",children:"---\npageInfo:\n title: Home Lab\nsections: # An array of sections\n- name: Section 1 - Getting Started\n items: # An array of items\n - title: GitHub\n description: Source code and documentation on GitHub\n icon: fab fa-github\n url: https://github.com/Lissy93/dashy\n - title: Issues\n description: View currently open issues, or raise a new one\n icon: fas fa-bug\n url: https://github.com/Lissy93/dashy/issues\n - title: Demo\n description: A live demo\n icon: far fa-rocket\n url: https://dashy-demo-1.netlify.app\n- name: Section 2 - Local Services\n items:\n - title: Firewall\n icon: favicon\n url: http://192.168.1.1/\n - title: Game Server\n icon: https://i.ibb.co/710B3Yc/space-invader-x256.png\n url: http://192.168.130.1/\n"})}),"\n",(0,d.jsxs)(n.p,{children:["For more example config files, see: ",(0,d.jsx)(n.a,{href:"https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10",children:"this gist"})]}),"\n",(0,d.jsxs)(n.p,{children:["If you need any help, feel free to ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/new?assignees=Lissy93&labels=%F0%9F%A4%B7%E2%80%8D%E2%99%82%EF%B8%8F+Question&template=question.md&title=%5BQUESTION%5D",children:"Raise an Issue"})," or ",(0,d.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/discussions",children:"Start a Discussion"})]}),"\n",(0,d.jsx)(n.p,{children:"Happy Configuring \ud83e\udd13\ud83d\udd27"}),"\n",(0,d.jsx)(n.p,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.strong,{children:(0,d.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})})]})}function a(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,d.jsx)(n,{...e,children:(0,d.jsx)(h,{...e})}):h(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>c});var i=s(96540);const d={},r=i.createContext(d);function t(e){const n=i.useContext(r);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(d):e.components||d:t(e.components),i.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/4d54d076.ddbd54a2.js b/assets/js/4d54d076.089aab94.js similarity index 99% rename from assets/js/4d54d076.ddbd54a2.js rename to assets/js/4d54d076.089aab94.js index 0df90327..813da63d 100644 --- a/assets/js/4d54d076.ddbd54a2.js +++ b/assets/js/4d54d076.089aab94.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[1459],{2199(e,s,t){t.r(s),t.d(s,{assets:()=>l,contentTitle:()=>n,default:()=>c,frontMatter:()=>r,metadata:()=>o,toc:()=>h});const o=JSON.parse('{"id":"contributing","title":"Contributing","description":"First off, thank you for considering contributing towards Dashy! \ud83d\ude4c","source":"@site/docs/contributing.md","sourceDirName":".","slug":"/contributing","permalink":"/docs/contributing","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/contributing.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"*Dashy Showcase* \ud83c\udf1f","permalink":"/docs/showcase"},"next":{"title":"Developing","permalink":"/docs/developing"}}');var a=t(74848),i=t(28453);const r={},n="Contributing",l={},h=[{value:"Take a 2-minute survey",id:"take-a-2-minute-survey",level:2},{value:"Share your dashboard",id:"share-your-dashboard",level:2},{value:"Make a small donation",id:"make-a-small-donation",level:2},{value:"Enable Anonymous Bug Reports",id:"enable-anonymous-bug-reports",level:2},{value:"Add Translations",id:"add-translations",level:2},{value:"Submit a PR",id:"submit-a-pr",level:2},{value:"Improve the Docs",id:"improve-the-docs",level:2},{value:"Raise a bug",id:"raise-a-bug",level:2},{value:"Join the discussion",id:"join-the-discussion",level:2},{value:"Request a feature via BountySource",id:"request-a-feature-via-bountysource",level:2},{value:"Spread the word",id:"spread-the-word",level:2},{value:"Star, Upvote or Leave a Review",id:"star-upvote-or-leave-a-review",level:2},{value:"Follow for More",id:"follow-for-more",level:2},{value:"Contributors",id:"contributors",level:3},{value:"Star-Gazers Over Time",id:"star-gazers-over-time",level:3}];function d(e){const s={a:"a",code:"code",details:"details",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",summary:"summary",ul:"ul",...(0,i.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(s.header,{children:(0,a.jsx)(s.h1,{id:"contributing",children:"Contributing"})}),"\n",(0,a.jsxs)(s.p,{children:["First off, thank you for considering contributing towards Dashy! \ud83d\ude4c\nThere are several ways that you can help out, and any contributions, however small will always be very much appreciated.\nYou will be appropriately credited in the readme - huge thank you to ",(0,a.jsx)(s.a,{href:"/docs/credits",children:"everyone who has helped"})," so far \ud83d\udc9e"]}),"\n",(0,a.jsx)(s.h2,{id:"take-a-2-minute-survey",children:"Take a 2-minute survey"}),"\n",(0,a.jsx)(s.p,{children:"Help improve Dashy by taking a very short, 6-question survey. This will give me a better understanding of what is important to you, so that I can make Dashy better in the future :)"}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://survey.typeform.com/to/gl0L68ou",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Take_the-Survey-%231a86fd?style=for-the-badge&logo=buddy",alt:"Take the Survey"})})}),"\n",(0,a.jsx)(s.h2,{id:"share-your-dashboard",children:"Share your dashboard"}),"\n",(0,a.jsxs)(s.p,{children:["Dashy now has a ",(0,a.jsx)(s.a,{href:"/docs/showcase",children:"Showcase"})," where you can show off a screenshot of your dashboard, and get inspiration from other users (and I really love seeing how people are using Dashy). To ",(0,a.jsx)(s.a,{href:"/docs/showcase#submitting-your-dashboard",children:"submit your dashboard"}),", either open a PR or raise an issue."]}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/issues/new?assignees=&labels=%F0%9F%92%AF+Showcase&template=showcase-addition.yml&title=%5BSHOWCASE%5D+%3Ctitle%3E",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Add_your_Dashboard-Showcase-%238616ee?style=for-the-badge&logo=feathub&logoColor=8616ee",alt:"Add your Dashboard to the Showcase"})})}),"\n",(0,a.jsx)(s.h2,{id:"make-a-small-donation",children:"Make a small donation"}),"\n",(0,a.jsx)(s.p,{children:"Donations help to cover server costs, development time and caffeine ;)\nDon't feel any pressure to donate anything, as Dashy and my other projects will always be 100% free, for everyone, for ever."}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://github.com/sponsors/Lissy93",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Sponsor_on_GitHub-Lissy93-%23ff4dda?style=for-the-badge&logo=githubsponsors&logoColor=ff4dda",alt:"Sponsor Lissy93 on GitHub"})})}),"\n",(0,a.jsxs)(s.p,{children:["Sponsoring will give you several perks - for $1 / \xa30.75 per month, you'll get a sponsor badge on your profile, be credited on the Dashy's readme, with a link to your website/ profile/ socials, get priority support, have your feature ideas implemented, plus lots more. For more info, see ",(0,a.jsx)(s.a,{href:"https://github.com/sponsors/Lissy93",children:"@Lissy93's Sponsor Page"}),"."]}),"\n",(0,a.jsxs)(s.details,{children:["\n\t",(0,a.jsx)(s.summary,{children:"You can also send a one-off small contribution using crypto"}),"\n\t[![Donate with BTC](https://en.cryptobadges.io/badge/big/3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC)](https://en.cryptobadges.io/donate/3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC)[![Donate with Ethereum](https://en.cryptobadges.io/badge/big/0x0fc98cBf8bea932B4470C46C0FbE1ed1f6765017)](https://en.cryptobadges.io/donate/0x0fc98cBf8bea932B4470C46C0FbE1ed1f6765017)\n",(0,a.jsxs)(s.ul,{children:["\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"BTC"}),": ",(0,a.jsx)(s.code,{children:"3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC"})]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"ETH"}),": ",(0,a.jsx)(s.code,{children:"0x0fc98cBf8bea932B4470C46C0FbE1ed1f6765017"})," / ",(0,a.jsx)(s.code,{children:"aliciasykes.eth"})]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"XMR"}),": ",(0,a.jsx)(s.code,{children:"471KZdxb6N63aABR4WYwMRjTVkc1p1x7wGsUTEF7AMYzL8L94A5pCuYWkosgJQ5Ze8Y2PscVCGZFJa3hDPg6MaDq47GUm8r"})]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"LTC"}),": ",(0,a.jsx)(s.code,{children:"MAuck6Ea1qaNihwKfXutkR1R6BorMth86H"})]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"ZEC"}),": ",(0,a.jsx)(s.code,{children:"t1bw1SefijsXRDQVxC9w64XsRK8hBhtQohQ"})]}),"\n"]}),"\n"]}),"\n",(0,a.jsx)(s.h2,{id:"enable-anonymous-bug-reports",children:"Enable Anonymous Bug Reports"}),"\n",(0,a.jsxs)(s.p,{children:["Bug reports helps me to discover bugs I was unaware of, and then fix them, in order to make Dashy more reliable long term. This is a simple, yet really helpful step you can take to help improve Dashy. ",(0,a.jsx)(s.a,{href:"https://github.com/getsentry/sentry",children:"Sentry"})," is an open source error tracking and performance monitoring tool, which enables the identification any errors which occur in the production app (only if you enable it)."]}),"\n",(0,a.jsx)(s.p,{children:"To enable error reporting:"}),"\n",(0,a.jsx)(s.pre,{children:(0,a.jsx)(s.code,{className:"language-yaml",children:"appConfig:\n enableErrorReporting: true\n"})}),"\n",(0,a.jsxs)(s.p,{children:["All reporting is ",(0,a.jsx)(s.strong,{children:"disabled"})," by default, and no data will ever be sent to any external endpoint without your explicit consent. All statistics are anonymized and stored securely. For more about privacy and security, see the ",(0,a.jsx)(s.a,{href:"https://sentry.io/security/",children:"Sentry Security Docs"}),"."]}),"\n",(0,a.jsx)(s.h2,{id:"add-translations",children:"Add Translations"}),"\n",(0,a.jsxs)(s.p,{children:["If you speak another language, then adding translations will help make Dashy available to non-native English speakers. This is a very quick and easy task, as all application text is located in ",(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/assets/locales/en.json",children:(0,a.jsx)(s.code,{children:"locales/en.json"})}),", so adding a new language is as simple as copying this file and translating the values. You don't have to translate it all, as any missing attributes will just fallback to English. For a full tutorial, see the ",(0,a.jsx)(s.a,{href:"/docs/multi-language-support",children:"Multi-Language Support Docs"}),"."]}),"\n",(0,a.jsx)(s.h2,{id:"submit-a-pr",children:"Submit a PR"}),"\n",(0,a.jsxs)(s.p,{children:["Contributing to the code or docs is super helpful. You can fix a bug, add a new feature or improve an existing one. If you've built your own custom widget, theme or view, consider sharing it in a PR. I've written ",(0,a.jsx)(s.a,{href:"/docs/development-guides",children:"several guides"})," to help you get started, and the steps for setting up the development environment are outlined in the ",(0,a.jsx)(s.a,{href:"/docs/developing",children:"Development Docs"}),". Feel free to ask if you have any questions."]}),"\n",(0,a.jsx)(s.h2,{id:"improve-the-docs",children:"Improve the Docs"}),"\n",(0,a.jsxs)(s.p,{children:["Found a typo, or something that isn't as clear as it could be? Maybe I've missed something off altogether, or you hit a roadblock that took you a while to figure out. Submitting a pull request to add to or improve the documentation will help future users get Dashy up and running more easily.\nAll content is located either in the ",(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/README",children:(0,a.jsx)(s.code,{children:"./README.md"})})," or ",(0,a.jsx)(s.a,{href:"/docs",children:(0,a.jsx)(s.code,{children:"/docs/"})})," directory, and synced to the Wiki and website using a GH ",(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/actions/workflows/wiki-sync.yml",children:"action"}),"."]}),"\n",(0,a.jsx)(s.h2,{id:"raise-a-bug",children:"Raise a bug"}),"\n",(0,a.jsx)(s.p,{children:"If you've found a bug, then please do raise it as an issue. This will help me know if there's something that needs fixing. Try and include as much detail as possible, such as your environment, steps to reproduce, any console output and maybe an example screenshot or recording if necessary."}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/issues/new?assignees=lissy93&labels=%F0%9F%90%9B+Bug&template=bug.yml&title=%5BBUG%5D+%3Ctitle%3E",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Raise_a-Bug-%23dc2d76?style=for-the-badge&logo=dependabot",alt:"Raise a Bug"})})}),"\n",(0,a.jsx)(s.h2,{id:"join-the-discussion",children:"Join the discussion"}),"\n",(0,a.jsx)(s.p,{children:"I've enabled the discussion feature on GitHub, here you can share tips and tricks, useful information, or your dashboard. You can also ask questions, and offer basic support to other users."}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/discussions",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Join_the-Discussion-%23ffd000?style=for-the-badge&logo=livechat",alt:"Join the Discussion on GitHub"})})}),"\n",(0,a.jsx)(s.h2,{id:"request-a-feature-via-bountysource",children:"Request a feature via BountySource"}),"\n",(0,a.jsx)(s.p,{children:"BountySource is a platform for sponsoring the development of certain features on open source projects. If there is a feature you'd like implemented into Dashy, but either isn't high enough priority or is deemed to be more work than it's worth, then you can instead contribute a bounty towards it's development. You won't pay a penny until your proposal is fully built, and you are satisfied with the result. This helps support the developers, and makes Dashy better for everyone."}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://www.bountysource.com/teams/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/BountySource-Dashy-%23F67909?style=for-the-badge&logo=openbugbounty",alt:"Request a Feature on BountySource"})})}),"\n",(0,a.jsx)(s.h2,{id:"spread-the-word",children:"Spread the word"}),"\n",(0,a.jsx)(s.p,{children:"Dashy is still a relatively young project, and as such not many people know of it. It would be great to see more users, and so it would be awesome if you could consider sharing with your friends or on social platforms."}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.a,{href:"https://mastodon.social/?text=Check%20out%20Dashy%2C%20the%20privacy-friendly%2C%20self-hosted%20startpage%20for%20organizing%20your%20life%3A%20https%3A%2F%2Fgithub.com%2FLissy93%2Fdashy%20-%20By%20%40lissy93%40mastodon.social",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Mastodon-%232b90d9?style=flat-square&logo=mastodon",alt:"Share Dashy on Mastodon"})}),"\n",(0,a.jsx)(s.a,{href:"http://www.reddit.com/submit?url=https://github.com/Lissy93/dashy&title=Dashy%20-%20The%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Reddit-%23FF5700?style=flat-square&logo=reddit",alt:"Share Dashy on Reddit"})}),"\n",(0,a.jsx)(s.a,{href:"https://twitter.com/intent/tweet?url=https://github.com/lissy93/dashy&text=Check%20out%20Dashy%20by%20@Lissy_Sykes,%20the%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Twitter-%231DA1F2?style=flat-square&logo=twitter",alt:"Share Dashy on Twitter"})}),"\n",(0,a.jsx)(s.a,{href:"https://www.facebook.com/sharer/sharer.php?u=https://github.com/lissy93/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Facebook-%234267B2?style=flat-square&logo=facebook",alt:"Share Dashy on Facebook"})}),"\n",(0,a.jsx)(s.a,{href:"https://www.linkedin.com/shareArticle?mini=true&url=https://github.com/lissy93/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-LinkedIn-%230077b5?style=flat-square&logo=linkedin",alt:"Share Dashy on LinkedIn"})}),"\n",(0,a.jsx)(s.a,{href:"https://pinterest.com/pin/create/button/?url=https://github.com/lissy93/dashy&media=https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/1-home-lab-material.png&description=Check%20out%20Dashy,%20the%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Pinterest-%23E60023?style=flat-square&logo=pinterest",alt:"Share Dashy on Pinterest"})}),"\n",(0,a.jsx)(s.a,{href:"https://vk.com/share.php?url=https%3A%2F%2Fgithub.com%2Flissy93%2Fdashy%2F&title=Check%20out%20Dashy%20-%20The%20Self-Hosted%20Dashboard%20for%20your%20Homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-VK-%234C75A3?style=flat-square&logo=vk",alt:"Share Dashy on VK"})}),"\n",(0,a.jsx)(s.a,{href:"viber://forward?text=https%3A%2F%2Fgithub.com%2Flissy93%2Fdashy%0ACheck%20out%20Dashy%2C%20the%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Viber-%238176d6?style=flat-square&logo=viber",alt:"Share Dashy via Viber"})}),"\n",(0,a.jsx)(s.a,{href:"https://t.me/share/url?url=https%3A%2F%2Fgithub.com%2Flissy93%2Fdashy&text=Check%20out%20Dashy%2C%20the%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Telegram-%230088cc?style=flat-square&logo=telegram",alt:"Share Dashy via Telegram"})}),"\n",(0,a.jsx)(s.a,{href:"mailto:info@example.com?&subject=Check%20out%20Dashy%20-%20The%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80&cc=&bcc=&body=https://github.com/lissy93/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Email-%238A90C7?style=flat-square&logo=protonmail",alt:"Share Dashy via Email"})})]}),"\n",(0,a.jsx)(s.h2,{id:"star-upvote-or-leave-a-review",children:"Star, Upvote or Leave a Review"}),"\n",(0,a.jsx)(s.p,{children:"Dashy is on the following platforms, and if you could spare a few seconds to give it an upvote or review, this will also help new users discover Dashy"}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.a,{href:"https://www.producthunt.com/posts/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Review-ProductHunt-%23b74424?style=flat-square&logo=producthunt",alt:"ProductHunt"})}),"\n",(0,a.jsx)(s.a,{href:"https://alternativeto.net/software/dashy/about/",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Review-AlternativeTo-%235581a6?style=flat-square&logo=abletonlive",alt:"AlternativeTo"})}),"\n",(0,a.jsx)(s.a,{href:"https://www.slant.co/improve/topics/27783/viewpoints/1/~self-hosted-homelab-startpage~dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Review-Slant-%2346a1df?style=flat-square&logo=capacitor",alt:"Slant"})}),"\n",(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/stargazers",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/github/stars/Lissy93/Dashy?color=ba96d6&label=Star%20-%20GitHub&logo=github&style=flat-square",alt:"Star on GitHub"})}),"\n",(0,a.jsx)(s.a,{href:"https://hub.docker.com/r/lissy93/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/docker/stars/lissy93/dashy?color=4cb6e0&label=Star%20-%20Docker&logo=docker&style=flat-square",alt:"Star on DockerHub"})})]}),"\n",(0,a.jsx)(s.h2,{id:"follow-for-more",children:"Follow for More"}),"\n",(0,a.jsx)(s.p,{children:"If you've enjoyed Dashy, you can follow the me to get updates about other projects that I am working on."}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.a,{href:"https://twitter.com/Lissy_Sykes",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/twitter/follow/Lissy_Sykes?style=social&logo=twitter",alt:"Alicia Sykes on Twitter"})}),"\n",(0,a.jsx)(s.a,{href:"https://github.com/Lissy93",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/github/followers/lissy93?label=Lissy93&style=social",alt:"Alicia Sykes on GitHub"})}),"\n",(0,a.jsx)(s.a,{href:"https://mastodon.social/web/accounts/1032965",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/mastodon/follow/1032965?domain=https%3A%2F%2Fmastodon.social",alt:"Alicia Sykes on Mastodon"})}),"\n",(0,a.jsx)(s.a,{href:"https://keybase.io/aliciasykes",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/aliciasykes--lightgrey?style=social&logo=Keybase",alt:"Alicia Sykes on Keybase"})}),"\n",(0,a.jsx)(s.a,{href:"https://aliciasykes.com",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/aliciasykes.com--lightgrey?style=social&logo=Tencent%20QQ",alt:"Alicia Sykes's Website"})}),"\n",(0,a.jsx)(s.a,{href:"https://notes.aliciasykes.com/",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Blog--lightgrey?style=social&logo=micro.blog",alt:"Alicia Sykes's Blog"})}),"\n",(0,a.jsx)(s.a,{href:"https://keybase.io/aliciasykes/pgp_keys.asc",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/PGP--lightgrey?style=social&logo=Let%E2%80%99s%20Encrypt",alt:"Alicia Sykes's PGP"})})]}),"\n",(0,a.jsxs)(s.p,{children:["If you like, you could also consider ",(0,a.jsx)(s.a,{href:"https://notes.aliciasykes.com/subscribe",children:"subscribing to my mailing list"})," for occasional blog post updates."]}),"\n",(0,a.jsx)(s.hr,{}),"\n",(0,a.jsx)(s.h3,{id:"contributors",children:"Contributors"}),"\n",(0,a.jsxs)(s.p,{children:["For a full list of Dashy's contributors, see the ",(0,a.jsx)(s.a,{href:"/docs/credits",children:"Credits Page"})]}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"/docs/credits",children:(0,a.jsx)(s.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/assets/CONTRIBUTORS.svg",alt:"Auto-generated contributors"})})}),"\n",(0,a.jsx)(s.h3,{id:"star-gazers-over-time",children:"Star-Gazers Over Time"}),"\n",(0,a.jsxs)(s.p,{children:["](",(0,a.jsx)(s.a,{href:"https://seladb.github.io/StarTrack-js/#/preload?r=lissy93,dashy",children:"https://seladb.github.io/StarTrack-js/#/preload?r=lissy93,dashy"}),")"]})]})}function c(e={}){const{wrapper:s}={...(0,i.R)(),...e.components};return s?(0,a.jsx)(s,{...e,children:(0,a.jsx)(d,{...e})}):d(e)}},28453(e,s,t){t.d(s,{R:()=>r,x:()=>n});var o=t(96540);const a={},i=o.createContext(a);function r(e){const s=o.useContext(i);return o.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function n(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:r(e.components),o.createElement(i.Provider,{value:s},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[1459],{2199(e,s,t){t.r(s),t.d(s,{assets:()=>l,contentTitle:()=>n,default:()=>c,frontMatter:()=>r,metadata:()=>o,toc:()=>h});const o=JSON.parse('{"id":"contributing","title":"Contributing","description":"First off, thank you for considering contributing towards Dashy! \ud83d\ude4c","source":"@site/docs/contributing.md","sourceDirName":".","slug":"/contributing","permalink":"/docs/contributing","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/contributing.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"*Dashy Showcase* \ud83c\udf1f","permalink":"/docs/showcase"},"next":{"title":"Developing","permalink":"/docs/developing"}}');var a=t(74848),i=t(28453);const r={},n="Contributing",l={},h=[{value:"Take a 2-minute survey",id:"take-a-2-minute-survey",level:2},{value:"Share your dashboard",id:"share-your-dashboard",level:2},{value:"Make a small donation",id:"make-a-small-donation",level:2},{value:"Enable Anonymous Bug Reports",id:"enable-anonymous-bug-reports",level:2},{value:"Add Translations",id:"add-translations",level:2},{value:"Submit a PR",id:"submit-a-pr",level:2},{value:"Improve the Docs",id:"improve-the-docs",level:2},{value:"Raise a bug",id:"raise-a-bug",level:2},{value:"Join the discussion",id:"join-the-discussion",level:2},{value:"Request a feature via BountySource",id:"request-a-feature-via-bountysource",level:2},{value:"Spread the word",id:"spread-the-word",level:2},{value:"Star, Upvote or Leave a Review",id:"star-upvote-or-leave-a-review",level:2},{value:"Follow for More",id:"follow-for-more",level:2},{value:"Contributors",id:"contributors",level:3},{value:"Star-Gazers Over Time",id:"star-gazers-over-time",level:3}];function d(e){const s={a:"a",code:"code",details:"details",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",summary:"summary",ul:"ul",...(0,i.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(s.header,{children:(0,a.jsx)(s.h1,{id:"contributing",children:"Contributing"})}),"\n",(0,a.jsxs)(s.p,{children:["First off, thank you for considering contributing towards Dashy! \ud83d\ude4c\nThere are several ways that you can help out, and any contributions, however small will always be very much appreciated.\nYou will be appropriately credited in the readme - huge thank you to ",(0,a.jsx)(s.a,{href:"/docs/credits",children:"everyone who has helped"})," so far \ud83d\udc9e"]}),"\n",(0,a.jsx)(s.h2,{id:"take-a-2-minute-survey",children:"Take a 2-minute survey"}),"\n",(0,a.jsx)(s.p,{children:"Help improve Dashy by taking a very short, 6-question survey. This will give me a better understanding of what is important to you, so that I can make Dashy better in the future :)"}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://survey.typeform.com/to/gl0L68ou",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Take_the-Survey-%231a86fd?style=for-the-badge&logo=buddy",alt:"Take the Survey"})})}),"\n",(0,a.jsx)(s.h2,{id:"share-your-dashboard",children:"Share your dashboard"}),"\n",(0,a.jsxs)(s.p,{children:["Dashy now has a ",(0,a.jsx)(s.a,{href:"/docs/showcase",children:"Showcase"})," where you can show off a screenshot of your dashboard, and get inspiration from other users (and I really love seeing how people are using Dashy). To ",(0,a.jsx)(s.a,{href:"/docs/showcase#submitting-your-dashboard",children:"submit your dashboard"}),", either open a PR or raise an issue."]}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/issues/new?assignees=&labels=%F0%9F%92%AF+Showcase&template=showcase-addition.yml&title=%5BSHOWCASE%5D+%3Ctitle%3E",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Add_your_Dashboard-Showcase-%238616ee?style=for-the-badge&logo=feathub&logoColor=8616ee",alt:"Add your Dashboard to the Showcase"})})}),"\n",(0,a.jsx)(s.h2,{id:"make-a-small-donation",children:"Make a small donation"}),"\n",(0,a.jsx)(s.p,{children:"Donations help to cover server costs, development time and caffeine ;)\nDon't feel any pressure to donate anything, as Dashy and my other projects will always be 100% free, for everyone, for ever."}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://github.com/sponsors/Lissy93",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Sponsor_on_GitHub-Lissy93-%23ff4dda?style=for-the-badge&logo=githubsponsors&logoColor=ff4dda",alt:"Sponsor Lissy93 on GitHub"})})}),"\n",(0,a.jsxs)(s.p,{children:["Sponsoring will give you several perks - for $1 / \xa30.75 per month, you'll get a sponsor badge on your profile, be credited on the Dashy's readme, with a link to your website/ profile/ socials, get priority support, have your feature ideas implemented, plus lots more. For more info, see ",(0,a.jsx)(s.a,{href:"https://github.com/sponsors/Lissy93",children:"@Lissy93's Sponsor Page"}),"."]}),"\n",(0,a.jsxs)(s.details,{children:["\n\t",(0,a.jsx)(s.summary,{children:"You can also send a one-off small contribution using crypto"}),"\n\t[![Donate with BTC](https://en.cryptobadges.io/badge/big/3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC)](https://en.cryptobadges.io/donate/3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC)[![Donate with Ethereum](https://en.cryptobadges.io/badge/big/0x0fc98cBf8bea932B4470C46C0FbE1ed1f6765017)](https://en.cryptobadges.io/donate/0x0fc98cBf8bea932B4470C46C0FbE1ed1f6765017)\n",(0,a.jsxs)(s.ul,{children:["\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"BTC"}),": ",(0,a.jsx)(s.code,{children:"3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC"})]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"ETH"}),": ",(0,a.jsx)(s.code,{children:"0x0fc98cBf8bea932B4470C46C0FbE1ed1f6765017"})," / ",(0,a.jsx)(s.code,{children:"aliciasykes.eth"})]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"XMR"}),": ",(0,a.jsx)(s.code,{children:"471KZdxb6N63aABR4WYwMRjTVkc1p1x7wGsUTEF7AMYzL8L94A5pCuYWkosgJQ5Ze8Y2PscVCGZFJa3hDPg6MaDq47GUm8r"})]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"LTC"}),": ",(0,a.jsx)(s.code,{children:"MAuck6Ea1qaNihwKfXutkR1R6BorMth86H"})]}),"\n",(0,a.jsxs)(s.li,{children:[(0,a.jsx)(s.strong,{children:"ZEC"}),": ",(0,a.jsx)(s.code,{children:"t1bw1SefijsXRDQVxC9w64XsRK8hBhtQohQ"})]}),"\n"]}),"\n"]}),"\n",(0,a.jsx)(s.h2,{id:"enable-anonymous-bug-reports",children:"Enable Anonymous Bug Reports"}),"\n",(0,a.jsxs)(s.p,{children:["Bug reports helps me to discover bugs I was unaware of, and then fix them, in order to make Dashy more reliable long term. This is a simple, yet really helpful step you can take to help improve Dashy. ",(0,a.jsx)(s.a,{href:"https://github.com/getsentry/sentry",children:"Sentry"})," is an open source error tracking and performance monitoring tool, which enables the identification any errors which occur in the production app (only if you enable it)."]}),"\n",(0,a.jsx)(s.p,{children:"To enable error reporting:"}),"\n",(0,a.jsx)(s.pre,{children:(0,a.jsx)(s.code,{className:"language-yaml",children:"appConfig:\n enableErrorReporting: true\n"})}),"\n",(0,a.jsxs)(s.p,{children:["All reporting is ",(0,a.jsx)(s.strong,{children:"disabled"})," by default, and no data will ever be sent to any external endpoint without your explicit consent. All statistics are anonymized and stored securely. For more about privacy and security, see the ",(0,a.jsx)(s.a,{href:"https://sentry.io/security/",children:"Sentry Security Docs"}),"."]}),"\n",(0,a.jsx)(s.h2,{id:"add-translations",children:"Add Translations"}),"\n",(0,a.jsxs)(s.p,{children:["If you speak another language, then adding translations will help make Dashy available to non-native English speakers. This is a very quick and easy task, as all application text is located in ",(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/assets/locales/en.json",children:(0,a.jsx)(s.code,{children:"locales/en.json"})}),", so adding a new language is as simple as copying this file and translating the values. You don't have to translate it all, as any missing attributes will just fallback to English. For a full tutorial, see the ",(0,a.jsx)(s.a,{href:"/docs/multi-language-support",children:"Multi-Language Support Docs"}),"."]}),"\n",(0,a.jsx)(s.h2,{id:"submit-a-pr",children:"Submit a PR"}),"\n",(0,a.jsxs)(s.p,{children:["Contributing to the code or docs is super helpful. You can fix a bug, add a new feature or improve an existing one. If you've built your own custom widget, theme or view, consider sharing it in a PR. I've written ",(0,a.jsx)(s.a,{href:"/docs/development-guides",children:"several guides"})," to help you get started, and the steps for setting up the development environment are outlined in the ",(0,a.jsx)(s.a,{href:"/docs/developing",children:"Development Docs"}),". Feel free to ask if you have any questions."]}),"\n",(0,a.jsx)(s.h2,{id:"improve-the-docs",children:"Improve the Docs"}),"\n",(0,a.jsxs)(s.p,{children:["Found a typo, or something that isn't as clear as it could be? Maybe I've missed something off altogether, or you hit a roadblock that took you a while to figure out. Submitting a pull request to add to or improve the documentation will help future users get Dashy up and running more easily.\nAll content is located either in the ",(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/README",children:(0,a.jsx)(s.code,{children:"./README.md"})})," or ",(0,a.jsx)(s.a,{href:"/docs",children:(0,a.jsx)(s.code,{children:"/docs/"})})," directory, and synced to the Wiki and website using a GH ",(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/actions/workflows/wiki-sync.yml",children:"action"}),"."]}),"\n",(0,a.jsx)(s.h2,{id:"raise-a-bug",children:"Raise a bug"}),"\n",(0,a.jsx)(s.p,{children:"If you've found a bug, then please do raise it as an issue. This will help me know if there's something that needs fixing. Try and include as much detail as possible, such as your environment, steps to reproduce, any console output and maybe an example screenshot or recording if necessary."}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/issues/new?assignees=lissy93&labels=%F0%9F%90%9B+Bug&template=bug.yml&title=%5BBUG%5D+%3Ctitle%3E",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Raise_a-Bug-%23dc2d76?style=for-the-badge&logo=dependabot",alt:"Raise a Bug"})})}),"\n",(0,a.jsx)(s.h2,{id:"join-the-discussion",children:"Join the discussion"}),"\n",(0,a.jsx)(s.p,{children:"I've enabled the discussion feature on GitHub, here you can share tips and tricks, useful information, or your dashboard. You can also ask questions, and offer basic support to other users."}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/discussions",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Join_the-Discussion-%23ffd000?style=for-the-badge&logo=livechat",alt:"Join the Discussion on GitHub"})})}),"\n",(0,a.jsx)(s.h2,{id:"request-a-feature-via-bountysource",children:"Request a feature via BountySource"}),"\n",(0,a.jsx)(s.p,{children:"BountySource is a platform for sponsoring the development of certain features on open source projects. If there is a feature you'd like implemented into Dashy, but either isn't high enough priority or is deemed to be more work than it's worth, then you can instead contribute a bounty towards it's development. You won't pay a penny until your proposal is fully built, and you are satisfied with the result. This helps support the developers, and makes Dashy better for everyone."}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"https://www.bountysource.com/teams/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/BountySource-Dashy-%23F67909?style=for-the-badge&logo=openbugbounty",alt:"Request a Feature on BountySource"})})}),"\n",(0,a.jsx)(s.h2,{id:"spread-the-word",children:"Spread the word"}),"\n",(0,a.jsx)(s.p,{children:"Dashy is still a relatively young project, and as such not many people know of it. It would be great to see more users, and so it would be awesome if you could consider sharing with your friends or on social platforms."}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.a,{href:"https://mastodon.social/?text=Check%20out%20Dashy%2C%20the%20privacy-friendly%2C%20self-hosted%20startpage%20for%20organizing%20your%20life%3A%20https%3A%2F%2Fgithub.com%2FLissy93%2Fdashy%20-%20By%20%40lissy93%40mastodon.social",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Mastodon-%232b90d9?style=flat-square&logo=mastodon",alt:"Share Dashy on Mastodon"})}),"\n",(0,a.jsx)(s.a,{href:"http://www.reddit.com/submit?url=https://github.com/Lissy93/dashy&title=Dashy%20-%20The%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Reddit-%23FF5700?style=flat-square&logo=reddit",alt:"Share Dashy on Reddit"})}),"\n",(0,a.jsx)(s.a,{href:"https://twitter.com/intent/tweet?url=https://github.com/lissy93/dashy&text=Check%20out%20Dashy%20by%20@Lissy_Sykes,%20the%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Twitter-%231DA1F2?style=flat-square&logo=twitter",alt:"Share Dashy on Twitter"})}),"\n",(0,a.jsx)(s.a,{href:"https://www.facebook.com/sharer/sharer.php?u=https://github.com/lissy93/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Facebook-%234267B2?style=flat-square&logo=facebook",alt:"Share Dashy on Facebook"})}),"\n",(0,a.jsx)(s.a,{href:"https://www.linkedin.com/shareArticle?mini=true&url=https://github.com/lissy93/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-LinkedIn-%230077b5?style=flat-square&logo=linkedin",alt:"Share Dashy on LinkedIn"})}),"\n",(0,a.jsx)(s.a,{href:"https://pinterest.com/pin/create/button/?url=https://github.com/lissy93/dashy&media=https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/1-home-lab-material.png&description=Check%20out%20Dashy,%20the%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Pinterest-%23E60023?style=flat-square&logo=pinterest",alt:"Share Dashy on Pinterest"})}),"\n",(0,a.jsx)(s.a,{href:"https://vk.com/share.php?url=https%3A%2F%2Fgithub.com%2Flissy93%2Fdashy%2F&title=Check%20out%20Dashy%20-%20The%20Self-Hosted%20Dashboard%20for%20your%20Homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-VK-%234C75A3?style=flat-square&logo=vk",alt:"Share Dashy on VK"})}),"\n",(0,a.jsx)(s.a,{href:"viber://forward?text=https%3A%2F%2Fgithub.com%2Flissy93%2Fdashy%0ACheck%20out%20Dashy%2C%20the%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Viber-%238176d6?style=flat-square&logo=viber",alt:"Share Dashy via Viber"})}),"\n",(0,a.jsx)(s.a,{href:"https://t.me/share/url?url=https%3A%2F%2Fgithub.com%2Flissy93%2Fdashy&text=Check%20out%20Dashy%2C%20the%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Telegram-%230088cc?style=flat-square&logo=telegram",alt:"Share Dashy via Telegram"})}),"\n",(0,a.jsx)(s.a,{href:"mailto:info@example.com?&subject=Check%20out%20Dashy%20-%20The%20self-hosted%20dashboard%20for%20your%20homelab%20%F0%9F%9A%80&cc=&bcc=&body=https://github.com/lissy93/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Share-Email-%238A90C7?style=flat-square&logo=protonmail",alt:"Share Dashy via Email"})})]}),"\n",(0,a.jsx)(s.h2,{id:"star-upvote-or-leave-a-review",children:"Star, Upvote or Leave a Review"}),"\n",(0,a.jsx)(s.p,{children:"Dashy is on the following platforms, and if you could spare a few seconds to give it an upvote or review, this will also help new users discover Dashy"}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.a,{href:"https://www.producthunt.com/posts/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Review-ProductHunt-%23b74424?style=flat-square&logo=producthunt",alt:"ProductHunt"})}),"\n",(0,a.jsx)(s.a,{href:"https://alternativeto.net/software/dashy/about/",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Review-AlternativeTo-%235581a6?style=flat-square&logo=abletonlive",alt:"AlternativeTo"})}),"\n",(0,a.jsx)(s.a,{href:"https://www.slant.co/improve/topics/27783/viewpoints/1/~self-hosted-homelab-startpage~dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Review-Slant-%2346a1df?style=flat-square&logo=capacitor",alt:"Slant"})}),"\n",(0,a.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/stargazers",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/github/stars/Lissy93/Dashy?color=ba96d6&label=Star%20-%20GitHub&logo=github&style=flat-square",alt:"Star on GitHub"})}),"\n",(0,a.jsx)(s.a,{href:"https://hub.docker.com/r/lissy93/dashy",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/docker/stars/lissy93/dashy?color=4cb6e0&label=Star%20-%20Docker&logo=docker&style=flat-square",alt:"Star on DockerHub"})})]}),"\n",(0,a.jsx)(s.h2,{id:"follow-for-more",children:"Follow for More"}),"\n",(0,a.jsx)(s.p,{children:"If you've enjoyed Dashy, you can follow the me to get updates about other projects that I am working on."}),"\n",(0,a.jsxs)(s.p,{children:[(0,a.jsx)(s.a,{href:"https://twitter.com/Lissy_Sykes",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/twitter/follow/Lissy_Sykes?style=social&logo=twitter",alt:"Alicia Sykes on Twitter"})}),"\n",(0,a.jsx)(s.a,{href:"https://github.com/Lissy93",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/github/followers/lissy93?label=Lissy93&style=social",alt:"Alicia Sykes on GitHub"})}),"\n",(0,a.jsx)(s.a,{href:"https://mastodon.social/web/accounts/1032965",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/mastodon/follow/1032965?domain=https%3A%2F%2Fmastodon.social",alt:"Alicia Sykes on Mastodon"})}),"\n",(0,a.jsx)(s.a,{href:"https://keybase.io/aliciasykes",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/aliciasykes--lightgrey?style=social&logo=Keybase",alt:"Alicia Sykes on Keybase"})}),"\n",(0,a.jsx)(s.a,{href:"https://aliciasykes.com",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/aliciasykes.com--lightgrey?style=social&logo=Tencent%20QQ",alt:"Alicia Sykes's Website"})}),"\n",(0,a.jsx)(s.a,{href:"https://notes.aliciasykes.com/",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/Blog--lightgrey?style=social&logo=micro.blog",alt:"Alicia Sykes's Blog"})}),"\n",(0,a.jsx)(s.a,{href:"https://keybase.io/aliciasykes/pgp_keys.asc",children:(0,a.jsx)(s.img,{src:"https://img.shields.io/badge/PGP--lightgrey?style=social&logo=Let%E2%80%99s%20Encrypt",alt:"Alicia Sykes's PGP"})})]}),"\n",(0,a.jsxs)(s.p,{children:["If you like, you could also consider ",(0,a.jsx)(s.a,{href:"https://notes.aliciasykes.com/subscribe",children:"subscribing to my mailing list"})," for occasional blog post updates."]}),"\n",(0,a.jsx)(s.hr,{}),"\n",(0,a.jsx)(s.h3,{id:"contributors",children:"Contributors"}),"\n",(0,a.jsxs)(s.p,{children:["For a full list of Dashy's contributors, see the ",(0,a.jsx)(s.a,{href:"/docs/credits",children:"Credits Page"})]}),"\n",(0,a.jsx)(s.p,{children:(0,a.jsx)(s.a,{href:"/docs/credits",children:(0,a.jsx)(s.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/assets/CONTRIBUTORS.svg",alt:"Auto-generated contributors"})})}),"\n",(0,a.jsx)(s.h3,{id:"star-gazers-over-time",children:"Star-Gazers Over Time"}),"\n",(0,a.jsxs)(s.p,{children:["](",(0,a.jsx)(s.a,{href:"https://seladb.github.io/StarTrack-js/#/preload?r=lissy93,dashy",children:"https://seladb.github.io/StarTrack-js/#/preload?r=lissy93,dashy"}),")"]})]})}function c(e={}){const{wrapper:s}={...(0,i.R)(),...e.components};return s?(0,a.jsx)(s,{...e,children:(0,a.jsx)(d,{...e})}):d(e)}},28453(e,s,t){t.d(s,{R:()=>r,x:()=>n});var o=t(96540);const a={},i=o.createContext(a);function r(e){const s=o.useContext(i);return o.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function n(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:r(e.components),o.createElement(i.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/52e2a80b.8317f95c.js b/assets/js/52e2a80b.fe45a810.js similarity index 98% rename from assets/js/52e2a80b.8317f95c.js rename to assets/js/52e2a80b.fe45a810.js index c2ccb80d..7d5f554c 100644 --- a/assets/js/52e2a80b.8317f95c.js +++ b/assets/js/52e2a80b.fe45a810.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[1306],{91264(e,n,s){s.r(n),s.d(n,{assets:()=>d,contentTitle:()=>a,default:()=>h,frontMatter:()=>r,metadata:()=>i,toc:()=>l});const i=JSON.parse('{"id":"readme","title":"![Dashy Docs](https://i.ibb.co/4mdNf7M/heading-docs.png)","description":"Running Dashy","source":"@site/docs/readme.md","sourceDirName":".","slug":"/","permalink":"/docs/","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/readme.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{}}');var t=s(74848),o=s(28453);const r={},a="Dashy Docs",d={},l=[{value:"Running Dashy",id:"running-dashy",level:2},{value:"Development and Contributing",id:"development-and-contributing",level:2},{value:"Feature Docs",id:"feature-docs",level:2},{value:"Misc",id:"misc",level:2}];function c(e){const n={a:"a",h1:"h1",h2:"h2",header:"header",img:"img",li:"li",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"dashy-docs",children:(0,t.jsx)(n.img,{src:"https://i.ibb.co/4mdNf7M/heading-docs.png",alt:"Dashy Docs"})})}),"\n",(0,t.jsx)(n.h2,{id:"running-dashy",children:"Running Dashy"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/quick-start",children:"Quick Start"})," - TLDR guide on getting Dashy up and running"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/deployment",children:"Deployment"})," - Full guide on deploying Dashy either locally or online"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/configuring",children:"Configuring"})," - Complete list of all available options in the config file"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/management",children:"App Management"})," - Managing your app, updating, security, web server configuration, etc"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/troubleshooting",children:"Troubleshooting"})," - Common errors and problems, and how to fix them"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"development-and-contributing",children:"Development and Contributing"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/developing",children:"Developing"})," - Running Dashy development server locally, and general workflow"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/development-guides",children:"Development Guides"})," - Common development tasks, to help new contributors"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/contributing",children:"Contributing"})," - How you can help keep Dashy alive"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/showcase",children:"Showcase"})," - See how others are using Dashy, and share your dashboard"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/credits",children:"Credits"})," - List of people and projects that have made Dashy possible"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/release-workflow",children:"Release Workflow"})," - Info about releases, CI and automated tasks"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"feature-docs",children:"Feature Docs"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/authentication",children:"Authentication"})," - Guide to setting up authentication to protect your dashboard"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/alternate-views",children:"Alternate Views"})," - Outline of available pages / views and item opening methods"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/backup-restore",children:"Backup & Restore"})," - Guide to backing up config with Dashy's cloud sync feature"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/icons",children:"Icons"})," - Outline of all available icon types for sections and items, with examples"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/multi-language-support",children:"Language Switching"})," - Details on how to switch language, or add a new locale"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/pages-and-sections",children:"Pages and Sections"})," - Multi-page support, sections, items and sub-items"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/api",children:"REST API"})," - Programmatically read and update your config over HTTP"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/status-indicators",children:"Status Indicators"})," - Using Dashy to monitor uptime and status of your apps/services and hosts"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/searching",children:"Searching & Shortcuts"})," - Searching, launching methods + keyboard shortcuts"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/theming",children:"Theming"})," - Complete guide to applying, writing and modifying themes + styles"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/widgets",children:"Widgets"})," - List of all dynamic content widgets, with usage guides and examples"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"misc",children:"Misc"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/privacy",children:"Privacy & Security"})," - List of requests, potential issues, and security resources"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/LICENSE",children:"License"})," - Copy of the MIT License"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/.github/CODE_OF_CONDUCT",children:"Code of Conduct"})," - Contributor Covenant Code of Conduct"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},28453(e,n,s){s.d(n,{R:()=>r,x:()=>a});var i=s(96540);const t={},o=i.createContext(t);function r(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[1306],{91264(e,n,s){s.r(n),s.d(n,{assets:()=>d,contentTitle:()=>a,default:()=>h,frontMatter:()=>r,metadata:()=>i,toc:()=>l});const i=JSON.parse('{"id":"readme","title":"![Dashy Docs](https://i.ibb.co/4mdNf7M/heading-docs.png)","description":"Running Dashy","source":"@site/docs/readme.md","sourceDirName":".","slug":"/","permalink":"/docs/","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/readme.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{}}');var t=s(74848),o=s(28453);const r={},a="Dashy Docs",d={},l=[{value:"Running Dashy",id:"running-dashy",level:2},{value:"Development and Contributing",id:"development-and-contributing",level:2},{value:"Feature Docs",id:"feature-docs",level:2},{value:"Misc",id:"misc",level:2}];function c(e){const n={a:"a",h1:"h1",h2:"h2",header:"header",img:"img",li:"li",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"dashy-docs",children:(0,t.jsx)(n.img,{src:"https://i.ibb.co/4mdNf7M/heading-docs.png",alt:"Dashy Docs"})})}),"\n",(0,t.jsx)(n.h2,{id:"running-dashy",children:"Running Dashy"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/quick-start",children:"Quick Start"})," - TLDR guide on getting Dashy up and running"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/deployment",children:"Deployment"})," - Full guide on deploying Dashy either locally or online"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/configuring",children:"Configuring"})," - Complete list of all available options in the config file"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/management",children:"App Management"})," - Managing your app, updating, security, web server configuration, etc"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/troubleshooting",children:"Troubleshooting"})," - Common errors and problems, and how to fix them"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"development-and-contributing",children:"Development and Contributing"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/developing",children:"Developing"})," - Running Dashy development server locally, and general workflow"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/development-guides",children:"Development Guides"})," - Common development tasks, to help new contributors"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/contributing",children:"Contributing"})," - How you can help keep Dashy alive"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/showcase",children:"Showcase"})," - See how others are using Dashy, and share your dashboard"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/credits",children:"Credits"})," - List of people and projects that have made Dashy possible"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/release-workflow",children:"Release Workflow"})," - Info about releases, CI and automated tasks"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"feature-docs",children:"Feature Docs"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/authentication",children:"Authentication"})," - Guide to setting up authentication to protect your dashboard"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/alternate-views",children:"Alternate Views"})," - Outline of available pages / views and item opening methods"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/backup-restore",children:"Backup & Restore"})," - Guide to backing up config with Dashy's cloud sync feature"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/icons",children:"Icons"})," - Outline of all available icon types for sections and items, with examples"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/multi-language-support",children:"Language Switching"})," - Details on how to switch language, or add a new locale"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/pages-and-sections",children:"Pages and Sections"})," - Multi-page support, sections, items and sub-items"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/api",children:"REST API"})," - Programmatically read and update your config over HTTP"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/status-indicators",children:"Status Indicators"})," - Using Dashy to monitor uptime and status of your apps/services and hosts"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/searching",children:"Searching & Shortcuts"})," - Searching, launching methods + keyboard shortcuts"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/theming",children:"Theming"})," - Complete guide to applying, writing and modifying themes + styles"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/widgets",children:"Widgets"})," - List of all dynamic content widgets, with usage guides and examples"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"misc",children:"Misc"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"/docs/privacy",children:"Privacy & Security"})," - List of requests, potential issues, and security resources"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/LICENSE",children:"License"})," - Copy of the MIT License"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/.github/CODE_OF_CONDUCT",children:"Code of Conduct"})," - Contributor Covenant Code of Conduct"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},28453(e,n,s){s.d(n,{R:()=>r,x:()=>a});var i=s(96540);const t={},o=i.createContext(t);function r(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5fbc5cf1.b3c8efcf.js b/assets/js/5fbc5cf1.f0eeee44.js similarity index 99% rename from assets/js/5fbc5cf1.b3c8efcf.js rename to assets/js/5fbc5cf1.f0eeee44.js index e5618bfc..612d76c8 100644 --- a/assets/js/5fbc5cf1.b3c8efcf.js +++ b/assets/js/5fbc5cf1.f0eeee44.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[7624],{46439(e,n,i){i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>c,metadata:()=>s,toc:()=>a});const s=JSON.parse('{"id":"api","title":"REST API","description":"Dashy includes an optional REST API, for reading and writing your config programmatically \u2014 from the command line, scripts or third-party applications. It covers whole config files, as well as individual sections and items.","source":"@site/docs/api.md","sourceDirName":".","slug":"/api","permalink":"/docs/api","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/api.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{}}');var t=i(74848),d=i(28453);const c={},r="REST API",l={},a=[{value:"Enabling the API",id:"enabling-the-api",level:2},{value:"Authentication",id:"authentication",level:2},{value:"API token",id:"api-token",level:3},{value:"Endpoints",id:"endpoints",level:2},{value:"Addressing Sections and Items",id:"addressing-sections-and-items",level:3},{value:"Updating",id:"updating",level:3},{value:"Examples",id:"examples",level:2},{value:"Limitations & Notes",id:"limitations--notes",level:2}];function o(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"rest-api",children:"REST API"})}),"\n",(0,t.jsx)(n.p,{children:"Dashy includes an optional REST API, for reading and writing your config programmatically \u2014 from the command line, scripts or third-party applications. It covers whole config files, as well as individual sections and items."}),"\n",(0,t.jsx)(n.admonition,{type:"note",children:(0,t.jsx)(n.p,{children:"The API is served by Dashy's Node server, so it's available with Docker and bare-metal deployments, but not on static hosting providers (Netlify, Vercel, EdgeOne, CDN)."})}),"\n",(0,t.jsx)(n.h2,{id:"enabling-the-api",children:"Enabling the API"}),"\n",(0,t.jsxs)(n.p,{children:["The API is disabled by default. To enable it, set the ",(0,t.jsx)(n.code,{children:"ENABLE_API"})," environmental variable to ",(0,t.jsx)(n.code,{children:"true"}),". For example, with Docker Compose:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"environment:\n - ENABLE_API=true\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Or with ",(0,t.jsx)(n.code,{children:"docker run"}),", pass ",(0,t.jsx)(n.code,{children:"-e ENABLE_API=true"}),". While disabled, all ",(0,t.jsx)(n.code,{children:"/api/*"})," requests return a 404."]}),"\n",(0,t.jsx)(n.h2,{id:"authentication",children:"Authentication"}),"\n",(0,t.jsxs)(n.p,{children:["By default, the API uses Dashy's existing ",(0,t.jsx)(n.a,{href:"/docs/authentication",children:"server-side authentication"}),". Read endpoints require any authenticated user, and write endpoints require an admin. If no auth is configured, the API is open \u2014 the same as Dashy's other endpoints."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"# With HTTP Basic Auth (ENABLE_HTTP_AUTH or BASIC_AUTH_USERNAME / BASIC_AUTH_PASSWORD)\ncurl -u alice:hunter2 http://localhost:8080/api/config\n\n# With OIDC / Keycloak, pass your ID token\ncurl -H 'Authorization: Bearer ' http://localhost:8080/api/config\n"})}),"\n",(0,t.jsx)(n.h3,{id:"api-token",children:"API token"}),"\n",(0,t.jsxs)(n.p,{children:["If you have no auth configured, or would prefer a dedicated credential for the API, set the ",(0,t.jsx)(n.code,{children:"API_TOKEN"})," environmental variable and send it as a bearer token. A valid token grants full (admin) access, and works alongside any other configured auth method."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"environment:\n - ENABLE_API=true\n - API_TOKEN=your-long-random-secret\n"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"curl -H 'Authorization: Bearer your-long-random-secret' http://localhost:8080/api/config\n"})}),"\n",(0,t.jsx)(n.admonition,{type:"note",children:(0,t.jsxs)(n.p,{children:["Setting ",(0,t.jsx)(n.code,{children:"API_TOKEN"})," also secures the API on deployments that have no other auth \u2014 anonymous requests are then rejected. Use a long, random value (e.g. ",(0,t.jsx)(n.code,{children:"openssl rand -hex 32"}),") and only send it over HTTPS. The token applies to the API only, not Dashy's other endpoints."]})}),"\n",(0,t.jsx)(n.h2,{id:"endpoints",children:"Endpoints"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:":filename"})," is any YAML config file in your user-data directory (e.g. ",(0,t.jsx)(n.code,{children:"conf.yml"}),", or a sub-page like ",(0,t.jsx)(n.code,{children:"home-lab.yml"}),"). ",(0,t.jsx)(n.code,{children:":key"})," is one of the top-level config keys: ",(0,t.jsx)(n.code,{children:"pageInfo"}),", ",(0,t.jsx)(n.code,{children:"appConfig"}),", ",(0,t.jsx)(n.code,{children:"sections"})," or ",(0,t.jsx)(n.code,{children:"pages"}),"."]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:(0,t.jsx)(n.strong,{children:"Method"})}),(0,t.jsx)(n.th,{children:(0,t.jsx)(n.strong,{children:"Path"})}),(0,t.jsx)(n.th,{children:(0,t.jsx)(n.strong,{children:"Description"})})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config"})}),(0,t.jsx)(n.td,{children:"List config files"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename"})}),(0,t.jsx)(n.td,{children:"Get a full config file, as JSON"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"PUT"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename"})}),(0,t.jsx)(n.td,{children:"Replace a full config file"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/:key"})}),(0,t.jsx)(n.td,{children:"Get a top-level key"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"PUT"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/:key"})}),(0,t.jsx)(n.td,{children:"Replace a top-level key"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"POST"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections"})}),(0,t.jsxs)(n.td,{children:["Add a section (",(0,t.jsx)(n.code,{children:"name"})," required)"]})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid"})}),(0,t.jsx)(n.td,{children:"Get a section"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"PATCH"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid"})}),(0,t.jsx)(n.td,{children:"Update fields on a section"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"DELETE"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid"})}),(0,t.jsx)(n.td,{children:"Delete a section"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid/items"})}),(0,t.jsx)(n.td,{children:"List a section's items"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"POST"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid/items"})}),(0,t.jsxs)(n.td,{children:["Add an item (",(0,t.jsx)(n.code,{children:"title"})," required)"]})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid/items/:iid"})}),(0,t.jsx)(n.td,{children:"Get an item"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"PATCH"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid/items/:iid"})}),(0,t.jsx)(n.td,{children:"Update fields on an item"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"DELETE"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid/items/:iid"})}),(0,t.jsx)(n.td,{children:"Delete an item"})]})]})]}),"\n",(0,t.jsxs)(n.p,{children:["All bodies are JSON. Errors return ",(0,t.jsx)(n.code,{children:'{ "success": false, "message": "..." }'})," with an appropriate status code (400 bad input, 401/403 auth, 404 not found)."]}),"\n",(0,t.jsx)(n.h3,{id:"addressing-sections-and-items",children:"Addressing Sections and Items"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:":sid"})," and ",(0,t.jsx)(n.code,{children:":iid"})," can be either a zero-based index (",(0,t.jsx)(n.code,{children:"0"}),", ",(0,t.jsx)(n.code,{children:"1"}),", ...) or an exact match on the section's ",(0,t.jsx)(n.code,{children:"name"})," / item's ",(0,t.jsx)(n.code,{children:"title"})," (URL-encoded). If multiple entries share a name, the first match wins. A section literally named ",(0,t.jsx)(n.code,{children:"2"})," can only be addressed by index."]}),"\n",(0,t.jsx)(n.h3,{id:"updating",children:"Updating"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"PATCH"})," does a shallow merge: only the fields you send are changed, but nested values (like a section's ",(0,t.jsx)(n.code,{children:"items"})," array) are replaced wholesale if included. ",(0,t.jsx)(n.code,{children:"PUT"})," replaces the target entirely."]}),"\n",(0,t.jsx)(n.h2,{id:"examples",children:"Examples"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'# List config files\ncurl http://localhost:8080/api/config\n\n# Get your main config as JSON\ncurl http://localhost:8080/api/config/conf.yml\n\n# Add an item to the first section\ncurl -X POST -H \'Content-Type: application/json\' \\\n -d \'{"title": "Grafana", "url": "https://grafana.local", "icon": "hl-grafana"}\' \\\n http://localhost:8080/api/config/conf.yml/sections/0/items\n\n# Rename a section\ncurl -X PATCH -H \'Content-Type: application/json\' \\\n -d \'{"name": "Monitoring"}\' \\\n \'http://localhost:8080/api/config/conf.yml/sections/Old%20Name\'\n\n# Update the theme\ncurl -X PUT -H \'Content-Type: application/json\' \\\n -d \'{"theme": "nord-frost"}\' \\\n http://localhost:8080/api/config/conf.yml/appConfig\n'})}),"\n",(0,t.jsx)(n.p,{children:"After modifying your config, refresh the page to see changes."}),"\n",(0,t.jsx)(n.h2,{id:"limitations--notes",children:"Limitations & Notes"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Writes re-serialize the YAML file, so comments, anchors and custom formatting are discarded (the same applies to saving via the UI). A timestamped backup is saved to ",(0,t.jsx)(n.code,{children:"user-data/config-backups/"})," before every write, unless ",(0,t.jsx)(n.code,{children:"DISABLE_CONFIG_BACKUPS=true"})]}),"\n",(0,t.jsxs)(n.li,{children:["Writes to ",(0,t.jsx)(n.code,{children:"conf.yml"})," are validated against ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json",children:"the schema"})," and rejected if invalid. Sub-page files are not schema-validated, since they may contain only a subset of fields"]}),"\n",(0,t.jsx)(n.li,{children:"Config files are capped at 256 KB"}),"\n",(0,t.jsx)(n.li,{children:"Concurrent writes are last-write-wins; there is no locking or optimistic concurrency"}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(o,{...e})}):o(e)}},28453(e,n,i){i.d(n,{R:()=>c,x:()=>r});var s=i(96540);const t={},d=s.createContext(t);function c(e){const n=s.useContext(d);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:c(e.components),s.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[7624],{46439(e,n,i){i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>c,metadata:()=>s,toc:()=>a});const s=JSON.parse('{"id":"api","title":"REST API","description":"Dashy includes an optional REST API, for reading and writing your config programmatically \u2014 from the command line, scripts or third-party applications. It covers whole config files, as well as individual sections and items.","source":"@site/docs/api.md","sourceDirName":".","slug":"/api","permalink":"/docs/api","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/api.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{}}');var t=i(74848),d=i(28453);const c={},r="REST API",l={},a=[{value:"Enabling the API",id:"enabling-the-api",level:2},{value:"Authentication",id:"authentication",level:2},{value:"API token",id:"api-token",level:3},{value:"Endpoints",id:"endpoints",level:2},{value:"Addressing Sections and Items",id:"addressing-sections-and-items",level:3},{value:"Updating",id:"updating",level:3},{value:"Examples",id:"examples",level:2},{value:"Limitations & Notes",id:"limitations--notes",level:2}];function o(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"rest-api",children:"REST API"})}),"\n",(0,t.jsx)(n.p,{children:"Dashy includes an optional REST API, for reading and writing your config programmatically \u2014 from the command line, scripts or third-party applications. It covers whole config files, as well as individual sections and items."}),"\n",(0,t.jsx)(n.admonition,{type:"note",children:(0,t.jsx)(n.p,{children:"The API is served by Dashy's Node server, so it's available with Docker and bare-metal deployments, but not on static hosting providers (Netlify, Vercel, EdgeOne, CDN)."})}),"\n",(0,t.jsx)(n.h2,{id:"enabling-the-api",children:"Enabling the API"}),"\n",(0,t.jsxs)(n.p,{children:["The API is disabled by default. To enable it, set the ",(0,t.jsx)(n.code,{children:"ENABLE_API"})," environmental variable to ",(0,t.jsx)(n.code,{children:"true"}),". For example, with Docker Compose:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"environment:\n - ENABLE_API=true\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Or with ",(0,t.jsx)(n.code,{children:"docker run"}),", pass ",(0,t.jsx)(n.code,{children:"-e ENABLE_API=true"}),". While disabled, all ",(0,t.jsx)(n.code,{children:"/api/*"})," requests return a 404."]}),"\n",(0,t.jsx)(n.h2,{id:"authentication",children:"Authentication"}),"\n",(0,t.jsxs)(n.p,{children:["By default, the API uses Dashy's existing ",(0,t.jsx)(n.a,{href:"/docs/authentication",children:"server-side authentication"}),". Read endpoints require any authenticated user, and write endpoints require an admin. If no auth is configured, the API is open \u2014 the same as Dashy's other endpoints."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"# With HTTP Basic Auth (ENABLE_HTTP_AUTH or BASIC_AUTH_USERNAME / BASIC_AUTH_PASSWORD)\ncurl -u alice:hunter2 http://localhost:8080/api/config\n\n# With OIDC / Keycloak, pass your ID token\ncurl -H 'Authorization: Bearer ' http://localhost:8080/api/config\n"})}),"\n",(0,t.jsx)(n.h3,{id:"api-token",children:"API token"}),"\n",(0,t.jsxs)(n.p,{children:["If you have no auth configured, or would prefer a dedicated credential for the API, set the ",(0,t.jsx)(n.code,{children:"API_TOKEN"})," environmental variable and send it as a bearer token. A valid token grants full (admin) access, and works alongside any other configured auth method."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"environment:\n - ENABLE_API=true\n - API_TOKEN=your-long-random-secret\n"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"curl -H 'Authorization: Bearer your-long-random-secret' http://localhost:8080/api/config\n"})}),"\n",(0,t.jsx)(n.admonition,{type:"note",children:(0,t.jsxs)(n.p,{children:["Setting ",(0,t.jsx)(n.code,{children:"API_TOKEN"})," also secures the API on deployments that have no other auth \u2014 anonymous requests are then rejected. Use a long, random value (e.g. ",(0,t.jsx)(n.code,{children:"openssl rand -hex 32"}),") and only send it over HTTPS. The token applies to the API only, not Dashy's other endpoints."]})}),"\n",(0,t.jsx)(n.h2,{id:"endpoints",children:"Endpoints"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:":filename"})," is any YAML config file in your user-data directory (e.g. ",(0,t.jsx)(n.code,{children:"conf.yml"}),", or a sub-page like ",(0,t.jsx)(n.code,{children:"home-lab.yml"}),"). ",(0,t.jsx)(n.code,{children:":key"})," is one of the top-level config keys: ",(0,t.jsx)(n.code,{children:"pageInfo"}),", ",(0,t.jsx)(n.code,{children:"appConfig"}),", ",(0,t.jsx)(n.code,{children:"sections"})," or ",(0,t.jsx)(n.code,{children:"pages"}),"."]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:(0,t.jsx)(n.strong,{children:"Method"})}),(0,t.jsx)(n.th,{children:(0,t.jsx)(n.strong,{children:"Path"})}),(0,t.jsx)(n.th,{children:(0,t.jsx)(n.strong,{children:"Description"})})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config"})}),(0,t.jsx)(n.td,{children:"List config files"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename"})}),(0,t.jsx)(n.td,{children:"Get a full config file, as JSON"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"PUT"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename"})}),(0,t.jsx)(n.td,{children:"Replace a full config file"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/:key"})}),(0,t.jsx)(n.td,{children:"Get a top-level key"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"PUT"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/:key"})}),(0,t.jsx)(n.td,{children:"Replace a top-level key"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"POST"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections"})}),(0,t.jsxs)(n.td,{children:["Add a section (",(0,t.jsx)(n.code,{children:"name"})," required)"]})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid"})}),(0,t.jsx)(n.td,{children:"Get a section"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"PATCH"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid"})}),(0,t.jsx)(n.td,{children:"Update fields on a section"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"DELETE"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid"})}),(0,t.jsx)(n.td,{children:"Delete a section"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid/items"})}),(0,t.jsx)(n.td,{children:"List a section's items"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"POST"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid/items"})}),(0,t.jsxs)(n.td,{children:["Add an item (",(0,t.jsx)(n.code,{children:"title"})," required)"]})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"GET"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid/items/:iid"})}),(0,t.jsx)(n.td,{children:"Get an item"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"PATCH"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid/items/:iid"})}),(0,t.jsx)(n.td,{children:"Update fields on an item"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"DELETE"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/api/config/:filename/sections/:sid/items/:iid"})}),(0,t.jsx)(n.td,{children:"Delete an item"})]})]})]}),"\n",(0,t.jsxs)(n.p,{children:["All bodies are JSON. Errors return ",(0,t.jsx)(n.code,{children:'{ "success": false, "message": "..." }'})," with an appropriate status code (400 bad input, 401/403 auth, 404 not found)."]}),"\n",(0,t.jsx)(n.h3,{id:"addressing-sections-and-items",children:"Addressing Sections and Items"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:":sid"})," and ",(0,t.jsx)(n.code,{children:":iid"})," can be either a zero-based index (",(0,t.jsx)(n.code,{children:"0"}),", ",(0,t.jsx)(n.code,{children:"1"}),", ...) or an exact match on the section's ",(0,t.jsx)(n.code,{children:"name"})," / item's ",(0,t.jsx)(n.code,{children:"title"})," (URL-encoded). If multiple entries share a name, the first match wins. A section literally named ",(0,t.jsx)(n.code,{children:"2"})," can only be addressed by index."]}),"\n",(0,t.jsx)(n.h3,{id:"updating",children:"Updating"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"PATCH"})," does a shallow merge: only the fields you send are changed, but nested values (like a section's ",(0,t.jsx)(n.code,{children:"items"})," array) are replaced wholesale if included. ",(0,t.jsx)(n.code,{children:"PUT"})," replaces the target entirely."]}),"\n",(0,t.jsx)(n.h2,{id:"examples",children:"Examples"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'# List config files\ncurl http://localhost:8080/api/config\n\n# Get your main config as JSON\ncurl http://localhost:8080/api/config/conf.yml\n\n# Add an item to the first section\ncurl -X POST -H \'Content-Type: application/json\' \\\n -d \'{"title": "Grafana", "url": "https://grafana.local", "icon": "hl-grafana"}\' \\\n http://localhost:8080/api/config/conf.yml/sections/0/items\n\n# Rename a section\ncurl -X PATCH -H \'Content-Type: application/json\' \\\n -d \'{"name": "Monitoring"}\' \\\n \'http://localhost:8080/api/config/conf.yml/sections/Old%20Name\'\n\n# Update the theme\ncurl -X PUT -H \'Content-Type: application/json\' \\\n -d \'{"theme": "nord-frost"}\' \\\n http://localhost:8080/api/config/conf.yml/appConfig\n'})}),"\n",(0,t.jsx)(n.p,{children:"After modifying your config, refresh the page to see changes."}),"\n",(0,t.jsx)(n.h2,{id:"limitations--notes",children:"Limitations & Notes"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Writes re-serialize the YAML file, so comments, anchors and custom formatting are discarded (the same applies to saving via the UI). A timestamped backup is saved to ",(0,t.jsx)(n.code,{children:"user-data/config-backups/"})," before every write, unless ",(0,t.jsx)(n.code,{children:"DISABLE_CONFIG_BACKUPS=true"})]}),"\n",(0,t.jsxs)(n.li,{children:["Writes to ",(0,t.jsx)(n.code,{children:"conf.yml"})," are validated against ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json",children:"the schema"})," and rejected if invalid. Sub-page files are not schema-validated, since they may contain only a subset of fields"]}),"\n",(0,t.jsx)(n.li,{children:"Config files are capped at 256 KB"}),"\n",(0,t.jsx)(n.li,{children:"Concurrent writes are last-write-wins; there is no locking or optimistic concurrency"}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(o,{...e})}):o(e)}},28453(e,n,i){i.d(n,{R:()=>c,x:()=>r});var s=i(96540);const t={},d=s.createContext(t);function c(e){const n=s.useContext(d);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:c(e.components),s.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/625b1ed7.5fc1eb7c.js b/assets/js/625b1ed7.90a913e6.js similarity index 99% rename from assets/js/625b1ed7.5fc1eb7c.js rename to assets/js/625b1ed7.90a913e6.js index 80be5a0d..33af3c4a 100644 --- a/assets/js/625b1ed7.5fc1eb7c.js +++ b/assets/js/625b1ed7.90a913e6.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[1831],{15159(e,n,t){t.r(n),t.d(n,{assets:()=>a,contentTitle:()=>h,default:()=>l,frontMatter:()=>d,metadata:()=>i,toc:()=>o});const i=JSON.parse('{"id":"authentication","title":"Authentication","description":"- Built-In Auth","source":"@site/docs/authentication.md","sourceDirName":".","slug":"/authentication","permalink":"/docs/authentication","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Status Indicators","permalink":"/docs/status-indicators"},"next":{"title":"Authelia OIDC","permalink":"/docs/authentication/authelia-oidc"}}');var s=t(74848),r=t(28453);const d={},h="Authentication",a={},o=[{value:"Built-In Auth",id:"built-in-auth",level:2},{value:"Keycloak",id:"keycloak",level:2},{value:"Header Authentication",id:"header-authentication",level:2},{value:"OIDC",id:"oidc",level:2},{value:"Zero-Trust Tunnels",id:"zero-trust-tunnels",level:2},{value:"Alternative Authentication Methods",id:"alternative-authentication-methods",level:2},{value:"Comparison of Auth Options",id:"comparison-of-auth-options",level:2}];function c(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"authentication",children:"Authentication"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#built-in-auth",children:"Built-In Auth"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#keycloak",children:"Keycloak"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#header-authentication",children:"Header Authentication"})}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#oidc",children:"OIDC Auth"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authentik",children:"Authentik"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authelia-oidc",children:"Authelia"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/zitadel",children:"Zitadel"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/pocketid",children:"Pocket ID"})}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#zero-trust-tunnels",children:"Zero-Trust Tunnels"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/cloudflare-tunnel",children:"Cloudflare Tunnel"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/tailscale",children:"Tailscale / Headscale"})}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#alternative-authentication-methods",children:"Alternative Authentication Methods"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#reverse-proxy-auth",children:"Reverse Proxy Auth"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#vpn",children:"VPN"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#ip-based-access",children:"IP-Based Access"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#web-server-authentication",children:"Web Server Authentication"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#client-certificates-mtls",children:"Client Certificates (mTLS)"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#sso--oauth-providers",children:"SSO / OAuth Providers"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#cloud-hosting-providers",children:"Cloud Hosting Providers"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"important",children:(0,s.jsx)(n.p,{children:"It is your responsibility to properly secure your Dashy instance.\nNever expose your Dashy instance to the public internet or untrusted users without sufficient authentication and authorization in place."})}),"\n",(0,s.jsx)(n.h2,{id:"built-in-auth",children:"Built-In Auth"}),"\n",(0,s.jsxs)(n.p,{children:["Dashy includes a built-in username/password login, with optional server-side HTTP Basic Auth. This is the easiest way to get a login page, without needing to spin up any other services. The full guide, covering password hashing, env-var passwords, guest access, user roles, visibility controls and security notes, is in the ",(0,s.jsx)(n.a,{href:"/docs/authentication/built-in",children:"Built-In Auth guide"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["To enable, simply add an array of users under ",(0,s.jsx)(n.code,{children:"appConfig.auth.users"}),", each with a username (",(0,s.jsx)(n.code,{children:"user"}),") and a SHA256 hash of their password (",(0,s.jsx)(n.code,{children:"hash"}),")."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n disableConfigurationForNonAdmin: true\n auth:\n users:\n - user: alicia\n hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5\n type: admin\n"})}),"\n",(0,s.jsx)(n.p,{children:"Optionally set this env var to enforce this on the server-side too"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-env",children:"ENABLE_HTTP_AUTH=true\n"})}),"\n",(0,s.jsx)(n.h2,{id:"keycloak",children:"Keycloak"}),"\n",(0,s.jsxs)(n.p,{children:["Dashy supports ",(0,s.jsx)(n.a,{href:"https://www.keycloak.org/",children:"Keycloak"})," (v17+) as an authentication provider. See the ",(0,s.jsx)(n.a,{href:"/docs/authentication/keycloak",children:"Keycloak guide"})," for the full deploy and configuration walkthrough."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n disableConfigurationForNonAdmin: true\n auth:\n enableKeycloak: true\n keycloak:\n serverUrl: http://localhost:9100\n realm: dashy\n clientId: dashy\n adminRole: dashy-admin\n"})}),"\n",(0,s.jsx)(n.h2,{id:"header-authentication",children:"Header Authentication"}),"\n",(0,s.jsxs)(n.p,{children:["Dashy can defer authentication to a reverse proxy that injects the user's identity in a request header. See the ",(0,s.jsx)(n.a,{href:"/docs/authentication/header-auth",children:"Header Authentication guide"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n auth:\n enableHeaderAuth: true\n users:\n - user: alice\n hash: 0a7b1d4c2e...\n type: admin\n headerAuth:\n userHeader: Remote-User\n proxyWhitelist:\n - 172.18.0.2\n"})}),"\n",(0,s.jsx)(n.h2,{id:"oidc",children:"OIDC"}),"\n",(0,s.jsxs)(n.p,{children:["Dashy has full support for OIDC based auth, with scoped permissions. See either the generic ",(0,s.jsx)(n.a,{href:"/docs/authentication/oidc",children:"OIDC"})," docs, or our provider-specific guides:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authentik",children:"Authentik"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authelia-oidc",children:"Authelia"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/zitadel",children:"Zitadel"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/pocketid",children:"Pocket ID"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/keycloak",children:"Keycloak"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n disableConfigurationForNonAdmin: true # Hide the config editor from non-admins (recommended)\n enableGuestAccess: false # Optional: view the dashboard read-only without signing in\n enableServiceWorker: true # Optional: enables the PWA and offline support\n enableAuthProxyCompat: true # Recover the PWA after a session expires (needs the service worker)\n auth:\n enableOidc: true # Turn OIDC on\n oidc:\n clientId: dashy # Client ID from your provider\n endpoint: https://auth.example.com/application/o/dashy/ # The issuer URL, not the .well-known one\n scope: openid profile email groups # Scopes to request (groups for adminGroup, roles for adminRole)\n adminGroup: dashy-admins # Members of this group are admins\n adminRole: dashy-admin # Or grant admin by role instead\n enableSilentRenew: true # Refresh the session in the background before it expires\n"})}),"\n",(0,s.jsx)(n.h2,{id:"zero-trust-tunnels",children:"Zero-Trust Tunnels"}),"\n",(0,s.jsx)(n.p,{children:"Dashy works well with third-party tunnel based auth, allowing you to access your dashboard remotely."}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/cloudflare-tunnel",children:"Cloudflare Tunnel"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/tailscale",children:"Tailscale / Headscale"})}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"alternative-authentication-methods",children:"Alternative Authentication Methods"}),"\n",(0,s.jsx)(n.p,{children:"These are alternatives to Dashy's built-in auth, Keycloak, and OIDC. Most of them sit in front of Dashy at the network or reverse proxy level, which is generally the better approach for anything internet-facing."}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#reverse-proxy-auth",children:"Reverse Proxy Auth"})," - Authelia, Authentik, or similar sitting in front of Dashy"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#vpn",children:"VPN"})," - Keep Dashy off the internet entirely"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#ip-based-access",children:"IP-Based Access"})," - Restrict by source IP in your web server"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#web-server-authentication",children:"Web Server Authentication"})," - HTTP basic auth at the proxy level"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#client-certificates-mtls",children:"Client Certificates (mTLS)"})," - Require a client TLS certificate to connect"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#sso--oauth-providers",children:"SSO / OAuth Providers"})," - Cloud-hosted identity providers"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#cloud-hosting-providers",children:"Cloud Hosting Providers"})," - Built-in auth on hosting platforms"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"comparison-of-auth-options",children:"Comparison of Auth Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Method"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"}),(0,s.jsx)(n.th,{children:"Complexity"}),(0,s.jsx)(n.th,{children:"Security"}),(0,s.jsx)(n.th,{children:"Best for"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"No Auth"}),(0,s.jsx)(n.td,{children:"Built-in"}),(0,s.jsx)(n.td,{children:"This is the default state Dashy ships with"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udd34 Weak"}),(0,s.jsx)(n.td,{children:"Internal usage"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/built-in",children:"Built-In Auth"})}),(0,s.jsx)(n.td,{children:"Built-in"}),(0,s.jsx)(n.td,{children:"Username/password list in your config, optionally enforced server-side"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"A quick login screen on a trusted LAN"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/header-auth",children:"Header Auth"})}),(0,s.jsx)(n.td,{children:"Built-in"}),(0,s.jsx)(n.td,{children:"Trusts a username header from a proxy that already did the login"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"Reusing an existing proxy or forward-auth session"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/oidc",children:"OIDC (generic)"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Any OpenID Connect provider, with server-side token checks and admin roles"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Standards-based SSO with any IdP"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authentik",children:"Authentik"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Self-hosted IdP with a full admin UI, MFA and group policies"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"One login across many self-hosted apps"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authelia-oidc",children:"Authelia"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Lightweight self-hosted IdP, configured from a single YAML file"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Self-hosters who prefer file-based config"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/keycloak",children:"Keycloak"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Heavyweight enterprise IdP with realms, roles and social login"}),(0,s.jsx)(n.td,{children:"\ud83d\udd34 Hard"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Larger or enterprise deployments"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/pocketid",children:"Pocket ID"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Minimal passkey-only IdP, a single Go binary"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Passwordless homelab SSO"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/zitadel",children:"Zitadel"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Go/Postgres IdP with project roles (needs an Action to map groups)"}),(0,s.jsx)(n.td,{children:"\ud83d\udd34 Hard"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Role-based access across projects"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/cloudflare-tunnel",children:"Cloudflare Tunnel"})}),(0,s.jsx)(n.td,{children:"Tunnel"}),(0,s.jsx)(n.td,{children:"Outbound tunnel to Cloudflare's edge, paired with Access for login"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Public access with no open ports"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/tailscale",children:"Tailscale / Headscale"})}),(0,s.jsx)(n.td,{children:"Tunnel"}),(0,s.jsx)(n.td,{children:"Private WireGuard mesh, with optional Funnel for public access"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Private remote access between your devices"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#reverse-proxy-auth",children:"Reverse Proxy Auth"})}),(0,s.jsx)(n.td,{children:"Proxy"}),(0,s.jsx)(n.td,{children:"An auth server (Authelia, Authentik, OAuth2 Proxy) in front via forward-auth"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Protecting many apps behind one proxy"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#web-server-authentication",children:"Web Server Auth"})}),(0,s.jsx)(n.td,{children:"Proxy"}),(0,s.jsx)(n.td,{children:"HTTP basic auth handled by your reverse proxy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"A fast password prompt over HTTPS"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#client-certificates-mtls",children:"Client Certificates (mTLS)"})}),(0,s.jsx)(n.td,{children:"Proxy"}),(0,s.jsx)(n.td,{children:"Require a client TLS certificate to connect, enforced at the proxy"}),(0,s.jsx)(n.td,{children:"\ud83d\udd34 Hard"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"A small fixed set of trusted devices"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#ip-based-access",children:"IP-Based Access"})}),(0,s.jsx)(n.td,{children:"Network"}),(0,s.jsx)(n.td,{children:"Allow only certain source IPs at the web server"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"An extra layer on a static IP or VPN"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#vpn",children:"VPN"})}),(0,s.jsx)(n.td,{children:"Network"}),(0,s.jsx)(n.td,{children:"Keep Dashy off the internet, reach it over WireGuard, Tailscale or OpenVPN"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Private access with zero public exposure"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#sso--oauth-providers",children:"SSO / OAuth Providers"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Cloud IdPs (Auth0, Okta, Google) wired in through Dashy's OIDC"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Offloading identity to a managed provider"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#cloud-hosting-providers",children:"Cloud Hosting Providers"})}),(0,s.jsx)(n.td,{children:"Platform"}),(0,s.jsx)(n.td,{children:"Platform-level auth (Cloudflare Access, Netlify, Vercel) outside Dashy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Dashboards hosted on a cloud platform"})]})]})]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})})]})}function l(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},28453(e,n,t){t.d(n,{R:()=>d,x:()=>h});var i=t(96540);const s={},r=i.createContext(s);function d(e){const n=i.useContext(r);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function h(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:d(e.components),i.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[1831],{15159(e,n,t){t.r(n),t.d(n,{assets:()=>a,contentTitle:()=>h,default:()=>l,frontMatter:()=>d,metadata:()=>i,toc:()=>o});const i=JSON.parse('{"id":"authentication","title":"Authentication","description":"- Built-In Auth","source":"@site/docs/authentication.md","sourceDirName":".","slug":"/authentication","permalink":"/docs/authentication","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Status Indicators","permalink":"/docs/status-indicators"},"next":{"title":"Authelia OIDC","permalink":"/docs/authentication/authelia-oidc"}}');var s=t(74848),r=t(28453);const d={},h="Authentication",a={},o=[{value:"Built-In Auth",id:"built-in-auth",level:2},{value:"Keycloak",id:"keycloak",level:2},{value:"Header Authentication",id:"header-authentication",level:2},{value:"OIDC",id:"oidc",level:2},{value:"Zero-Trust Tunnels",id:"zero-trust-tunnels",level:2},{value:"Alternative Authentication Methods",id:"alternative-authentication-methods",level:2},{value:"Comparison of Auth Options",id:"comparison-of-auth-options",level:2}];function c(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"authentication",children:"Authentication"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#built-in-auth",children:"Built-In Auth"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#keycloak",children:"Keycloak"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"#header-authentication",children:"Header Authentication"})}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#oidc",children:"OIDC Auth"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authentik",children:"Authentik"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authelia-oidc",children:"Authelia"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/zitadel",children:"Zitadel"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/pocketid",children:"Pocket ID"})}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#zero-trust-tunnels",children:"Zero-Trust Tunnels"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/cloudflare-tunnel",children:"Cloudflare Tunnel"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/tailscale",children:"Tailscale / Headscale"})}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"#alternative-authentication-methods",children:"Alternative Authentication Methods"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#reverse-proxy-auth",children:"Reverse Proxy Auth"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#vpn",children:"VPN"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#ip-based-access",children:"IP-Based Access"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#web-server-authentication",children:"Web Server Authentication"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#client-certificates-mtls",children:"Client Certificates (mTLS)"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#sso--oauth-providers",children:"SSO / OAuth Providers"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#cloud-hosting-providers",children:"Cloud Hosting Providers"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"important",children:(0,s.jsx)(n.p,{children:"It is your responsibility to properly secure your Dashy instance.\nNever expose your Dashy instance to the public internet or untrusted users without sufficient authentication and authorization in place."})}),"\n",(0,s.jsx)(n.h2,{id:"built-in-auth",children:"Built-In Auth"}),"\n",(0,s.jsxs)(n.p,{children:["Dashy includes a built-in username/password login, with optional server-side HTTP Basic Auth. This is the easiest way to get a login page, without needing to spin up any other services. The full guide, covering password hashing, env-var passwords, guest access, user roles, visibility controls and security notes, is in the ",(0,s.jsx)(n.a,{href:"/docs/authentication/built-in",children:"Built-In Auth guide"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["To enable, simply add an array of users under ",(0,s.jsx)(n.code,{children:"appConfig.auth.users"}),", each with a username (",(0,s.jsx)(n.code,{children:"user"}),") and a SHA256 hash of their password (",(0,s.jsx)(n.code,{children:"hash"}),")."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n disableConfigurationForNonAdmin: true\n auth:\n users:\n - user: alicia\n hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5\n type: admin\n"})}),"\n",(0,s.jsx)(n.p,{children:"Optionally set this env var to enforce this on the server-side too"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-env",children:"ENABLE_HTTP_AUTH=true\n"})}),"\n",(0,s.jsx)(n.h2,{id:"keycloak",children:"Keycloak"}),"\n",(0,s.jsxs)(n.p,{children:["Dashy supports ",(0,s.jsx)(n.a,{href:"https://www.keycloak.org/",children:"Keycloak"})," (v17+) as an authentication provider. See the ",(0,s.jsx)(n.a,{href:"/docs/authentication/keycloak",children:"Keycloak guide"})," for the full deploy and configuration walkthrough."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n disableConfigurationForNonAdmin: true\n auth:\n enableKeycloak: true\n keycloak:\n serverUrl: http://localhost:9100\n realm: dashy\n clientId: dashy\n adminRole: dashy-admin\n"})}),"\n",(0,s.jsx)(n.h2,{id:"header-authentication",children:"Header Authentication"}),"\n",(0,s.jsxs)(n.p,{children:["Dashy can defer authentication to a reverse proxy that injects the user's identity in a request header. See the ",(0,s.jsx)(n.a,{href:"/docs/authentication/header-auth",children:"Header Authentication guide"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n auth:\n enableHeaderAuth: true\n users:\n - user: alice\n hash: 0a7b1d4c2e...\n type: admin\n headerAuth:\n userHeader: Remote-User\n proxyWhitelist:\n - 172.18.0.2\n"})}),"\n",(0,s.jsx)(n.h2,{id:"oidc",children:"OIDC"}),"\n",(0,s.jsxs)(n.p,{children:["Dashy has full support for OIDC based auth, with scoped permissions. See either the generic ",(0,s.jsx)(n.a,{href:"/docs/authentication/oidc",children:"OIDC"})," docs, or our provider-specific guides:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authentik",children:"Authentik"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authelia-oidc",children:"Authelia"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/zitadel",children:"Zitadel"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/pocketid",children:"Pocket ID"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/keycloak",children:"Keycloak"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n disableConfigurationForNonAdmin: true # Hide the config editor from non-admins (recommended)\n enableGuestAccess: false # Optional: view the dashboard read-only without signing in\n enableServiceWorker: true # Optional: enables the PWA and offline support\n enableAuthProxyCompat: true # Recover the PWA after a session expires (needs the service worker)\n auth:\n enableOidc: true # Turn OIDC on\n oidc:\n clientId: dashy # Client ID from your provider\n endpoint: https://auth.example.com/application/o/dashy/ # The issuer URL, not the .well-known one\n scope: openid profile email groups # Scopes to request (groups for adminGroup, roles for adminRole)\n adminGroup: dashy-admins # Members of this group are admins\n adminRole: dashy-admin # Or grant admin by role instead\n enableSilentRenew: true # Refresh the session in the background before it expires\n"})}),"\n",(0,s.jsx)(n.h2,{id:"zero-trust-tunnels",children:"Zero-Trust Tunnels"}),"\n",(0,s.jsx)(n.p,{children:"Dashy works well with third-party tunnel based auth, allowing you to access your dashboard remotely."}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/cloudflare-tunnel",children:"Cloudflare Tunnel"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/tailscale",children:"Tailscale / Headscale"})}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"alternative-authentication-methods",children:"Alternative Authentication Methods"}),"\n",(0,s.jsx)(n.p,{children:"These are alternatives to Dashy's built-in auth, Keycloak, and OIDC. Most of them sit in front of Dashy at the network or reverse proxy level, which is generally the better approach for anything internet-facing."}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#reverse-proxy-auth",children:"Reverse Proxy Auth"})," - Authelia, Authentik, or similar sitting in front of Dashy"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#vpn",children:"VPN"})," - Keep Dashy off the internet entirely"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#ip-based-access",children:"IP-Based Access"})," - Restrict by source IP in your web server"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#web-server-authentication",children:"Web Server Authentication"})," - HTTP basic auth at the proxy level"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#client-certificates-mtls",children:"Client Certificates (mTLS)"})," - Require a client TLS certificate to connect"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#sso--oauth-providers",children:"SSO / OAuth Providers"})," - Cloud-hosted identity providers"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#cloud-hosting-providers",children:"Cloud Hosting Providers"})," - Built-in auth on hosting platforms"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"comparison-of-auth-options",children:"Comparison of Auth Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Method"}),(0,s.jsx)(n.th,{children:"Type"}),(0,s.jsx)(n.th,{children:"Description"}),(0,s.jsx)(n.th,{children:"Complexity"}),(0,s.jsx)(n.th,{children:"Security"}),(0,s.jsx)(n.th,{children:"Best for"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"No Auth"}),(0,s.jsx)(n.td,{children:"Built-in"}),(0,s.jsx)(n.td,{children:"This is the default state Dashy ships with"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udd34 Weak"}),(0,s.jsx)(n.td,{children:"Internal usage"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/built-in",children:"Built-In Auth"})}),(0,s.jsx)(n.td,{children:"Built-in"}),(0,s.jsx)(n.td,{children:"Username/password list in your config, optionally enforced server-side"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"A quick login screen on a trusted LAN"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/header-auth",children:"Header Auth"})}),(0,s.jsx)(n.td,{children:"Built-in"}),(0,s.jsx)(n.td,{children:"Trusts a username header from a proxy that already did the login"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"Reusing an existing proxy or forward-auth session"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/oidc",children:"OIDC (generic)"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Any OpenID Connect provider, with server-side token checks and admin roles"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Standards-based SSO with any IdP"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authentik",children:"Authentik"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Self-hosted IdP with a full admin UI, MFA and group policies"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"One login across many self-hosted apps"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/authelia-oidc",children:"Authelia"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Lightweight self-hosted IdP, configured from a single YAML file"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Self-hosters who prefer file-based config"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/keycloak",children:"Keycloak"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Heavyweight enterprise IdP with realms, roles and social login"}),(0,s.jsx)(n.td,{children:"\ud83d\udd34 Hard"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Larger or enterprise deployments"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/pocketid",children:"Pocket ID"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Minimal passkey-only IdP, a single Go binary"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Passwordless homelab SSO"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/zitadel",children:"Zitadel"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Go/Postgres IdP with project roles (needs an Action to map groups)"}),(0,s.jsx)(n.td,{children:"\ud83d\udd34 Hard"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Role-based access across projects"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/cloudflare-tunnel",children:"Cloudflare Tunnel"})}),(0,s.jsx)(n.td,{children:"Tunnel"}),(0,s.jsx)(n.td,{children:"Outbound tunnel to Cloudflare's edge, paired with Access for login"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Public access with no open ports"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/tailscale",children:"Tailscale / Headscale"})}),(0,s.jsx)(n.td,{children:"Tunnel"}),(0,s.jsx)(n.td,{children:"Private WireGuard mesh, with optional Funnel for public access"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Private remote access between your devices"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#reverse-proxy-auth",children:"Reverse Proxy Auth"})}),(0,s.jsx)(n.td,{children:"Proxy"}),(0,s.jsx)(n.td,{children:"An auth server (Authelia, Authentik, OAuth2 Proxy) in front via forward-auth"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Protecting many apps behind one proxy"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#web-server-authentication",children:"Web Server Auth"})}),(0,s.jsx)(n.td,{children:"Proxy"}),(0,s.jsx)(n.td,{children:"HTTP basic auth handled by your reverse proxy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"A fast password prompt over HTTPS"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#client-certificates-mtls",children:"Client Certificates (mTLS)"})}),(0,s.jsx)(n.td,{children:"Proxy"}),(0,s.jsx)(n.td,{children:"Require a client TLS certificate to connect, enforced at the proxy"}),(0,s.jsx)(n.td,{children:"\ud83d\udd34 Hard"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"A small fixed set of trusted devices"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#ip-based-access",children:"IP-Based Access"})}),(0,s.jsx)(n.td,{children:"Network"}),(0,s.jsx)(n.td,{children:"Allow only certain source IPs at the web server"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"An extra layer on a static IP or VPN"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#vpn",children:"VPN"})}),(0,s.jsx)(n.td,{children:"Network"}),(0,s.jsx)(n.td,{children:"Keep Dashy off the internet, reach it over WireGuard, Tailscale or OpenVPN"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Private access with zero public exposure"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#sso--oauth-providers",children:"SSO / OAuth Providers"})}),(0,s.jsx)(n.td,{children:"OIDC"}),(0,s.jsx)(n.td,{children:"Cloud IdPs (Auth0, Okta, Google) wired in through Dashy's OIDC"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe0 Medium"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Offloading identity to a managed provider"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.a,{href:"/docs/authentication/other-auth-methods#cloud-hosting-providers",children:"Cloud Hosting Providers"})}),(0,s.jsx)(n.td,{children:"Platform"}),(0,s.jsx)(n.td,{children:"Platform-level auth (Cloudflare Access, Netlify, Vercel) outside Dashy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Easy"}),(0,s.jsx)(n.td,{children:"\ud83d\udfe2 Strong"}),(0,s.jsx)(n.td,{children:"Dashboards hosted on a cloud platform"})]})]})]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.a,{href:"#",children:"\u2b06\ufe0f Back to Top"})})})})]})}function l(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},28453(e,n,t){t.d(n,{R:()=>d,x:()=>h});var i=t(96540);const s={},r=i.createContext(s);function d(e){const n=i.useContext(r);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function h(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:d(e.components),i.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/68d50eef.76dcb182.js b/assets/js/68d50eef.ab01a67b.js similarity index 99% rename from assets/js/68d50eef.76dcb182.js rename to assets/js/68d50eef.ab01a67b.js index 7fd32a41..4241293c 100644 --- a/assets/js/68d50eef.76dcb182.js +++ b/assets/js/68d50eef.ab01a67b.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[5992],{52745(e,n,i){i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>l,default:()=>h,frontMatter:()=>r,metadata:()=>s,toc:()=>d});const s=JSON.parse('{"id":"developing","title":"Developing","description":"This article outlines how to get Dashy running in a development environment, and outlines the basics of the architecture.","source":"@site/docs/developing.md","sourceDirName":".","slug":"/developing","permalink":"/docs/developing","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/developing.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Contributing","permalink":"/docs/contributing"},"next":{"title":"Development Guides","permalink":"/docs/development-guides"}}');var t=i(74848),o=i(28453);const r={},l="Developing",a={},d=[{value:"Setting up the Dev Environment",id:"setting-up-the-dev-environment",level:2},{value:"Prerequisites",id:"prerequisites",level:3},{value:"Running the Project",id:"running-the-project",level:3},{value:"Project Commands",id:"project-commands",level:3},{value:"Basics",id:"basics",level:4},{value:"Development",id:"development",level:4},{value:"Utils and Checks",id:"utils-and-checks",level:4},{value:"Alternate Start Commands",id:"alternate-start-commands",level:4},{value:"Notes",id:"notes",level:4},{value:"Environmental Variables",id:"environmental-variables",level:3},{value:"Environment Modes",id:"environment-modes",level:3},{value:"Git Strategy",id:"git-strategy",level:2},{value:"Git Flow",id:"git-flow",level:3},{value:"Git Branch Naming",id:"git-branch-naming",level:3},{value:"Commit Emojis",id:"commit-emojis",level:3},{value:"PR Guidelines",id:"pr-guidelines",level:3},{value:"Resources for Beginners",id:"resources-for-beginners",level:2},{value:"App Info",id:"app-info",level:2},{value:"Style Guide",id:"style-guide",level:3},{value:"Application Structure",id:"application-structure",level:3},{value:"Files in the Root: ./",id:"files-in-the-root-",level:4},{value:"Frontend Source: ./src/",id:"frontend-source-src",level:4},{value:"Visualisation of Source Directory",id:"visualisation-of-source-directory",level:4},{value:"Development Tools",id:"development-tools",level:2},{value:"Performance - Lighthouse",id:"performance---lighthouse",level:3},{value:"Dependencies - BundlePhobia",id:"dependencies---bundlephobia",level:3}];function c(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"developing",children:"Developing"})}),"\n",(0,t.jsxs)(n.p,{children:["This article outlines how to get Dashy running in a development environment, and outlines the basics of the architecture.\nIf you're adding new features, you may want to check out the ",(0,t.jsx)(n.a,{href:"/docs/development-guides",children:"Development Guides"})," docs, for tutorials covering basic tasks."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"#setting-up-the-dev-environment",children:"Setting up the Development Environment"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#prerequisites",children:"Prerequisites"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#running-the-project",children:"Running the App"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#project-commands",children:"Project Commands"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#environmental-variables",children:"Environmental Variables"})}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"#git-strategy",children:"Git Strategy"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#git-flow",children:"Flow"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#git-branch-naming",children:"Branches"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#commit-emojis",children:"Commit emojis"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#pr-guidelines",children:"PR Guidelines"})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#resources-for-beginners",children:"Resources for Beginners"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#app-info",children:"App Info"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#style-guide",children:"Code Style Guide"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#application-structure",children:"Application Structure"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#development-tools",children:"Development Tools"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#notes",children:"Misc / Notes"})}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"setting-up-the-dev-environment",children:"Setting up the Dev Environment"}),"\n",(0,t.jsx)(n.h3,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,t.jsxs)(n.p,{children:["You will need either the latest or LTS version of ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://nodejs.org/",children:"Node.js"})})," to build and serve the application and ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://git-scm.com/downloads",children:"Git"})})," to easily fetch the code, and push any changes. If you plan on running or deploying the container, you'll also need ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://docs.docker.com/get-docker/",children:"Docker"})}),". To avoid any unexpected issues, ensure you've got at least ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://www.npmjs.com/get-npm",children:"NPM"})})," V 7.5 or ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://classic.yarnpkg.com/en/docs/install/#windows-stable",children:"Yarn"})})," 1.22 (you may find ",(0,t.jsx)(n.a,{href:"https://github.com/nvm-sh/nvm",children:"NVM"})," helpful for switching/ managing versions)."]}),"\n",(0,t.jsx)(n.h3,{id:"running-the-project",children:"Running the Project"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["Get Code: ",(0,t.jsx)(n.code,{children:"git clone https://github.com/Lissy93/dashy.git"})]}),"\n",(0,t.jsxs)(n.li,{children:["Navigate into the directory: ",(0,t.jsx)(n.code,{children:"cd dashy"})]}),"\n",(0,t.jsxs)(n.li,{children:["Install dependencies: ",(0,t.jsx)(n.code,{children:"yarn"})]}),"\n",(0,t.jsxs)(n.li,{children:["Start dev server: ",(0,t.jsx)(n.code,{children:"yarn dev"})]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["Dashy should now be being served on ",(0,t.jsx)(n.a,{href:"http://localhost:8080/",children:"http://localhost:8080/"}),". Hot reload is enabled, so making changes to any of the files will trigger them to be rebuilt and the page refreshed."]}),"\n",(0,t.jsx)(n.h3,{id:"project-commands",children:"Project Commands"}),"\n",(0,t.jsx)(n.h4,{id:"basics",children:"Basics"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn build"})})," - Builds the production bundle into ",(0,t.jsx)(n.code,{children:"./dist"}),". The Vite dev/runtime server serves ",(0,t.jsx)(n.code,{children:"user-data/conf.yml"})," on each request, so a rebuild is only needed when source code or assets change, not when config changes"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn start"})})," - Starts the Node server, which serves the built site from ",(0,t.jsx)(n.code,{children:"./dist"})," and the live config from ",(0,t.jsx)(n.code,{children:"user-data/"}),". Run ",(0,t.jsx)(n.code,{children:"yarn build"})," first"]}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"development",children:"Development"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn dev"})})," - Starts the development server with hot reloading"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn lint"})})," - Lints code to ensure it follows a consistent, neat style"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn test"})})," - Runs tests, and outputs results"]}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"utils-and-checks",children:"Utils and Checks"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn validate-config"})})," - If you have quite a long configuration file, you may wish to check that it's all good to go, before deploying the app. This can be done with ",(0,t.jsx)(n.code,{children:"yarn validate-config"})," or ",(0,t.jsx)(n.code,{children:"docker exec -it [container-id] yarn validate-config"}),". Your config file needs to be in ",(0,t.jsx)(n.code,{children:"/user-data/conf.yml"})," (or within your Docker container at ",(0,t.jsx)(n.code,{children:"/app/user-data/conf.yml"}),"). This will first check that your YAML is valid, and then validates it against Dashy's ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json",children:"schema"}),"."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn health-check"})})," - Checks that the application is up and running on it's specified port, and outputs current status and response times. Useful for integrating into your monitoring service, if you need to maintain high system availability"]}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"alternate-start-commands",children:"Alternate Start Commands"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn build && yarn start"})})," - Builds the app, then starts the production Node server. Use this for a manual production-style run on bare metal. With Vite, ",(0,t.jsx)(n.code,{children:"conf.yml"})," is served from ",(0,t.jsx)(n.code,{children:"user-data/"})," at runtime, so config changes only require a page refresh"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn pm2-start"})})," - Starts the Node server using ",(0,t.jsx)(n.a,{href:"https://pm2.keymetrics.io/",children:"PM2"}),", a process manager for Node.js applications, that helps them stay alive. PM2 has some built-in basic monitoring features, and an optional ",(0,t.jsx)(n.a,{href:"https://pm2.io/",children:"management solution"}),". If you are running the app on bare metal, it is recommended to use this start command"]}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"notes",children:"Notes"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["If you are using NPM, replace ",(0,t.jsx)(n.code,{children:"yarn"})," with ",(0,t.jsx)(n.code,{children:"npm run"})]}),"\n",(0,t.jsxs)(n.li,{children:["If you are using Docker, precede each command with ",(0,t.jsx)(n.code,{children:"docker exec -it [container-id]"}),". Container ID can be found by running ",(0,t.jsx)(n.code,{children:"docker ps"})]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"environmental-variables",children:"Environmental Variables"}),"\n",(0,t.jsxs)(n.p,{children:["All environmental variables are optional. Currently there are not many environmental variables used, as most of the user preferences are stored under ",(0,t.jsx)(n.code,{children:"appConfig"})," in the ",(0,t.jsx)(n.code,{children:"conf.yml"})," file."]}),"\n",(0,t.jsxs)(n.p,{children:["You can set variables either in your environment, or using the ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/.env",children:(0,t.jsx)(n.code,{children:".env"})})," file."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"NODE_ENV"})," - Current environment, can be either development, production or test"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"PORT"})," - The port to expose the running application on"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"HOST"})," - The host that Dashy is running on, domain or IP"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"BASE_URL"})," - The default base path for serving up static assets"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"VITE_APP_DOMAIN"})," - Usually the same as BASE_URL, but accessible in frontend"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"INTEGRITY"})," - Should enable SRI for build script and link resources"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"IS_DOCKER"})," - Computed automatically on build. Indicates if running in container"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"VITE_APP_VERSION"})," - Again, set automatically using package.json during build time"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"BACKUP_DIR"})," - Directory for conf.yml backups"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"DISABLE_CONFIG_BACKUPS"})," - Set to 'true' to skip the pre-save backup step (useful on read-only filesystems or where permissions don't allow it)"]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"environment-modes",children:"Environment Modes"}),"\n",(0,t.jsxs)(n.p,{children:["You can set the environment using the ",(0,t.jsx)(n.code,{children:"NODE_ENV"})," variable. By default, the correct environment should be selected based on the script you run to start the app. The following environments are supported: ",(0,t.jsx)(n.code,{children:"production"}),", ",(0,t.jsx)(n.code,{children:"development"})," and ",(0,t.jsx)(n.code,{children:"test"}),". For more info, see ",(0,t.jsx)(n.a,{href:"https://vite.dev/guide/env-and-mode",children:"Vite Env Variables and Modes"}),"."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"git-strategy",children:"Git Strategy"}),"\n",(0,t.jsx)(n.h3,{id:"git-flow",children:"Git Flow"}),"\n",(0,t.jsxs)(n.p,{children:["Like most Git repos, we are following the ",(0,t.jsx)(n.a,{href:"https://guides.github.com/introduction/flow",children:"Github Flow"})," standard."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsx)(n.li,{children:"Create a branch (or fork if you don'd have write access)"}),"\n",(0,t.jsx)(n.li,{children:"Code some awesome stuff \ud83e\uddd1\u200d\ud83d\udcbb"}),"\n",(0,t.jsx)(n.li,{children:"Add, commit and push your changes to your branch/ fork"}),"\n",(0,t.jsx)(n.li,{children:"Head over to GitHub and create a Pull Request"}),"\n",(0,t.jsx)(n.li,{children:"Fill in the required sections in the template, and hit submit"}),"\n",(0,t.jsx)(n.li,{children:"Follow up with any reviews on your code"}),"\n",(0,t.jsx)(n.li,{children:"Merge \ud83c\udf89"}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"git-branch-naming",children:"Git Branch Naming"}),"\n",(0,t.jsxs)(n.p,{children:["The format of your branch name should be something similar to: ",(0,t.jsx)(n.code,{children:"[TYPE]/[TICKET]_[TITLE]"}),"\nFor example, ",(0,t.jsx)(n.code,{children:"FEATURE/420_Awesome-feature"})," or ",(0,t.jsx)(n.code,{children:"FIX/690_login-server-error"})]}),"\n",(0,t.jsx)(n.h3,{id:"commit-emojis",children:"Commit Emojis"}),"\n",(0,t.jsx)(n.p,{children:"Using a single emoji at the start of each commit message, to indicate the type task, makes the commit ledger easier to understand, plus it looks cool."}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\ud83c\udfa8 ",(0,t.jsx)(n.code,{children:":art:"})," - Improve structure / format of the code."]}),"\n",(0,t.jsxs)(n.li,{children:["\u26a1\ufe0f ",(0,t.jsx)(n.code,{children:":zap:"})," - Improve performance."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udd25 ",(0,t.jsx)(n.code,{children:":fire:"})," - Remove code or files."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,t.jsx)(n.code,{children:":bug:"})," - Fix a bug."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\ude91\ufe0f ",(0,t.jsx)(n.code,{children:":ambulance:"})," - Critical hotfix"]}),"\n",(0,t.jsxs)(n.li,{children:["\u2728 ",(0,t.jsx)(n.code,{children:":sparkles:"})," - Introduce new features."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udcdd ",(0,t.jsx)(n.code,{children:":memo:"})," - Add or update documentation."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\ude80 ",(0,t.jsx)(n.code,{children:":rocket:"})," - Deploy stuff."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udc84 ",(0,t.jsx)(n.code,{children:":lipstick:"})," - Add or update the UI and style files."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83c\udf89 ",(0,t.jsx)(n.code,{children:":tada:"})," - Begin a project."]}),"\n",(0,t.jsxs)(n.li,{children:["\u2705 ",(0,t.jsx)(n.code,{children:":white_check_mark:"})," - Add, update, or pass tests."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udd12\ufe0f ",(0,t.jsx)(n.code,{children:":lock:"})," - Fix security issues."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udd16 ",(0,t.jsx)(n.code,{children:":bookmark:"})," - Make a Release or Version tag."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udea8 ",(0,t.jsx)(n.code,{children:":rotating_light:"})," - Fix compiler / linter warnings."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udea7 ",(0,t.jsx)(n.code,{children:":construction:"})," - Work in progress."]}),"\n",(0,t.jsxs)(n.li,{children:["\u2b06\ufe0f ",(0,t.jsx)(n.code,{children:":arrow_up:"})," - Upgrade dependencies."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udc77 ",(0,t.jsx)(n.code,{children:":construction_worker:"})," - Add or update CI build system."]}),"\n",(0,t.jsxs)(n.li,{children:["\u267b\ufe0f ",(0,t.jsx)(n.code,{children:":recycle:"})," - Refactor code."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83e\ude79 ",(0,t.jsx)(n.code,{children:":adhesive_bandage:"})," - Simple fix for a non-critical issue."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udd27 ",(0,t.jsx)(n.code,{children:":wrench:"})," - Add or update configuration files."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83c\udf71 ",(0,t.jsx)(n.code,{children:":bento:"})," - Add or update assets."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\uddc3\ufe0f ",(0,t.jsx)(n.code,{children:":card_file_box:"})," - Perform database schema related changes."]}),"\n",(0,t.jsxs)(n.li,{children:["\u270f\ufe0f ",(0,t.jsx)(n.code,{children:":pencil2:"})," - Fix typos."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83c\udf10 ",(0,t.jsx)(n.code,{children:":globe_with_meridians:"})," - Internationalization and translations."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["For a full list of options, see ",(0,t.jsx)(n.a,{href:"https://gitmoji.dev/",children:"gitmoji.dev"})]}),"\n",(0,t.jsx)(n.h3,{id:"pr-guidelines",children:"PR Guidelines"}),"\n",(0,t.jsx)(n.p,{children:"Once you've made your changes, and pushed them to your fork or branch, you're ready to open a pull request!"}),"\n",(0,t.jsx)(n.p,{children:"For a pull request to be merged, it must:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Must be backwards compatible"}),"\n",(0,t.jsx)(n.li,{children:"The build, lint and tests (run by GH actions) must pass"}),"\n",(0,t.jsx)(n.li,{children:"There must not be any merge conflicts"}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"When you submit your PR, include the required info, by filling out the PR template. Including:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"A brief description of your changes"}),"\n",(0,t.jsx)(n.li,{children:"The issue, ticket or discussion number (if applicable)"}),"\n",(0,t.jsx)(n.li,{children:"For UI relate updates include a screenshot"}),"\n",(0,t.jsx)(n.li,{children:"If any dependencies were added, explain why it was needed, state the cost associated, and confirm it does not introduce any security issues"}),"\n",(0,t.jsx)(n.li,{children:"Finally, check the checkboxes, to confirm that the standards are met, and hit submit!"}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"resources-for-beginners",children:"Resources for Beginners"}),"\n",(0,t.jsx)(n.p,{children:"New to Web Development? Glad you're here! Dashy is a pretty simple app, so it should make a good candidate for your first PR. Presuming that you already have a basic knowledge of JavaScript, the following articles should point you in the right direction for getting up to speed with the technologies used in this project:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://opensource.guide/how-to-contribute/",children:"Open Source for Beginners"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://vuejs.org/guide/introduction.html",children:"Introduction to Vue.js"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.taniarascia.com/getting-started-with-vue/",children:"Vue.js Walkthrough"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://github.com/lukehoban/es6features",children:"ES6 Features"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://blog.logrocket.com/the-definitive-guide-to-scss/",children:"Definitive guide to SCSS"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://docker-curriculum.com/",children:"Complete beginners guide to Docker"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://training.play-with-docker.com/",children:"Docker Classroom - Interactive Tutorials"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.freecodecamp.org/news/learn-typescript-in-5-minutes-13eda868daeb/",children:"Quick start TypeScript guide"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.typescripttutorial.net/",children:"Complete TypeScript tutorial series"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://blog.logrocket.com/vue-typescript-tutorial-examples/",children:"Using TypeScript with Vue.js"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"http://git-cheatsheet.com/",children:"Git cheat sheet"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.freecodecamp.org/news/what-is-npm-a-node-package-manager-tutorial-for-beginners/",children:"Basics of using NPM"})}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["As well as Node, Git and Docker- you'll also need an IDE (e.g. ",(0,t.jsx)(n.a,{href:"https://code.visualstudio.com/",children:"VS Code"})," or ",(0,t.jsx)(n.a,{href:"https://www.vim.org/",children:"Vim"}),") and a terminal (Windows users may find ",(0,t.jsx)(n.a,{href:"https://docs.microsoft.com/en-us/windows/wsl/",children:"WSL"})," more convenient)."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"app-info",children:"App Info"}),"\n",(0,t.jsx)(n.h3,{id:"style-guide",children:"Style Guide"}),"\n",(0,t.jsxs)(n.p,{children:["Linting is done using ",(0,t.jsx)(n.a,{href:"https://eslint.org/",children:"ESLint"}),", and using the ",(0,t.jsx)(n.a,{href:"https://github.com/vuejs/eslint-config-standard",children:"Vue.js Styleguide"}),", which is very similar to the ",(0,t.jsx)(n.a,{href:"https://github.com/airbnb/javascript",children:"AirBnB Styleguide"}),". You can run ",(0,t.jsx)(n.code,{children:"yarn lint"})," to report and fix issues. While the dev server is running, issues will be reported to the console automatically, and any lint errors will trigger the build to fail. Note that all lint checks must pass before any PR can be merged. Linting is also run as a git pre-commit hook"]}),"\n",(0,t.jsx)(n.p,{children:"The most significant things to note are:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Indentation should be done with two spaces"}),"\n",(0,t.jsx)(n.li,{children:"Strings should use single quotes"}),"\n",(0,t.jsx)(n.li,{children:"All statements must end in a semi-colon"}),"\n",(0,t.jsx)(n.li,{children:"The final element in all objects must be preceded with a comma"}),"\n",(0,t.jsx)(n.li,{children:"Maximum line length is 100"}),"\n",(0,t.jsx)(n.li,{children:"There must be exactly one blank line between sections, before function names, and at the end of the file"}),"\n",(0,t.jsx)(n.li,{children:"With conditionals, put else on the same line as your if block's closing brace"}),"\n",(0,t.jsx)(n.li,{children:"All multiline blocks must use braces"}),"\n",(0,t.jsx)(n.li,{children:"Avoid console statements in the frontend"}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Styleguides:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Vue: ",(0,t.jsx)(n.a,{href:"https://vuejs.org/style-guide/",children:"Vue styleguide"})]}),"\n",(0,t.jsxs)(n.li,{children:["JavaScript: ",(0,t.jsx)(n.a,{href:"https://github.com/airbnb/javascript",children:"github.com/airbnb/javascript"})]}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h3,{id:"application-structure",children:"Application Structure"}),"\n",(0,t.jsxs)(n.h4,{id:"files-in-the-root-",children:["Files in the Root: ",(0,t.jsx)(n.code,{children:"./"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-text",children:"\u256e\n\u251c\u2500\u2500 package.json # Project meta-data, dependencies and paths to scripts\n\u251c\u2500\u2500 src/ # Project front-end source code\n\u251c\u2500\u2500 server.js # A Node.js server to serve up the /dist directory\n\u251c\u2500\u2500 services/ # All server-side endpoints and utilities\n\u251c\u2500\u2500 vite.config.mjs # Vite (build tool) configuration\n\u251c\u2500\u2500 Dockerfile # The blueprint for building the Docker container\n\u251c\u2500\u2500 docker-compose.yml # A Docker run command\n\u251c\u2500\u2500 .env # Location for any environmental variables\n\u251c\u2500\u2500 yarn.lock # Auto-generated list of current packages and version numbers\n\u251c\u2500\u2500 docs/ # Markdown documentation\n\u251c\u2500\u2500 README.md # Readme, basic info for getting started\n\u251c\u2500\u2500 LICENSE # License for use\n\u256f\n"})}),"\n",(0,t.jsxs)(n.h4,{id:"frontend-source-src",children:["Frontend Source: ",(0,t.jsx)(n.code,{children:"./src/"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-text",children:"./src\n\u251c\u2500\u2500 App.vue # Vue.js starting file\n\u251c\u2500\u2500 assets # Static non-compiled assets\n\u2502 \u251c\u2500\u2500 fonts # .ttf font files\n\u2502 \u251c\u2500\u2500 locales # All app text, each language in a separate JSON file\n\u2502 \u2570\u2500\u2500 interface-icons # SVG icons used in the app\n\u251c\u2500\u2500 components # All front-end Vue web components\n\u2502 \u251c\u2500\u2500 Charts # Charting components for dynamically displaying widget data\n\u2502 \u2502 \u251c\u2500\u2500 Gauge.vue # A speed-dial style chart for showing 0 - 100 values\n\u2502 \u2502 \u2570\u2500\u2500 PercentageChart.vue # A horizontal bar for showing percentage breakdowns\n\u2502 \u251c\u2500\u2500 Configuration # Components relating to the user config pop-up\n\u2502 \u2502 \u251c\u2500\u2500 AppInfoModal.vue # A modal showing core app info, like version, language, etc\n\u2502 \u2502 \u251c\u2500\u2500 AppVersion.vue # Shows current version from package.json, compares with GitHub\n\u2502 \u2502 \u251c\u2500\u2500 CloudBackupRestore.vue # Form where the user manages cloud sync options\n\u2502 \u2502 \u251c\u2500\u2500 ConfigContainer.vue # Main container, wrapping all other config components\n\u2502 \u2502 \u251c\u2500\u2500 CustomCss.vue # Form where the user can input custom CSS\n\u2502 \u2502 \u2570\u2500\u2500 JsonEditor.vue # JSON editor, where the user can modify the main config file\n\u2502 \u251c\u2500\u2500 FormElements # Basic form elements used throughout the app\n\u2502 \u2502 \u251c\u2500\u2500 Button.vue # Standard button component\n\u2502 \u2502 \u251c\u2500\u2500 Radio.vue # Standard radio button input\n\u2502 \u2502 \u251c\u2500\u2500 Select.vue # Standard dropdown input selector\n\u2502 \u2502 \u251c\u2500\u2500 Input.vue # Standard text field input component\n\u2502 \u2502 \u2570\u2500\u2500 Toggle.vue # Standard on / off toggle switch\n\u2502 \u251c\u2500\u2500 InteractiveEditor # Components for the interactive UI config editor\n\u2502 \u2502 \u251c\u2500\u2500 AddNewSectionLauncher # Button that launches the EditSection form, used for adding new section\n\u2502 \u2502 \u251c\u2500\u2500 EditAppConfig.vue # Form for editing appConfig\n\u2502 \u2502 \u251c\u2500\u2500 EditPageInfo.vue # Form for editing pageInfo\n\u2502 \u2502 \u251c\u2500\u2500 EditSection.vue # Form for adding / editing sections\n\u2502 \u2502 \u251c\u2500\u2500 EditItem.vue # Form for adding or editing items\n\u2502 \u2502 \u251c\u2500\u2500 EditModeSaveMenu.vue # The bar at the bottom of screen in edit mode, containing save buttons\n\u2502 \u2502 \u251c\u2500\u2500 EditModeTopBanner.vue # The bar at the top of screen in edit mode\n\u2502 \u2502 \u251c\u2500\u2500 ExportConfigMenu.vue # Modal for viewing / exporting edited config\n\u2502 \u2502 \u251c\u2500\u2500 MoveItemTo.vue # Form for moving / copying items to other sections\n\u2502 \u2502 \u2570\u2500\u2500 SaveCancelButtons.vue # Buttons visible in all the edit menus, to save or cancel changes\n\u2502 \u251c\u2500\u2500 LinkItems # Components for Sections and Link Items\n\u2502 \u2502 \u251c\u2500\u2500 Collapsable.vue # The collapsible functionality of sections\n\u2502 \u2502 \u251c\u2500\u2500 IframeModal.vue # Pop-up iframe modal, for viewing websites within the app\n\u2502 \u2502 \u251c\u2500\u2500 Item.vue # Main link item, which is displayed within an item group\n\u2502 \u2502 \u251c\u2500\u2500 ItemGroup.vue # Item group is a section containing icons\n\u2502 \u2502 \u251c\u2500\u2500 ItemIcon.vue # The icon used by both items and sections\n\u2502 \u2502 \u251c\u2500\u2500 ItemOpenMethodIcon.vue # A small icon, visible on hover, indicating opening method\n\u2502 \u2502 \u251c\u2500\u2500 ItemContextMenu.vue # The right-click menu, for showing Item opening methods and info\n\u2502 \u2502 \u251c\u2500\u2500 SectionContextMenu.vue # The right-click menu, for showing Section edit/ open options\n\u2502 \u2502 \u2570\u2500\u2500 StatusIndicator.vue # Traffic light dot, showing if app is online or down\n\u2502 \u251c\u2500\u2500 Minimal View # Components used for the startpage / minimal alternative view\n\u2502 \u2502 \u251c\u2500\u2500 MinimalHeading.vue # Title part of minimal view\n\u2502 \u2502 \u251c\u2500\u2500 MinimalSearch.vue # Search bar for minimal view\n\u2502 \u2502 \u2570\u2500\u2500 MinimalSection.vue # Tabbed-Item section for minimal view\n\u2502 \u251c\u2500\u2500 PageStrcture # Components relating the main structure of the page\n\u2502 \u2502 \u251c\u2500\u2500 Footer.vue # Footer, visible at the bottom of all pages\n\u2502 \u2502 \u251c\u2500\u2500 Header.vue # Header, visible at the top of pages, and includes title and nav\n\u2502 \u2502 \u251c\u2500\u2500 LoadingScreen.vue # Splash screen shown on first load\n\u2502 \u2502 \u251c\u2500\u2500 Nav.vue # Navigation bar, includes a list of links\n\u2502 \u2502 \u2570\u2500\u2500 PageTitle.vue # Page title and sub-title, visible within the Header\n\u2502 \u251c\u2500\u2500 Workspace # Components used for the multi-tasking/ Workspace view\n\u2502 \u2502 \u251c\u2500\u2500 MultiTaskingWeb.vue # When multi-tasking enabled, generates new iframe\n\u2502 \u2502 \u251c\u2500\u2500 SideBar.vue # The left sidebar for the workspace view\n\u2502 \u2502 \u251c\u2500\u2500 SideBarItem.vue # App item for the sidebar view\n\u2502 \u2502 \u251c\u2500\u2500 SideBarSection.vue # Collapsible collection of items within workspace sidebar\n\u2502 \u2502 \u251c\u2500\u2500 WebContent.vue # Workspace iframe view, displays content of current app\n\u2502 \u2502 \u2570\u2500\u2500 WidgetView.vue # Workspace container for displaying widgets in main content\n\u2502 \u251c\u2500\u2500 Widgets # Directory contains all custom widget components\n\u2502 \u2502 \u2570\u2500\u2500 .... # Too many to list, see widget docs instead\n\u2502 \u2570\u2500\u2500 Settings # Components relating to the quick-settings, in the top-right\n\u2502 \u251c\u2500\u2500 AuthButtons.vue # Logout button and other app info\n\u2502 \u251c\u2500\u2500 ConfigLauncher.vue # Icon that when clicked will launch the Configuration component\n\u2502 \u251c\u2500\u2500 CustomThemeMaker.vue # Color pickers for letting user build their own theme\n\u2502 \u251c\u2500\u2500 ItemSizeSelector.vue # Set of buttons used to set and save item size\n\u2502 \u251c\u2500\u2500 KeyboardShortcutInfo.vue# Small pop-up displaying the available keyboard shortcuts\n\u2502 \u251c\u2500\u2500 LanguageSwitcher.vue # Dropdown in a modal for changing app language\n\u2502 \u251c\u2500\u2500 LayoutSelector.vue # Set of buttons, letting the user select their desired layout\n\u2502 \u251c\u2500\u2500 SearchBar.vue # The input field in the header, used for searching the app\n\u2502 \u251c\u2500\u2500 SettingsContainer.vue # Container that wraps all the quick-settings components\n\u2502 \u2570\u2500\u2500 ThemeSelector.vue # Drop-down menu enabling the user to select and change themes\n\u251c\u2500\u2500 main.js # Main front-end entry point\n\u251c\u2500\u2500 registerServiceWorker.js # Registers and manages service workers, for PWA apps\n\u251c\u2500\u2500 router.js # Defines all available application routes\n\u251c\u2500\u2500 styles # Directory of all globally used common SCSS styles\n\u2502 \u251c\u2500\u2500 color-palette.scss # All color variable names and default values\n\u2502 \u251c\u2500\u2500 color-themes.scss # All variable values for built-in themes\n\u2502 \u251c\u2500\u2500 dimensions.scss # Dimensions and sizes as variables\n\u2502 \u251c\u2500\u2500 global-styles.scss # Basics and style resets used globally\n\u2502 \u251c\u2500\u2500 media-queries.scss # Screen sizes and media queries\n\u2502 \u251c\u2500\u2500 style-helpers.scss # SCSS functions used for modifying values\n\u2502 \u251c\u2500\u2500 typography.scss # Font and text styles used globally\n\u2502 \u2570\u2500\u2500 user-defined-themes.scss # Empty, put any custom styles or themes here\n\u251c\u2500\u2500 mixins # Reusable component bases, extended by other views / components\n\u2502 \u251c\u2500\u2500 ChartingMixin.js # Functions for rendering charts in widget components\n\u2502 \u251c\u2500\u2500 GlancesMixin.js # Functions for fetching system info from Glances for widgets\n\u2502 \u251c\u2500\u2500 HomeMixin.js # Functions for homepage, used by default, minimal and workspace views\n\u2502 \u2570\u2500\u2500 WidgetMixin.js # Functions for all widgets, like data fetching, updating and error handling\n\u251c\u2500\u2500 utils # Directory of re-used helper functions\n\u2502 \u251c\u2500\u2500 ArrowKeyNavigation.js # Functionality for arrow-key navigation\n\u2502 \u251c\u2500\u2500 Auth.js # Handles all authentication related actions\n\u2502 \u251c\u2500\u2500 CheckSectionVisibility.js # Checks which parts of the page should be visible/ hidden based on config\n\u2502 \u251c\u2500\u2500 ClickOutside.js # A directive for detecting click, used to hide dropdown, modal or context menu\n\u2502 \u251c\u2500\u2500 ConfigHelpers.js # Helper functions for managing configuration\n\u2502 \u251c\u2500\u2500 CloudBackup.js # Functionality for encrypting, processing and network calls\n\u2502 \u251c\u2500\u2500 ConfigSchema.json # The schema, used to validate the users conf.yml file\n\u2502 \u251c\u2500\u2500 ConfigAccumulator.js # Central place for managing and combining config\n\u2502 \u251c\u2500\u2500 ConfigValidator.js # A helper script that validates the config file against schema\n\u2502 \u251c\u2500\u2500 CoolConsole.js # Prints info, warning and error messages to browser console, with a cool style\n\u2502 \u251c\u2500\u2500 defaults.js # Global constants and their default values\n\u2502 \u251c\u2500\u2500 emojis.json # List of emojis with unicode and shortcode, used for emoji icon feature\n\u2502 \u251c\u2500\u2500 EmojiUnicodeRegex.js # Regular expression to validate emoji unicode format, for emoji icons\n\u2502 \u251c\u2500\u2500 ErrorHandler.js # Helper function called when an error is returned\n\u2502 \u251c\u2500\u2500 InitServiceWorker.js # Initializes and manages service worker, if enabled\n\u2502 \u251c\u2500\u2500 Search.js # Helper functions for searching/ filtering items in all views\n\u2502 \u251c\u2500\u2500 JsonToYaml.js # Function that parses and converts raw JSON into valid YAML\n\u2502 \u251c\u2500\u2500 KeycloakAuth.js # Singleton class to manage Keycloak authentication\n\u2502 \u251c\u2500\u2500 OidcAuth.js # Manages OIDC authentication with external providers\n\u2502 \u251c\u2500\u2500 HeaderAuth.js # Handles header-based authentication via reverse proxy\n\u2502 \u251c\u2500\u2500 languages.js # Handles fetching, switching and validating languages\n\u2502 \u2570\u2500\u2500 ThemeHelper.js # Function that handles the fetching and setting of user themes\n\u2570\u2500\u2500 views # Directory of available pages, corresponding to available routes\n \u251c\u2500\u2500 Home.vue # The home page container\n \u251c\u2500\u2500 About.vue # About page\n \u251c\u2500\u2500 Login.vue # Authentication page\n \u251c\u2500\u2500 Minimal.vue # The minimal view\n \u251c\u2500\u2500 Workspace.vue # The workspace view with apps in sidebar\n \u251c\u2500\u2500 DownloadConfig.vue # Config export page\n \u2570\u2500\u2500 404.vue # Not found page\n"})}),"\n",(0,t.jsx)(n.h4,{id:"visualisation-of-source-directory",children:"Visualisation of Source Directory"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/assets/repo-visualization.svg",alt:"File Breakdown"})}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"development-tools",children:"Development Tools"}),"\n",(0,t.jsx)(n.h3,{id:"performance---lighthouse",children:"Performance - Lighthouse"}),"\n",(0,t.jsx)(n.p,{children:"The easiest method of checking performance is to use Chromium's build in auditing tool, Lighthouse. To run the test, open Developer Tools (usually F12) --\x3e Lighthouse and click on the 'Generate Report' button at the bottom."}),"\n",(0,t.jsx)(n.h3,{id:"dependencies---bundlephobia",children:"Dependencies - BundlePhobia"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.a,{href:"https://bundlephobia.com/",children:"BundlePhobia"})," is a really useful app that lets you analyze the cost of adding any particular dependency to an application"]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"WARN asset size limit: The following asset(s) exceed the recommended size limit (244 KiB)."})," - For the PWA to support Windows 10, a splash screen asset is required, and is quite large. This throws a warning, however PWA assets are not loaded until needed, so shouldn't have any impact on application performance. A similar warning is thrown for the Raleway font, and that is looking to be addressed."]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},28453(e,n,i){i.d(n,{R:()=>r,x:()=>l});var s=i(96540);const t={},o=s.createContext(t);function r(e){const n=s.useContext(o);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),s.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[5992],{52745(e,n,i){i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>l,default:()=>h,frontMatter:()=>r,metadata:()=>s,toc:()=>d});const s=JSON.parse('{"id":"developing","title":"Developing","description":"This article outlines how to get Dashy running in a development environment, and outlines the basics of the architecture.","source":"@site/docs/developing.md","sourceDirName":".","slug":"/developing","permalink":"/docs/developing","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/developing.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Contributing","permalink":"/docs/contributing"},"next":{"title":"Development Guides","permalink":"/docs/development-guides"}}');var t=i(74848),o=i(28453);const r={},l="Developing",a={},d=[{value:"Setting up the Dev Environment",id:"setting-up-the-dev-environment",level:2},{value:"Prerequisites",id:"prerequisites",level:3},{value:"Running the Project",id:"running-the-project",level:3},{value:"Project Commands",id:"project-commands",level:3},{value:"Basics",id:"basics",level:4},{value:"Development",id:"development",level:4},{value:"Utils and Checks",id:"utils-and-checks",level:4},{value:"Alternate Start Commands",id:"alternate-start-commands",level:4},{value:"Notes",id:"notes",level:4},{value:"Environmental Variables",id:"environmental-variables",level:3},{value:"Environment Modes",id:"environment-modes",level:3},{value:"Git Strategy",id:"git-strategy",level:2},{value:"Git Flow",id:"git-flow",level:3},{value:"Git Branch Naming",id:"git-branch-naming",level:3},{value:"Commit Emojis",id:"commit-emojis",level:3},{value:"PR Guidelines",id:"pr-guidelines",level:3},{value:"Resources for Beginners",id:"resources-for-beginners",level:2},{value:"App Info",id:"app-info",level:2},{value:"Style Guide",id:"style-guide",level:3},{value:"Application Structure",id:"application-structure",level:3},{value:"Files in the Root: ./",id:"files-in-the-root-",level:4},{value:"Frontend Source: ./src/",id:"frontend-source-src",level:4},{value:"Visualisation of Source Directory",id:"visualisation-of-source-directory",level:4},{value:"Development Tools",id:"development-tools",level:2},{value:"Performance - Lighthouse",id:"performance---lighthouse",level:3},{value:"Dependencies - BundlePhobia",id:"dependencies---bundlephobia",level:3}];function c(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"developing",children:"Developing"})}),"\n",(0,t.jsxs)(n.p,{children:["This article outlines how to get Dashy running in a development environment, and outlines the basics of the architecture.\nIf you're adding new features, you may want to check out the ",(0,t.jsx)(n.a,{href:"/docs/development-guides",children:"Development Guides"})," docs, for tutorials covering basic tasks."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"#setting-up-the-dev-environment",children:"Setting up the Development Environment"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#prerequisites",children:"Prerequisites"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#running-the-project",children:"Running the App"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#project-commands",children:"Project Commands"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#environmental-variables",children:"Environmental Variables"})}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"#git-strategy",children:"Git Strategy"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#git-flow",children:"Flow"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#git-branch-naming",children:"Branches"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#commit-emojis",children:"Commit emojis"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#pr-guidelines",children:"PR Guidelines"})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#resources-for-beginners",children:"Resources for Beginners"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#app-info",children:"App Info"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#style-guide",children:"Code Style Guide"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#application-structure",children:"Application Structure"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#development-tools",children:"Development Tools"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#notes",children:"Misc / Notes"})}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"setting-up-the-dev-environment",children:"Setting up the Dev Environment"}),"\n",(0,t.jsx)(n.h3,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,t.jsxs)(n.p,{children:["You will need either the latest or LTS version of ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://nodejs.org/",children:"Node.js"})})," to build and serve the application and ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://git-scm.com/downloads",children:"Git"})})," to easily fetch the code, and push any changes. If you plan on running or deploying the container, you'll also need ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://docs.docker.com/get-docker/",children:"Docker"})}),". To avoid any unexpected issues, ensure you've got at least ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://www.npmjs.com/get-npm",children:"NPM"})})," V 7.5 or ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://classic.yarnpkg.com/en/docs/install/#windows-stable",children:"Yarn"})})," 1.22 (you may find ",(0,t.jsx)(n.a,{href:"https://github.com/nvm-sh/nvm",children:"NVM"})," helpful for switching/ managing versions)."]}),"\n",(0,t.jsx)(n.h3,{id:"running-the-project",children:"Running the Project"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["Get Code: ",(0,t.jsx)(n.code,{children:"git clone https://github.com/Lissy93/dashy.git"})]}),"\n",(0,t.jsxs)(n.li,{children:["Navigate into the directory: ",(0,t.jsx)(n.code,{children:"cd dashy"})]}),"\n",(0,t.jsxs)(n.li,{children:["Install dependencies: ",(0,t.jsx)(n.code,{children:"yarn"})]}),"\n",(0,t.jsxs)(n.li,{children:["Start dev server: ",(0,t.jsx)(n.code,{children:"yarn dev"})]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["Dashy should now be being served on ",(0,t.jsx)(n.a,{href:"http://localhost:8080/",children:"http://localhost:8080/"}),". Hot reload is enabled, so making changes to any of the files will trigger them to be rebuilt and the page refreshed."]}),"\n",(0,t.jsx)(n.h3,{id:"project-commands",children:"Project Commands"}),"\n",(0,t.jsx)(n.h4,{id:"basics",children:"Basics"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn build"})})," - Builds the production bundle into ",(0,t.jsx)(n.code,{children:"./dist"}),". The Vite dev/runtime server serves ",(0,t.jsx)(n.code,{children:"user-data/conf.yml"})," on each request, so a rebuild is only needed when source code or assets change, not when config changes"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn start"})})," - Starts the Node server, which serves the built site from ",(0,t.jsx)(n.code,{children:"./dist"})," and the live config from ",(0,t.jsx)(n.code,{children:"user-data/"}),". Run ",(0,t.jsx)(n.code,{children:"yarn build"})," first"]}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"development",children:"Development"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn dev"})})," - Starts the development server with hot reloading"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn lint"})})," - Lints code to ensure it follows a consistent, neat style"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn test"})})," - Runs tests, and outputs results"]}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"utils-and-checks",children:"Utils and Checks"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn validate-config"})})," - If you have quite a long configuration file, you may wish to check that it's all good to go, before deploying the app. This can be done with ",(0,t.jsx)(n.code,{children:"yarn validate-config"})," or ",(0,t.jsx)(n.code,{children:"docker exec -it [container-id] yarn validate-config"}),". Your config file needs to be in ",(0,t.jsx)(n.code,{children:"/user-data/conf.yml"})," (or within your Docker container at ",(0,t.jsx)(n.code,{children:"/app/user-data/conf.yml"}),"). This will first check that your YAML is valid, and then validates it against Dashy's ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json",children:"schema"}),"."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn health-check"})})," - Checks that the application is up and running on it's specified port, and outputs current status and response times. Useful for integrating into your monitoring service, if you need to maintain high system availability"]}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"alternate-start-commands",children:"Alternate Start Commands"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn build && yarn start"})})," - Builds the app, then starts the production Node server. Use this for a manual production-style run on bare metal. With Vite, ",(0,t.jsx)(n.code,{children:"conf.yml"})," is served from ",(0,t.jsx)(n.code,{children:"user-data/"})," at runtime, so config changes only require a page refresh"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"yarn pm2-start"})})," - Starts the Node server using ",(0,t.jsx)(n.a,{href:"https://pm2.keymetrics.io/",children:"PM2"}),", a process manager for Node.js applications, that helps them stay alive. PM2 has some built-in basic monitoring features, and an optional ",(0,t.jsx)(n.a,{href:"https://pm2.io/",children:"management solution"}),". If you are running the app on bare metal, it is recommended to use this start command"]}),"\n"]}),"\n",(0,t.jsx)(n.h4,{id:"notes",children:"Notes"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["If you are using NPM, replace ",(0,t.jsx)(n.code,{children:"yarn"})," with ",(0,t.jsx)(n.code,{children:"npm run"})]}),"\n",(0,t.jsxs)(n.li,{children:["If you are using Docker, precede each command with ",(0,t.jsx)(n.code,{children:"docker exec -it [container-id]"}),". Container ID can be found by running ",(0,t.jsx)(n.code,{children:"docker ps"})]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"environmental-variables",children:"Environmental Variables"}),"\n",(0,t.jsxs)(n.p,{children:["All environmental variables are optional. Currently there are not many environmental variables used, as most of the user preferences are stored under ",(0,t.jsx)(n.code,{children:"appConfig"})," in the ",(0,t.jsx)(n.code,{children:"conf.yml"})," file."]}),"\n",(0,t.jsxs)(n.p,{children:["You can set variables either in your environment, or using the ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/.env",children:(0,t.jsx)(n.code,{children:".env"})})," file."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"NODE_ENV"})," - Current environment, can be either development, production or test"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"PORT"})," - The port to expose the running application on"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"HOST"})," - The host that Dashy is running on, domain or IP"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"BASE_URL"})," - The default base path for serving up static assets"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"VITE_APP_DOMAIN"})," - Usually the same as BASE_URL, but accessible in frontend"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"INTEGRITY"})," - Should enable SRI for build script and link resources"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"IS_DOCKER"})," - Computed automatically on build. Indicates if running in container"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"VITE_APP_VERSION"})," - Again, set automatically using package.json during build time"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"BACKUP_DIR"})," - Directory for conf.yml backups"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"DISABLE_CONFIG_BACKUPS"})," - Set to 'true' to skip the pre-save backup step (useful on read-only filesystems or where permissions don't allow it)"]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"environment-modes",children:"Environment Modes"}),"\n",(0,t.jsxs)(n.p,{children:["You can set the environment using the ",(0,t.jsx)(n.code,{children:"NODE_ENV"})," variable. By default, the correct environment should be selected based on the script you run to start the app. The following environments are supported: ",(0,t.jsx)(n.code,{children:"production"}),", ",(0,t.jsx)(n.code,{children:"development"})," and ",(0,t.jsx)(n.code,{children:"test"}),". For more info, see ",(0,t.jsx)(n.a,{href:"https://vite.dev/guide/env-and-mode",children:"Vite Env Variables and Modes"}),"."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"git-strategy",children:"Git Strategy"}),"\n",(0,t.jsx)(n.h3,{id:"git-flow",children:"Git Flow"}),"\n",(0,t.jsxs)(n.p,{children:["Like most Git repos, we are following the ",(0,t.jsx)(n.a,{href:"https://guides.github.com/introduction/flow",children:"Github Flow"})," standard."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsx)(n.li,{children:"Create a branch (or fork if you don'd have write access)"}),"\n",(0,t.jsx)(n.li,{children:"Code some awesome stuff \ud83e\uddd1\u200d\ud83d\udcbb"}),"\n",(0,t.jsx)(n.li,{children:"Add, commit and push your changes to your branch/ fork"}),"\n",(0,t.jsx)(n.li,{children:"Head over to GitHub and create a Pull Request"}),"\n",(0,t.jsx)(n.li,{children:"Fill in the required sections in the template, and hit submit"}),"\n",(0,t.jsx)(n.li,{children:"Follow up with any reviews on your code"}),"\n",(0,t.jsx)(n.li,{children:"Merge \ud83c\udf89"}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"git-branch-naming",children:"Git Branch Naming"}),"\n",(0,t.jsxs)(n.p,{children:["The format of your branch name should be something similar to: ",(0,t.jsx)(n.code,{children:"[TYPE]/[TICKET]_[TITLE]"}),"\nFor example, ",(0,t.jsx)(n.code,{children:"FEATURE/420_Awesome-feature"})," or ",(0,t.jsx)(n.code,{children:"FIX/690_login-server-error"})]}),"\n",(0,t.jsx)(n.h3,{id:"commit-emojis",children:"Commit Emojis"}),"\n",(0,t.jsx)(n.p,{children:"Using a single emoji at the start of each commit message, to indicate the type task, makes the commit ledger easier to understand, plus it looks cool."}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\ud83c\udfa8 ",(0,t.jsx)(n.code,{children:":art:"})," - Improve structure / format of the code."]}),"\n",(0,t.jsxs)(n.li,{children:["\u26a1\ufe0f ",(0,t.jsx)(n.code,{children:":zap:"})," - Improve performance."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udd25 ",(0,t.jsx)(n.code,{children:":fire:"})," - Remove code or files."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udc1b ",(0,t.jsx)(n.code,{children:":bug:"})," - Fix a bug."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\ude91\ufe0f ",(0,t.jsx)(n.code,{children:":ambulance:"})," - Critical hotfix"]}),"\n",(0,t.jsxs)(n.li,{children:["\u2728 ",(0,t.jsx)(n.code,{children:":sparkles:"})," - Introduce new features."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udcdd ",(0,t.jsx)(n.code,{children:":memo:"})," - Add or update documentation."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\ude80 ",(0,t.jsx)(n.code,{children:":rocket:"})," - Deploy stuff."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udc84 ",(0,t.jsx)(n.code,{children:":lipstick:"})," - Add or update the UI and style files."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83c\udf89 ",(0,t.jsx)(n.code,{children:":tada:"})," - Begin a project."]}),"\n",(0,t.jsxs)(n.li,{children:["\u2705 ",(0,t.jsx)(n.code,{children:":white_check_mark:"})," - Add, update, or pass tests."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udd12\ufe0f ",(0,t.jsx)(n.code,{children:":lock:"})," - Fix security issues."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udd16 ",(0,t.jsx)(n.code,{children:":bookmark:"})," - Make a Release or Version tag."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udea8 ",(0,t.jsx)(n.code,{children:":rotating_light:"})," - Fix compiler / linter warnings."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udea7 ",(0,t.jsx)(n.code,{children:":construction:"})," - Work in progress."]}),"\n",(0,t.jsxs)(n.li,{children:["\u2b06\ufe0f ",(0,t.jsx)(n.code,{children:":arrow_up:"})," - Upgrade dependencies."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udc77 ",(0,t.jsx)(n.code,{children:":construction_worker:"})," - Add or update CI build system."]}),"\n",(0,t.jsxs)(n.li,{children:["\u267b\ufe0f ",(0,t.jsx)(n.code,{children:":recycle:"})," - Refactor code."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83e\ude79 ",(0,t.jsx)(n.code,{children:":adhesive_bandage:"})," - Simple fix for a non-critical issue."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\udd27 ",(0,t.jsx)(n.code,{children:":wrench:"})," - Add or update configuration files."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83c\udf71 ",(0,t.jsx)(n.code,{children:":bento:"})," - Add or update assets."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83d\uddc3\ufe0f ",(0,t.jsx)(n.code,{children:":card_file_box:"})," - Perform database schema related changes."]}),"\n",(0,t.jsxs)(n.li,{children:["\u270f\ufe0f ",(0,t.jsx)(n.code,{children:":pencil2:"})," - Fix typos."]}),"\n",(0,t.jsxs)(n.li,{children:["\ud83c\udf10 ",(0,t.jsx)(n.code,{children:":globe_with_meridians:"})," - Internationalization and translations."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["For a full list of options, see ",(0,t.jsx)(n.a,{href:"https://gitmoji.dev/",children:"gitmoji.dev"})]}),"\n",(0,t.jsx)(n.h3,{id:"pr-guidelines",children:"PR Guidelines"}),"\n",(0,t.jsx)(n.p,{children:"Once you've made your changes, and pushed them to your fork or branch, you're ready to open a pull request!"}),"\n",(0,t.jsx)(n.p,{children:"For a pull request to be merged, it must:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Must be backwards compatible"}),"\n",(0,t.jsx)(n.li,{children:"The build, lint and tests (run by GH actions) must pass"}),"\n",(0,t.jsx)(n.li,{children:"There must not be any merge conflicts"}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"When you submit your PR, include the required info, by filling out the PR template. Including:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"A brief description of your changes"}),"\n",(0,t.jsx)(n.li,{children:"The issue, ticket or discussion number (if applicable)"}),"\n",(0,t.jsx)(n.li,{children:"For UI relate updates include a screenshot"}),"\n",(0,t.jsx)(n.li,{children:"If any dependencies were added, explain why it was needed, state the cost associated, and confirm it does not introduce any security issues"}),"\n",(0,t.jsx)(n.li,{children:"Finally, check the checkboxes, to confirm that the standards are met, and hit submit!"}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"resources-for-beginners",children:"Resources for Beginners"}),"\n",(0,t.jsx)(n.p,{children:"New to Web Development? Glad you're here! Dashy is a pretty simple app, so it should make a good candidate for your first PR. Presuming that you already have a basic knowledge of JavaScript, the following articles should point you in the right direction for getting up to speed with the technologies used in this project:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://opensource.guide/how-to-contribute/",children:"Open Source for Beginners"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://vuejs.org/guide/introduction.html",children:"Introduction to Vue.js"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.taniarascia.com/getting-started-with-vue/",children:"Vue.js Walkthrough"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://github.com/lukehoban/es6features",children:"ES6 Features"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://blog.logrocket.com/the-definitive-guide-to-scss/",children:"Definitive guide to SCSS"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://docker-curriculum.com/",children:"Complete beginners guide to Docker"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://training.play-with-docker.com/",children:"Docker Classroom - Interactive Tutorials"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.freecodecamp.org/news/learn-typescript-in-5-minutes-13eda868daeb/",children:"Quick start TypeScript guide"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.typescripttutorial.net/",children:"Complete TypeScript tutorial series"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://blog.logrocket.com/vue-typescript-tutorial-examples/",children:"Using TypeScript with Vue.js"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"http://git-cheatsheet.com/",children:"Git cheat sheet"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.freecodecamp.org/news/what-is-npm-a-node-package-manager-tutorial-for-beginners/",children:"Basics of using NPM"})}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["As well as Node, Git and Docker- you'll also need an IDE (e.g. ",(0,t.jsx)(n.a,{href:"https://code.visualstudio.com/",children:"VS Code"})," or ",(0,t.jsx)(n.a,{href:"https://www.vim.org/",children:"Vim"}),") and a terminal (Windows users may find ",(0,t.jsx)(n.a,{href:"https://docs.microsoft.com/en-us/windows/wsl/",children:"WSL"})," more convenient)."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"app-info",children:"App Info"}),"\n",(0,t.jsx)(n.h3,{id:"style-guide",children:"Style Guide"}),"\n",(0,t.jsxs)(n.p,{children:["Linting is done using ",(0,t.jsx)(n.a,{href:"https://eslint.org/",children:"ESLint"}),", and using the ",(0,t.jsx)(n.a,{href:"https://github.com/vuejs/eslint-config-standard",children:"Vue.js Styleguide"}),", which is very similar to the ",(0,t.jsx)(n.a,{href:"https://github.com/airbnb/javascript",children:"AirBnB Styleguide"}),". You can run ",(0,t.jsx)(n.code,{children:"yarn lint"})," to report and fix issues. While the dev server is running, issues will be reported to the console automatically, and any lint errors will trigger the build to fail. Note that all lint checks must pass before any PR can be merged. Linting is also run as a git pre-commit hook"]}),"\n",(0,t.jsx)(n.p,{children:"The most significant things to note are:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Indentation should be done with two spaces"}),"\n",(0,t.jsx)(n.li,{children:"Strings should use single quotes"}),"\n",(0,t.jsx)(n.li,{children:"All statements must end in a semi-colon"}),"\n",(0,t.jsx)(n.li,{children:"The final element in all objects must be preceded with a comma"}),"\n",(0,t.jsx)(n.li,{children:"Maximum line length is 100"}),"\n",(0,t.jsx)(n.li,{children:"There must be exactly one blank line between sections, before function names, and at the end of the file"}),"\n",(0,t.jsx)(n.li,{children:"With conditionals, put else on the same line as your if block's closing brace"}),"\n",(0,t.jsx)(n.li,{children:"All multiline blocks must use braces"}),"\n",(0,t.jsx)(n.li,{children:"Avoid console statements in the frontend"}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Styleguides:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Vue: ",(0,t.jsx)(n.a,{href:"https://vuejs.org/style-guide/",children:"Vue styleguide"})]}),"\n",(0,t.jsxs)(n.li,{children:["JavaScript: ",(0,t.jsx)(n.a,{href:"https://github.com/airbnb/javascript",children:"github.com/airbnb/javascript"})]}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h3,{id:"application-structure",children:"Application Structure"}),"\n",(0,t.jsxs)(n.h4,{id:"files-in-the-root-",children:["Files in the Root: ",(0,t.jsx)(n.code,{children:"./"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-text",children:"\u256e\n\u251c\u2500\u2500 package.json # Project meta-data, dependencies and paths to scripts\n\u251c\u2500\u2500 src/ # Project front-end source code\n\u251c\u2500\u2500 server.js # A Node.js server to serve up the /dist directory\n\u251c\u2500\u2500 services/ # All server-side endpoints and utilities\n\u251c\u2500\u2500 vite.config.mjs # Vite (build tool) configuration\n\u251c\u2500\u2500 Dockerfile # The blueprint for building the Docker container\n\u251c\u2500\u2500 docker-compose.yml # A Docker run command\n\u251c\u2500\u2500 .env # Location for any environmental variables\n\u251c\u2500\u2500 yarn.lock # Auto-generated list of current packages and version numbers\n\u251c\u2500\u2500 docs/ # Markdown documentation\n\u251c\u2500\u2500 README.md # Readme, basic info for getting started\n\u251c\u2500\u2500 LICENSE # License for use\n\u256f\n"})}),"\n",(0,t.jsxs)(n.h4,{id:"frontend-source-src",children:["Frontend Source: ",(0,t.jsx)(n.code,{children:"./src/"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-text",children:"./src\n\u251c\u2500\u2500 App.vue # Vue.js starting file\n\u251c\u2500\u2500 assets # Static non-compiled assets\n\u2502 \u251c\u2500\u2500 fonts # .ttf font files\n\u2502 \u251c\u2500\u2500 locales # All app text, each language in a separate JSON file\n\u2502 \u2570\u2500\u2500 interface-icons # SVG icons used in the app\n\u251c\u2500\u2500 components # All front-end Vue web components\n\u2502 \u251c\u2500\u2500 Charts # Charting components for dynamically displaying widget data\n\u2502 \u2502 \u251c\u2500\u2500 Gauge.vue # A speed-dial style chart for showing 0 - 100 values\n\u2502 \u2502 \u2570\u2500\u2500 PercentageChart.vue # A horizontal bar for showing percentage breakdowns\n\u2502 \u251c\u2500\u2500 Configuration # Components relating to the user config pop-up\n\u2502 \u2502 \u251c\u2500\u2500 AppInfoModal.vue # A modal showing core app info, like version, language, etc\n\u2502 \u2502 \u251c\u2500\u2500 AppVersion.vue # Shows current version from package.json, compares with GitHub\n\u2502 \u2502 \u251c\u2500\u2500 CloudBackupRestore.vue # Form where the user manages cloud sync options\n\u2502 \u2502 \u251c\u2500\u2500 ConfigContainer.vue # Main container, wrapping all other config components\n\u2502 \u2502 \u251c\u2500\u2500 CustomCss.vue # Form where the user can input custom CSS\n\u2502 \u2502 \u2570\u2500\u2500 JsonEditor.vue # JSON editor, where the user can modify the main config file\n\u2502 \u251c\u2500\u2500 FormElements # Basic form elements used throughout the app\n\u2502 \u2502 \u251c\u2500\u2500 Button.vue # Standard button component\n\u2502 \u2502 \u251c\u2500\u2500 Radio.vue # Standard radio button input\n\u2502 \u2502 \u251c\u2500\u2500 Select.vue # Standard dropdown input selector\n\u2502 \u2502 \u251c\u2500\u2500 Input.vue # Standard text field input component\n\u2502 \u2502 \u2570\u2500\u2500 Toggle.vue # Standard on / off toggle switch\n\u2502 \u251c\u2500\u2500 InteractiveEditor # Components for the interactive UI config editor\n\u2502 \u2502 \u251c\u2500\u2500 AddNewSectionLauncher # Button that launches the EditSection form, used for adding new section\n\u2502 \u2502 \u251c\u2500\u2500 EditAppConfig.vue # Form for editing appConfig\n\u2502 \u2502 \u251c\u2500\u2500 EditPageInfo.vue # Form for editing pageInfo\n\u2502 \u2502 \u251c\u2500\u2500 EditSection.vue # Form for adding / editing sections\n\u2502 \u2502 \u251c\u2500\u2500 EditItem.vue # Form for adding or editing items\n\u2502 \u2502 \u251c\u2500\u2500 EditModeSaveMenu.vue # The bar at the bottom of screen in edit mode, containing save buttons\n\u2502 \u2502 \u251c\u2500\u2500 EditModeTopBanner.vue # The bar at the top of screen in edit mode\n\u2502 \u2502 \u251c\u2500\u2500 ExportConfigMenu.vue # Modal for viewing / exporting edited config\n\u2502 \u2502 \u251c\u2500\u2500 MoveItemTo.vue # Form for moving / copying items to other sections\n\u2502 \u2502 \u2570\u2500\u2500 SaveCancelButtons.vue # Buttons visible in all the edit menus, to save or cancel changes\n\u2502 \u251c\u2500\u2500 LinkItems # Components for Sections and Link Items\n\u2502 \u2502 \u251c\u2500\u2500 Collapsable.vue # The collapsible functionality of sections\n\u2502 \u2502 \u251c\u2500\u2500 IframeModal.vue # Pop-up iframe modal, for viewing websites within the app\n\u2502 \u2502 \u251c\u2500\u2500 Item.vue # Main link item, which is displayed within an item group\n\u2502 \u2502 \u251c\u2500\u2500 ItemGroup.vue # Item group is a section containing icons\n\u2502 \u2502 \u251c\u2500\u2500 ItemIcon.vue # The icon used by both items and sections\n\u2502 \u2502 \u251c\u2500\u2500 ItemOpenMethodIcon.vue # A small icon, visible on hover, indicating opening method\n\u2502 \u2502 \u251c\u2500\u2500 ItemContextMenu.vue # The right-click menu, for showing Item opening methods and info\n\u2502 \u2502 \u251c\u2500\u2500 SectionContextMenu.vue # The right-click menu, for showing Section edit/ open options\n\u2502 \u2502 \u2570\u2500\u2500 StatusIndicator.vue # Traffic light dot, showing if app is online or down\n\u2502 \u251c\u2500\u2500 Minimal View # Components used for the startpage / minimal alternative view\n\u2502 \u2502 \u251c\u2500\u2500 MinimalHeading.vue # Title part of minimal view\n\u2502 \u2502 \u251c\u2500\u2500 MinimalSearch.vue # Search bar for minimal view\n\u2502 \u2502 \u2570\u2500\u2500 MinimalSection.vue # Tabbed-Item section for minimal view\n\u2502 \u251c\u2500\u2500 PageStrcture # Components relating the main structure of the page\n\u2502 \u2502 \u251c\u2500\u2500 Footer.vue # Footer, visible at the bottom of all pages\n\u2502 \u2502 \u251c\u2500\u2500 Header.vue # Header, visible at the top of pages, and includes title and nav\n\u2502 \u2502 \u251c\u2500\u2500 LoadingScreen.vue # Splash screen shown on first load\n\u2502 \u2502 \u251c\u2500\u2500 Nav.vue # Navigation bar, includes a list of links\n\u2502 \u2502 \u2570\u2500\u2500 PageTitle.vue # Page title and sub-title, visible within the Header\n\u2502 \u251c\u2500\u2500 Workspace # Components used for the multi-tasking/ Workspace view\n\u2502 \u2502 \u251c\u2500\u2500 MultiTaskingWeb.vue # When multi-tasking enabled, generates new iframe\n\u2502 \u2502 \u251c\u2500\u2500 SideBar.vue # The left sidebar for the workspace view\n\u2502 \u2502 \u251c\u2500\u2500 SideBarItem.vue # App item for the sidebar view\n\u2502 \u2502 \u251c\u2500\u2500 SideBarSection.vue # Collapsible collection of items within workspace sidebar\n\u2502 \u2502 \u251c\u2500\u2500 WebContent.vue # Workspace iframe view, displays content of current app\n\u2502 \u2502 \u2570\u2500\u2500 WidgetView.vue # Workspace container for displaying widgets in main content\n\u2502 \u251c\u2500\u2500 Widgets # Directory contains all custom widget components\n\u2502 \u2502 \u2570\u2500\u2500 .... # Too many to list, see widget docs instead\n\u2502 \u2570\u2500\u2500 Settings # Components relating to the quick-settings, in the top-right\n\u2502 \u251c\u2500\u2500 AuthButtons.vue # Logout button and other app info\n\u2502 \u251c\u2500\u2500 ConfigLauncher.vue # Icon that when clicked will launch the Configuration component\n\u2502 \u251c\u2500\u2500 CustomThemeMaker.vue # Color pickers for letting user build their own theme\n\u2502 \u251c\u2500\u2500 ItemSizeSelector.vue # Set of buttons used to set and save item size\n\u2502 \u251c\u2500\u2500 KeyboardShortcutInfo.vue# Small pop-up displaying the available keyboard shortcuts\n\u2502 \u251c\u2500\u2500 LanguageSwitcher.vue # Dropdown in a modal for changing app language\n\u2502 \u251c\u2500\u2500 LayoutSelector.vue # Set of buttons, letting the user select their desired layout\n\u2502 \u251c\u2500\u2500 SearchBar.vue # The input field in the header, used for searching the app\n\u2502 \u251c\u2500\u2500 SettingsContainer.vue # Container that wraps all the quick-settings components\n\u2502 \u2570\u2500\u2500 ThemeSelector.vue # Drop-down menu enabling the user to select and change themes\n\u251c\u2500\u2500 main.js # Main front-end entry point\n\u251c\u2500\u2500 registerServiceWorker.js # Registers and manages service workers, for PWA apps\n\u251c\u2500\u2500 router.js # Defines all available application routes\n\u251c\u2500\u2500 styles # Directory of all globally used common SCSS styles\n\u2502 \u251c\u2500\u2500 color-palette.scss # All color variable names and default values\n\u2502 \u251c\u2500\u2500 color-themes.scss # All variable values for built-in themes\n\u2502 \u251c\u2500\u2500 dimensions.scss # Dimensions and sizes as variables\n\u2502 \u251c\u2500\u2500 global-styles.scss # Basics and style resets used globally\n\u2502 \u251c\u2500\u2500 media-queries.scss # Screen sizes and media queries\n\u2502 \u251c\u2500\u2500 style-helpers.scss # SCSS functions used for modifying values\n\u2502 \u251c\u2500\u2500 typography.scss # Font and text styles used globally\n\u2502 \u2570\u2500\u2500 user-defined-themes.scss # Empty, put any custom styles or themes here\n\u251c\u2500\u2500 mixins # Reusable component bases, extended by other views / components\n\u2502 \u251c\u2500\u2500 ChartingMixin.js # Functions for rendering charts in widget components\n\u2502 \u251c\u2500\u2500 GlancesMixin.js # Functions for fetching system info from Glances for widgets\n\u2502 \u251c\u2500\u2500 HomeMixin.js # Functions for homepage, used by default, minimal and workspace views\n\u2502 \u2570\u2500\u2500 WidgetMixin.js # Functions for all widgets, like data fetching, updating and error handling\n\u251c\u2500\u2500 utils # Directory of re-used helper functions\n\u2502 \u251c\u2500\u2500 ArrowKeyNavigation.js # Functionality for arrow-key navigation\n\u2502 \u251c\u2500\u2500 Auth.js # Handles all authentication related actions\n\u2502 \u251c\u2500\u2500 CheckSectionVisibility.js # Checks which parts of the page should be visible/ hidden based on config\n\u2502 \u251c\u2500\u2500 ClickOutside.js # A directive for detecting click, used to hide dropdown, modal or context menu\n\u2502 \u251c\u2500\u2500 ConfigHelpers.js # Helper functions for managing configuration\n\u2502 \u251c\u2500\u2500 CloudBackup.js # Functionality for encrypting, processing and network calls\n\u2502 \u251c\u2500\u2500 ConfigSchema.json # The schema, used to validate the users conf.yml file\n\u2502 \u251c\u2500\u2500 ConfigAccumulator.js # Central place for managing and combining config\n\u2502 \u251c\u2500\u2500 ConfigValidator.js # A helper script that validates the config file against schema\n\u2502 \u251c\u2500\u2500 CoolConsole.js # Prints info, warning and error messages to browser console, with a cool style\n\u2502 \u251c\u2500\u2500 defaults.js # Global constants and their default values\n\u2502 \u251c\u2500\u2500 emojis.json # List of emojis with unicode and shortcode, used for emoji icon feature\n\u2502 \u251c\u2500\u2500 EmojiUnicodeRegex.js # Regular expression to validate emoji unicode format, for emoji icons\n\u2502 \u251c\u2500\u2500 ErrorHandler.js # Helper function called when an error is returned\n\u2502 \u251c\u2500\u2500 InitServiceWorker.js # Initializes and manages service worker, if enabled\n\u2502 \u251c\u2500\u2500 Search.js # Helper functions for searching/ filtering items in all views\n\u2502 \u251c\u2500\u2500 JsonToYaml.js # Function that parses and converts raw JSON into valid YAML\n\u2502 \u251c\u2500\u2500 KeycloakAuth.js # Singleton class to manage Keycloak authentication\n\u2502 \u251c\u2500\u2500 OidcAuth.js # Manages OIDC authentication with external providers\n\u2502 \u251c\u2500\u2500 HeaderAuth.js # Handles header-based authentication via reverse proxy\n\u2502 \u251c\u2500\u2500 languages.js # Handles fetching, switching and validating languages\n\u2502 \u2570\u2500\u2500 ThemeHelper.js # Function that handles the fetching and setting of user themes\n\u2570\u2500\u2500 views # Directory of available pages, corresponding to available routes\n \u251c\u2500\u2500 Home.vue # The home page container\n \u251c\u2500\u2500 About.vue # About page\n \u251c\u2500\u2500 Login.vue # Authentication page\n \u251c\u2500\u2500 Minimal.vue # The minimal view\n \u251c\u2500\u2500 Workspace.vue # The workspace view with apps in sidebar\n \u251c\u2500\u2500 DownloadConfig.vue # Config export page\n \u2570\u2500\u2500 404.vue # Not found page\n"})}),"\n",(0,t.jsx)(n.h4,{id:"visualisation-of-source-directory",children:"Visualisation of Source Directory"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{src:"https://raw.githubusercontent.com/Lissy93/dashy/master/docs/assets/repo-visualization.svg",alt:"File Breakdown"})}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"development-tools",children:"Development Tools"}),"\n",(0,t.jsx)(n.h3,{id:"performance---lighthouse",children:"Performance - Lighthouse"}),"\n",(0,t.jsx)(n.p,{children:"The easiest method of checking performance is to use Chromium's build in auditing tool, Lighthouse. To run the test, open Developer Tools (usually F12) --\x3e Lighthouse and click on the 'Generate Report' button at the bottom."}),"\n",(0,t.jsx)(n.h3,{id:"dependencies---bundlephobia",children:"Dependencies - BundlePhobia"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.a,{href:"https://bundlephobia.com/",children:"BundlePhobia"})," is a really useful app that lets you analyze the cost of adding any particular dependency to an application"]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"WARN asset size limit: The following asset(s) exceed the recommended size limit (244 KiB)."})," - For the PWA to support Windows 10, a splash screen asset is required, and is quite large. This throws a warning, however PWA assets are not loaded until needed, so shouldn't have any impact on application performance. A similar warning is thrown for the Raleway font, and that is looking to be addressed."]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},28453(e,n,i){i.d(n,{R:()=>r,x:()=>l});var s=i(96540);const t={},o=s.createContext(t);function r(e){const n=s.useContext(o);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),s.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/72e14192.ad6976a9.js b/assets/js/72e14192.9ace4040.js similarity index 99% rename from assets/js/72e14192.ad6976a9.js rename to assets/js/72e14192.9ace4040.js index 9da064a3..e9fd2c28 100644 --- a/assets/js/72e14192.ad6976a9.js +++ b/assets/js/72e14192.9ace4040.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[2814],{81953(e,s,n){n.r(s),n.d(s,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>r,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"quick-start","title":"Quick Start","description":"Welcome to Dashy! So glad you\'re here \ud83d\ude0a In a couple of minutes, you\'ll have your new dashboard up and running \ud83d\ude80","source":"@site/docs/quick-start.md","sourceDirName":".","slug":"/quick-start","permalink":"/docs/quick-start","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/quick-start.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","next":{"title":"Deployment","permalink":"/docs/deployment"}}');var t=n(74848),o=n(28453);const r={},a="Quick Start",l={},c=[{value:"1. Prerequisites",id:"1-prerequisites",level:2},{value:"2. Installation",id:"2-installation",level:2},{value:"3. User Data Directory",id:"3-user-data-directory",level:2},{value:"4. Configure",id:"4-configure",level:2},{value:"5. Further Customisation",id:"5-further-customisation",level:2},{value:"6. Final Note",id:"6-final-note",level:2},{value:"Alternative Deployment Method 1 - From Source",id:"alternative-deployment-method-1---from-source",level:2},{value:"Alternative Deployment Method 2 - Netlify",id:"alternative-deployment-method-2---netlify",level:2},{value:"Alternative Deployment Method 3 - Cloud Services",id:"alternative-deployment-method-3---cloud-services",level:2}];function d(e){const s={a:"a",admonition:"admonition",br:"br",code:"code",h1:"h1",h2:"h2",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(s.header,{children:(0,t.jsx)(s.h1,{id:"quick-start",children:"Quick Start"})}),"\n",(0,t.jsx)(s.p,{children:"Welcome to Dashy! So glad you're here \ud83d\ude0a In a couple of minutes, you'll have your new dashboard up and running \ud83d\ude80"}),"\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.strong,{children:"TLDR;"})," Run ",(0,t.jsx)(s.code,{children:"docker run -p 8080:8080 lissy93/dashy"}),", then open ",(0,t.jsx)(s.code,{children:"http://localhost:8080"})]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"1-prerequisites",children:"1. Prerequisites"}),"\n",(0,t.jsxs)(s.p,{children:["The quickest and easiest method of running Dashy is using Docker (or another container engine). You can find installation instructions for your system in the ",(0,t.jsx)(s.a,{href:"https://docs.docker.com/get-docker/",children:"Docker Documentation"}),".\nIf you don't want to use Docker, then you can use one of Dashy's other supported installation methods instead, all of which are outlined in the ",(0,t.jsx)(s.a,{href:"/docs/deployment",children:"Deployment Docs"}),"."]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"2-installation",children:"2. Installation"}),"\n",(0,t.jsx)(s.p,{children:"To pull the latest image, and build and start the app run:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-bash",children:"docker run -d \\\n -p 8080:8080 \\\n -v ~/dashy-data:/app/user-data \\\n --name dashy \\\n --restart=always \\\n lissy93/dashy:latest\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Your dashboard should now be up and running at ",(0,t.jsx)(s.code,{children:"http://localhost:8080"})," (or your servers IP address/ domain, and the port that you chose) \ud83c\udf89"]}),"\n",(0,t.jsxs)(s.p,{children:["Dashy is also available via GHCR (",(0,t.jsx)(s.code,{children:"ghcr.io/lissy93/dashy"}),").",(0,t.jsx)(s.br,{}),"\nYou can either use ",(0,t.jsx)(s.code,{children:":latest"})," or pin to specific versions (like ",(0,t.jsx)(s.code,{children:"4.0.0"}),").",(0,t.jsx)(s.br,{}),"\nAll images are multi-arch (works on amd64, arm64, and arm/v7).",(0,t.jsx)(s.br,{}),"\nTo use with compose, see our sample ",(0,t.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/master/docker-compose.yml",children:(0,t.jsx)(s.code,{children:"docker-compose.yml"})}),".",(0,t.jsx)(s.br,{}),"\nOnce up and running, check the ",(0,t.jsx)(s.a,{href:"https://dashy.to/docs/configuring",children:"configuring reference"})," and ",(0,t.jsx)(s.a,{href:"https://dashy.to/docs",children:"other docs"}),".",(0,t.jsx)(s.br,{})]}),"\n",(0,t.jsx)(s.admonition,{type:"note",children:(0,t.jsxs)(s.p,{children:["You need to mount a directory for your Dashy settings in ",(0,t.jsx)(s.code,{children:"/app/user-data"}),".\nThe only required file here is the ",(0,t.jsx)(s.code,{children:"conf.yml"}),", but this is also where you can put any other page configs and assets like images/icons, stylesheets, fonts, etc.\nEverything in this directory is served from Dashy's root (e.g. ",(0,t.jsx)(s.code,{children:"/app/user-data/logo.png"})," will be accessible at ",(0,t.jsx)(s.code,{children:"http://[dashy.local]/logo.png"}),")."]})}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"3-user-data-directory",children:"3. User Data Directory"}),"\n",(0,t.jsxs)(s.p,{children:["Your config file should be placed inside ",(0,t.jsx)(s.code,{children:"user-data"})," (in Docker, that's ",(0,t.jsx)(s.code,{children:"/app/user-data/"}),")."]}),"\n",(0,t.jsx)(s.p,{children:"This directory can also contain some optional assets you wish to use within your dashboard, like icons, fonts, styles, scripts, etc."}),"\n",(0,t.jsxs)(s.p,{children:["Any files placed here will be served up to the root of the domain, and override the contents of ",(0,t.jsx)(s.code,{children:"public/"}),".\nFor example, if you had ",(0,t.jsx)(s.code,{children:"user-data/favicon.ico"})," this would be accessible at ",(0,t.jsx)(s.code,{children:"http://my-dashy-instance.local/favicon.ico"})]}),"\n",(0,t.jsxs)(s.p,{children:["Example Files in ",(0,t.jsx)(s.code,{children:"user-data"}),":"]}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"conf.yml"})," - This is the only file that is compulsory, it's your main Dashy config"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"**.yml"})," - Include more config files, if you'd like to have multiple pages, see ",(0,t.jsx)(s.a,{href:"/docs/pages-and-sections#multi-page-support",children:"Multi-page support"})," for docs"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"favicon.ico"})," - The default favicon, shown in the browser's tab title"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"initialization.html"})," - Static HTML page displayed before the app has finished compiling, see ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/public/initialization.html",children:(0,t.jsx)(s.code,{children:"public/initialization.html"})})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"robots.txt"})," - Search engine crawl rules, override this if you want your dashboard to be indexable"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"manifest.json"})," - PWA configuration file, for installing Dashy on mobile devices"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"index.html"})," - The main index page which initializes the client-side app, copy it from ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/public/index.html",children:(0,t.jsx)(s.code,{children:"/public/index.html"})})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"**.html"})," - Write your own HTML pages, and access them at ",(0,t.jsx)(s.code,{children:"http://my-dashy-instance.local/my-page.html"})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"fonts/"})," - Custom fonts (be sure to include the ones already in ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/tree/master/public/fonts",children:(0,t.jsx)(s.code,{children:"public/fonts"})})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"item-icons/"})," - To use your own icons for items on your dashboard, see ",(0,t.jsx)(s.a,{href:"/docs/icons#local-icons",children:"Icons --\x3e Local Icons"})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"web-icons/"})," - Override Dashy logo"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"widget-resources/"})," - Fonts, icons and assets for custom widgets"]}),"\n"]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"4-configure",children:"4. Configure"}),"\n",(0,t.jsxs)(s.p,{children:["Now that you've got Dashy running, you are going to want to set it up with your own content.\nConfig is written in ",(0,t.jsx)(s.a,{href:"https://yaml.org/",children:"YAML Format"}),", and saved in ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/user-data/conf.yml",children:(0,t.jsx)(s.code,{children:"/user-data/conf.yml"})}),".\nThe format on the config file is pretty straight forward. There are four root attributes:"]}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/configuring#pageinfo",children:(0,t.jsx)(s.code,{children:"pageInfo"})})," - Dashboard meta data, like title, description, nav bar links and footer text"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/configuring#appconfig-optional",children:(0,t.jsx)(s.code,{children:"appConfig"})})," - Dashboard settings, like themes, authentication, language and customization"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/configuring#section",children:(0,t.jsx)(s.code,{children:"sections"})})," - An array of sections, each including an array of items"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/configuring#pages-optional",children:(0,t.jsx)(s.code,{children:"pages"})})," - Have multiples pages in your dashboard"]}),"\n"]}),"\n",(0,t.jsxs)(s.p,{children:["You can view a full list of all available config options in the ",(0,t.jsx)(s.a,{href:"/docs/configuring",children:"Configuring Docs"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"pageInfo:\n title: Home Lab\nsections: # An array of sections\n- name: Example Section\n icon: far fa-rocket\n items:\n - title: GitHub\n description: Dashy source code and docs\n icon: fab fa-github\n url: https://github.com/Lissy93/dashy\n - title: Issues\n description: View open issues, or raise a new one\n icon: fas fa-bug\n url: https://github.com/Lissy93/dashy/issues\n- name: Local Services\n items:\n - title: Firewall\n icon: favicon\n url: http://192.168.1.1/\n - title: Game Server\n icon: https://i.ibb.co/710B3Yc/space-invader-x256.png\n url: http://192.168.130.1/\n"})}),"\n",(0,t.jsx)(s.p,{children:"Notes:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["You can use a Docker volume to pass your ",(0,t.jsx)(s.code,{children:"user-data"})," directory from the host into the container\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["E.g. ",(0,t.jsx)(s.code,{children:"-v ./host-system/user-data:/app/user-data"})]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(s.li,{children:"It's also possible to edit your config directly through the UI, and changes will be saved in this file"}),"\n",(0,t.jsxs)(s.li,{children:["Check your config against Dashy's schema, with ",(0,t.jsx)(s.code,{children:"docker exec -it [container-id] yarn validate-config"})]}),"\n",(0,t.jsxs)(s.li,{children:["You might find it helpful to look at some examples, a collection of which can be ",(0,t.jsx)(s.a,{href:"https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10",children:"found here"})]}),"\n",(0,t.jsx)(s.li,{children:"It's also possible to load a remote config, e.g. from a GitHub Gist"}),"\n"]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"5-further-customisation",children:"5. Further Customisation"}),"\n",(0,t.jsxs)(s.p,{children:["Once you've got Dashy setup, you'll want to ensure the container is properly healthy, secured, backed up and kept up-to-date. All this is covered in the ",(0,t.jsx)(s.a,{href:"/docs/management",children:"Management Docs"}),"."]}),"\n",(0,t.jsx)(s.p,{children:"You might also want to check out the docs for specific features you'd like to use:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/authentication",children:"Authentication"})," - Setting up authentication to protect your dashboard"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/alternate-views",children:"Alternate Views"})," - Using the startpage and workspace view"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/backup-restore",children:"Backup & Restore"})," - Guide to Dashy's cloud sync feature"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/icons",children:"Icons"})," - Outline of all available icon types for sections and items"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/multi-language-support",children:"Localisation"})," - How to change language, or add your own"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/status-indicators",children:"Status Indicators"})," - Using Dashy to monitor uptime and status of your apps/services and hosts"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/searching",children:"Search & Shortcuts"})," - Using instant filter, web search and custom hotkeys"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/theming",children:"Theming"})," - Complete guide to applying, writing and modifying themes and styles"]}),"\n"]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"6-final-note",children:"6. Final Note"}),"\n",(0,t.jsxs)(s.p,{children:["If you need any help or support in getting Dashy running, head over to the ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/discussions",children:"Discussions"})," page. If you think you've found a bug, please do ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/issues/new/choose",children:"raise it"})," so it can be fixed. For contact options, see the ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/.github/SUPPORT",children:"Support Page"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["If you're enjoying Dashy, and have a few minutes to spare, please do take a moment to look at the ",(0,t.jsx)(s.a,{href:"/docs/contributing",children:"Contributing Page"}),". Huge thanks to ",(0,t.jsx)(s.a,{href:"/docs/credits",children:"everyone"})," who has already helped out!"]}),"\n",(0,t.jsx)(s.p,{children:"Enjoy your dashboard :)"}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"alternative-deployment-method-1---from-source",children:"Alternative Deployment Method 1 - From Source"}),"\n",(0,t.jsxs)(s.p,{children:["You can also easily run the app on your system without Docker. For this ",(0,t.jsx)(s.a,{href:"https://git-scm.com/downloads",children:"Git"}),", ",(0,t.jsx)(s.a,{href:"https://nodejs.org/",children:"Node.js"}),", and ",(0,t.jsx)(s.a,{href:"https://yarnpkg.com/",children:"Yarn"})," are required."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-bash",children:"git clone https://github.com/Lissy93/dashy.git && cd dashy\nyarn # Install dependencies\nyarn build # Build the app\nyarn start # Start the app\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Then edit ",(0,t.jsx)(s.code,{children:"./user-data/conf.yml"})]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"alternative-deployment-method-2---netlify",children:"Alternative Deployment Method 2 - Netlify"}),"\n",(0,t.jsxs)(s.p,{children:["Don't have a server? No problem! You can run Dashy for free on Netlify (as well as many ",(0,t.jsx)(s.a,{href:"/docs/deployment#deploy-to-cloud-service",children:"other cloud providers"}),"). All you need it a GitHub account."]}),"\n",(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsx)(s.li,{children:"Fork Dashy's repository on GitHub"}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"https://app.netlify.com/login/",children:"Log in"})," to Netlify with GitHub"]}),"\n",(0,t.jsxs)(s.li,{children:['Click "New site from Git" and select your forked repo, then click ',(0,t.jsx)(s.strong,{children:"Deploy"}),"!"]}),"\n",(0,t.jsxs)(s.li,{children:["You can then edit the config in ",(0,t.jsx)(s.code,{children:"./user-data/conf.yml"})," in your repo, and Netlify will rebuild the app"]}),"\n"]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"alternative-deployment-method-3---cloud-services",children:"Alternative Deployment Method 3 - Cloud Services"}),"\n",(0,t.jsx)(s.p,{children:"Dashy supports 1-Click deployments on several popular cloud platforms. To spin up a new instance, just click a link below:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/dashy",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/ZxtzrP3/netlify.png",width:"18"})," Deploy to Netlify"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://heroku.com/deploy?template=https://github.com/Lissy93/dashy",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/d2P1WZ7/heroku.png",width:"18"})," Deploy to Heroku"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://vercel.com/new/project?template=https://github.com/lissy93/dashy",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/Ld2FZzb/vercel.png",width:"18"})," Deploy to Vercel"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://render.com/deploy?repo=https://github.com/lissy93/dashy/tree/deploy_render",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/xCHtzgh/render.png",width:"18"})," Deploy to Render"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://deploy.cloud.run/?git_repo=https://github.com/lissy93/dashy.git",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/J7MGymY/googlecloud.png",width:"18"})," Deploy to GCP"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/Lissy93/dashy/master/docker-compose.yml",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/HVWVYF7/docker.png",width:"18"})," Deploy to PWD"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://easypanel.io/docs/templates/dashy",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/7NxnM2P/easypanel.png",width:"18"})," Deploy to Easypanel"]})}),"\n"]})]})}function h(e={}){const{wrapper:s}={...(0,o.R)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},28453(e,s,n){n.d(s,{R:()=>r,x:()=>a});var i=n(96540);const t={},o=i.createContext(t);function r(e){const s=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),i.createElement(o.Provider,{value:s},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[2814],{81953(e,s,n){n.r(s),n.d(s,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>r,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"quick-start","title":"Quick Start","description":"Welcome to Dashy! So glad you\'re here \ud83d\ude0a In a couple of minutes, you\'ll have your new dashboard up and running \ud83d\ude80","source":"@site/docs/quick-start.md","sourceDirName":".","slug":"/quick-start","permalink":"/docs/quick-start","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/quick-start.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","next":{"title":"Deployment","permalink":"/docs/deployment"}}');var t=n(74848),o=n(28453);const r={},a="Quick Start",l={},c=[{value:"1. Prerequisites",id:"1-prerequisites",level:2},{value:"2. Installation",id:"2-installation",level:2},{value:"3. User Data Directory",id:"3-user-data-directory",level:2},{value:"4. Configure",id:"4-configure",level:2},{value:"5. Further Customisation",id:"5-further-customisation",level:2},{value:"6. Final Note",id:"6-final-note",level:2},{value:"Alternative Deployment Method 1 - From Source",id:"alternative-deployment-method-1---from-source",level:2},{value:"Alternative Deployment Method 2 - Netlify",id:"alternative-deployment-method-2---netlify",level:2},{value:"Alternative Deployment Method 3 - Cloud Services",id:"alternative-deployment-method-3---cloud-services",level:2}];function d(e){const s={a:"a",admonition:"admonition",br:"br",code:"code",h1:"h1",h2:"h2",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(s.header,{children:(0,t.jsx)(s.h1,{id:"quick-start",children:"Quick Start"})}),"\n",(0,t.jsx)(s.p,{children:"Welcome to Dashy! So glad you're here \ud83d\ude0a In a couple of minutes, you'll have your new dashboard up and running \ud83d\ude80"}),"\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.strong,{children:"TLDR;"})," Run ",(0,t.jsx)(s.code,{children:"docker run -p 8080:8080 lissy93/dashy"}),", then open ",(0,t.jsx)(s.code,{children:"http://localhost:8080"})]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"1-prerequisites",children:"1. Prerequisites"}),"\n",(0,t.jsxs)(s.p,{children:["The quickest and easiest method of running Dashy is using Docker (or another container engine). You can find installation instructions for your system in the ",(0,t.jsx)(s.a,{href:"https://docs.docker.com/get-docker/",children:"Docker Documentation"}),".\nIf you don't want to use Docker, then you can use one of Dashy's other supported installation methods instead, all of which are outlined in the ",(0,t.jsx)(s.a,{href:"/docs/deployment",children:"Deployment Docs"}),"."]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"2-installation",children:"2. Installation"}),"\n",(0,t.jsx)(s.p,{children:"To pull the latest image, and build and start the app run:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-bash",children:"docker run -d \\\n -p 8080:8080 \\\n -v ~/dashy-data:/app/user-data \\\n --name dashy \\\n --restart=always \\\n lissy93/dashy:latest\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Your dashboard should now be up and running at ",(0,t.jsx)(s.code,{children:"http://localhost:8080"})," (or your servers IP address/ domain, and the port that you chose) \ud83c\udf89"]}),"\n",(0,t.jsxs)(s.p,{children:["Dashy is also available via GHCR (",(0,t.jsx)(s.code,{children:"ghcr.io/lissy93/dashy"}),").",(0,t.jsx)(s.br,{}),"\nYou can either use ",(0,t.jsx)(s.code,{children:":latest"})," or pin to specific versions (like ",(0,t.jsx)(s.code,{children:"4.0.0"}),").",(0,t.jsx)(s.br,{}),"\nAll images are multi-arch (works on amd64, arm64, and arm/v7).",(0,t.jsx)(s.br,{}),"\nTo use with compose, see our sample ",(0,t.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/master/docker-compose.yml",children:(0,t.jsx)(s.code,{children:"docker-compose.yml"})}),".",(0,t.jsx)(s.br,{}),"\nOnce up and running, check the ",(0,t.jsx)(s.a,{href:"https://dashy.to/docs/configuring",children:"configuring reference"})," and ",(0,t.jsx)(s.a,{href:"https://dashy.to/docs",children:"other docs"}),".",(0,t.jsx)(s.br,{})]}),"\n",(0,t.jsx)(s.admonition,{type:"note",children:(0,t.jsxs)(s.p,{children:["You need to mount a directory for your Dashy settings in ",(0,t.jsx)(s.code,{children:"/app/user-data"}),".\nThe only required file here is the ",(0,t.jsx)(s.code,{children:"conf.yml"}),", but this is also where you can put any other page configs and assets like images/icons, stylesheets, fonts, etc.\nEverything in this directory is served from Dashy's root (e.g. ",(0,t.jsx)(s.code,{children:"/app/user-data/logo.png"})," will be accessible at ",(0,t.jsx)(s.code,{children:"http://[dashy.local]/logo.png"}),")."]})}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"3-user-data-directory",children:"3. User Data Directory"}),"\n",(0,t.jsxs)(s.p,{children:["Your config file should be placed inside ",(0,t.jsx)(s.code,{children:"user-data"})," (in Docker, that's ",(0,t.jsx)(s.code,{children:"/app/user-data/"}),")."]}),"\n",(0,t.jsx)(s.p,{children:"This directory can also contain some optional assets you wish to use within your dashboard, like icons, fonts, styles, scripts, etc."}),"\n",(0,t.jsxs)(s.p,{children:["Any files placed here will be served up to the root of the domain, and override the contents of ",(0,t.jsx)(s.code,{children:"public/"}),".\nFor example, if you had ",(0,t.jsx)(s.code,{children:"user-data/favicon.ico"})," this would be accessible at ",(0,t.jsx)(s.code,{children:"http://my-dashy-instance.local/favicon.ico"})]}),"\n",(0,t.jsxs)(s.p,{children:["Example Files in ",(0,t.jsx)(s.code,{children:"user-data"}),":"]}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"conf.yml"})," - This is the only file that is compulsory, it's your main Dashy config"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"**.yml"})," - Include more config files, if you'd like to have multiple pages, see ",(0,t.jsx)(s.a,{href:"/docs/pages-and-sections#multi-page-support",children:"Multi-page support"})," for docs"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"favicon.ico"})," - The default favicon, shown in the browser's tab title"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"initialization.html"})," - Static HTML page displayed before the app has finished compiling, see ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/public/initialization.html",children:(0,t.jsx)(s.code,{children:"public/initialization.html"})})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"robots.txt"})," - Search engine crawl rules, override this if you want your dashboard to be indexable"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"manifest.json"})," - PWA configuration file, for installing Dashy on mobile devices"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"index.html"})," - The main index page which initializes the client-side app, copy it from ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/public/index.html",children:(0,t.jsx)(s.code,{children:"/public/index.html"})})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"**.html"})," - Write your own HTML pages, and access them at ",(0,t.jsx)(s.code,{children:"http://my-dashy-instance.local/my-page.html"})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"fonts/"})," - Custom fonts (be sure to include the ones already in ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/tree/master/public/fonts",children:(0,t.jsx)(s.code,{children:"public/fonts"})})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"item-icons/"})," - To use your own icons for items on your dashboard, see ",(0,t.jsx)(s.a,{href:"/docs/icons#local-icons",children:"Icons --\x3e Local Icons"})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"web-icons/"})," - Override Dashy logo"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:"widget-resources/"})," - Fonts, icons and assets for custom widgets"]}),"\n"]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"4-configure",children:"4. Configure"}),"\n",(0,t.jsxs)(s.p,{children:["Now that you've got Dashy running, you are going to want to set it up with your own content.\nConfig is written in ",(0,t.jsx)(s.a,{href:"https://yaml.org/",children:"YAML Format"}),", and saved in ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/user-data/conf.yml",children:(0,t.jsx)(s.code,{children:"/user-data/conf.yml"})}),".\nThe format on the config file is pretty straight forward. There are four root attributes:"]}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/configuring#pageinfo",children:(0,t.jsx)(s.code,{children:"pageInfo"})})," - Dashboard meta data, like title, description, nav bar links and footer text"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/configuring#appconfig-optional",children:(0,t.jsx)(s.code,{children:"appConfig"})})," - Dashboard settings, like themes, authentication, language and customization"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/configuring#section",children:(0,t.jsx)(s.code,{children:"sections"})})," - An array of sections, each including an array of items"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/configuring#pages-optional",children:(0,t.jsx)(s.code,{children:"pages"})})," - Have multiples pages in your dashboard"]}),"\n"]}),"\n",(0,t.jsxs)(s.p,{children:["You can view a full list of all available config options in the ",(0,t.jsx)(s.a,{href:"/docs/configuring",children:"Configuring Docs"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"pageInfo:\n title: Home Lab\nsections: # An array of sections\n- name: Example Section\n icon: far fa-rocket\n items:\n - title: GitHub\n description: Dashy source code and docs\n icon: fab fa-github\n url: https://github.com/Lissy93/dashy\n - title: Issues\n description: View open issues, or raise a new one\n icon: fas fa-bug\n url: https://github.com/Lissy93/dashy/issues\n- name: Local Services\n items:\n - title: Firewall\n icon: favicon\n url: http://192.168.1.1/\n - title: Game Server\n icon: https://i.ibb.co/710B3Yc/space-invader-x256.png\n url: http://192.168.130.1/\n"})}),"\n",(0,t.jsx)(s.p,{children:"Notes:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["You can use a Docker volume to pass your ",(0,t.jsx)(s.code,{children:"user-data"})," directory from the host into the container\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["E.g. ",(0,t.jsx)(s.code,{children:"-v ./host-system/user-data:/app/user-data"})]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(s.li,{children:"It's also possible to edit your config directly through the UI, and changes will be saved in this file"}),"\n",(0,t.jsxs)(s.li,{children:["Check your config against Dashy's schema, with ",(0,t.jsx)(s.code,{children:"docker exec -it [container-id] yarn validate-config"})]}),"\n",(0,t.jsxs)(s.li,{children:["You might find it helpful to look at some examples, a collection of which can be ",(0,t.jsx)(s.a,{href:"https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10",children:"found here"})]}),"\n",(0,t.jsx)(s.li,{children:"It's also possible to load a remote config, e.g. from a GitHub Gist"}),"\n"]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"5-further-customisation",children:"5. Further Customisation"}),"\n",(0,t.jsxs)(s.p,{children:["Once you've got Dashy setup, you'll want to ensure the container is properly healthy, secured, backed up and kept up-to-date. All this is covered in the ",(0,t.jsx)(s.a,{href:"/docs/management",children:"Management Docs"}),"."]}),"\n",(0,t.jsx)(s.p,{children:"You might also want to check out the docs for specific features you'd like to use:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/authentication",children:"Authentication"})," - Setting up authentication to protect your dashboard"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/alternate-views",children:"Alternate Views"})," - Using the startpage and workspace view"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/backup-restore",children:"Backup & Restore"})," - Guide to Dashy's cloud sync feature"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/icons",children:"Icons"})," - Outline of all available icon types for sections and items"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/multi-language-support",children:"Localisation"})," - How to change language, or add your own"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/status-indicators",children:"Status Indicators"})," - Using Dashy to monitor uptime and status of your apps/services and hosts"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/searching",children:"Search & Shortcuts"})," - Using instant filter, web search and custom hotkeys"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"/docs/theming",children:"Theming"})," - Complete guide to applying, writing and modifying themes and styles"]}),"\n"]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"6-final-note",children:"6. Final Note"}),"\n",(0,t.jsxs)(s.p,{children:["If you need any help or support in getting Dashy running, head over to the ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/discussions",children:"Discussions"})," page. If you think you've found a bug, please do ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/issues/new/choose",children:"raise it"})," so it can be fixed. For contact options, see the ",(0,t.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/.github/SUPPORT",children:"Support Page"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["If you're enjoying Dashy, and have a few minutes to spare, please do take a moment to look at the ",(0,t.jsx)(s.a,{href:"/docs/contributing",children:"Contributing Page"}),". Huge thanks to ",(0,t.jsx)(s.a,{href:"/docs/credits",children:"everyone"})," who has already helped out!"]}),"\n",(0,t.jsx)(s.p,{children:"Enjoy your dashboard :)"}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"alternative-deployment-method-1---from-source",children:"Alternative Deployment Method 1 - From Source"}),"\n",(0,t.jsxs)(s.p,{children:["You can also easily run the app on your system without Docker. For this ",(0,t.jsx)(s.a,{href:"https://git-scm.com/downloads",children:"Git"}),", ",(0,t.jsx)(s.a,{href:"https://nodejs.org/",children:"Node.js"}),", and ",(0,t.jsx)(s.a,{href:"https://yarnpkg.com/",children:"Yarn"})," are required."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-bash",children:"git clone https://github.com/Lissy93/dashy.git && cd dashy\nyarn # Install dependencies\nyarn build # Build the app\nyarn start # Start the app\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Then edit ",(0,t.jsx)(s.code,{children:"./user-data/conf.yml"})]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"alternative-deployment-method-2---netlify",children:"Alternative Deployment Method 2 - Netlify"}),"\n",(0,t.jsxs)(s.p,{children:["Don't have a server? No problem! You can run Dashy for free on Netlify (as well as many ",(0,t.jsx)(s.a,{href:"/docs/deployment#deploy-to-cloud-service",children:"other cloud providers"}),"). All you need it a GitHub account."]}),"\n",(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsx)(s.li,{children:"Fork Dashy's repository on GitHub"}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.a,{href:"https://app.netlify.com/login/",children:"Log in"})," to Netlify with GitHub"]}),"\n",(0,t.jsxs)(s.li,{children:['Click "New site from Git" and select your forked repo, then click ',(0,t.jsx)(s.strong,{children:"Deploy"}),"!"]}),"\n",(0,t.jsxs)(s.li,{children:["You can then edit the config in ",(0,t.jsx)(s.code,{children:"./user-data/conf.yml"})," in your repo, and Netlify will rebuild the app"]}),"\n"]}),"\n",(0,t.jsx)(s.hr,{}),"\n",(0,t.jsx)(s.h2,{id:"alternative-deployment-method-3---cloud-services",children:"Alternative Deployment Method 3 - Cloud Services"}),"\n",(0,t.jsx)(s.p,{children:"Dashy supports 1-Click deployments on several popular cloud platforms. To spin up a new instance, just click a link below:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/dashy",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/ZxtzrP3/netlify.png",width:"18"})," Deploy to Netlify"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://heroku.com/deploy?template=https://github.com/Lissy93/dashy",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/d2P1WZ7/heroku.png",width:"18"})," Deploy to Heroku"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://vercel.com/new/project?template=https://github.com/lissy93/dashy",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/Ld2FZzb/vercel.png",width:"18"})," Deploy to Vercel"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://render.com/deploy?repo=https://github.com/lissy93/dashy/tree/deploy_render",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/xCHtzgh/render.png",width:"18"})," Deploy to Render"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://deploy.cloud.run/?git_repo=https://github.com/lissy93/dashy.git",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/J7MGymY/googlecloud.png",width:"18"})," Deploy to GCP"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/Lissy93/dashy/master/docker-compose.yml",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/HVWVYF7/docker.png",width:"18"})," Deploy to PWD"]})}),"\n",(0,t.jsx)(s.li,{children:(0,t.jsxs)(s.a,{href:"https://easypanel.io/docs/templates/dashy",children:[(0,t.jsx)(s.img,{src:"https://i.ibb.co/7NxnM2P/easypanel.png",width:"18"})," Deploy to Easypanel"]})}),"\n"]})]})}function h(e={}){const{wrapper:s}={...(0,o.R)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},28453(e,s,n){n.d(s,{R:()=>r,x:()=>a});var i=n(96540);const t={},o=i.createContext(t);function r(e){const s=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),i.createElement(o.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/7776d47a.68fccae7.js b/assets/js/7776d47a.2e144c54.js similarity index 99% rename from assets/js/7776d47a.68fccae7.js rename to assets/js/7776d47a.2e144c54.js index 0f3dc4e4..98377b04 100644 --- a/assets/js/7776d47a.68fccae7.js +++ b/assets/js/7776d47a.2e144c54.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[6138],{53874(e,n,s){s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>t,metadata:()=>i,toc:()=>d});const i=JSON.parse('{"id":"authentication/authelia-oidc","title":"Authelia OIDC","description":"Dashy supports using Authelia as its OIDC provider.","source":"@site/docs/authentication/authelia-oidc.md","sourceDirName":"authentication","slug":"/authentication/authelia-oidc","permalink":"/docs/authentication/authelia-oidc","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/authelia-oidc.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Authentication","permalink":"/docs/authentication"},"next":{"title":"Authentik OIDC","permalink":"/docs/authentication/authentik"}}');var r=s(74848),o=s(28453);const t={},a="Authelia OIDC",l={},d=[{value:"Contents",id:"contents",level:3},{value:"1. Deploy Authelia",id:"1-deploy-authelia",level:2},{value:"2. Configure Authelia",id:"2-configure-authelia",level:2},{value:"Generate secrets and keys",id:"generate-secrets-and-keys",level:3},{value:"Users database",id:"users-database",level:3},{value:"Main configuration",id:"main-configuration",level:3},{value:"Summary",id:"summary",level:3},{value:"3. Enabling Authelia in Dashy",id:"3-enabling-authelia-in-dashy",level:2},{value:"4. Groups and Visibility",id:"4-groups-and-visibility",level:2},{value:"5. Silent token renewal (optional)",id:"5-silent-token-renewal-optional",level:2},{value:"Troubleshooting common Authelia Issues",id:"troubleshooting-common-authelia-issues",level:2},{value:"Config validation fails on session cookies",id:"config-validation-fails-on-session-cookies",level:4},{value:"Dashy server can't fetch Authelia's discovery doc",id:"dashy-server-cant-fetch-authelias-discovery-doc",level:4},{value:""Critical Configuration Load Error" with a CORS message on first load",id:"critical-configuration-load-error-with-a-cors-message-on-first-load",level:4},{value:"CORS error on the token endpoint after login",id:"cors-error-on-the-token-endpoint-after-login",level:4},{value:"Redirect loop after login",id:"redirect-loop-after-login",level:4},{value:"invalid_request: redirect URI mismatch",id:"invalid_request-redirect-uri-mismatch",level:4},{value:"Logged in but config saves return 403",id:"logged-in-but-config-saves-return-403",level:4},{value:"Issuer mismatch behind a reverse proxy",id:"issuer-mismatch-behind-a-reverse-proxy",level:4},{value:"Locked out after a few wrong passwords",id:"locked-out-after-a-few-wrong-passwords",level:4},{value:"Two-factor prompt appears unexpectedly",id:"two-factor-prompt-appears-unexpectedly",level:4},{value:"Username claim is a UUID, not the username",id:"username-claim-is-a-uuid-not-the-username",level:4},{value:"Token expired / clock skew",id:"token-expired--clock-skew",level:4},{value:"Numeric Client ID truncated",id:"numeric-client-id-truncated",level:4},{value:"Dashy server can't reach Authelia",id:"dashy-server-cant-reach-authelia",level:4},{value:"Config change to auth.oidc not picked up",id:"config-change-to-authoidc-not-picked-up",level:4},{value:"How it Works",id:"how-it-works",level:2},{value:"Client side",id:"client-side",level:3},{value:"Server side",id:"server-side",level:3},{value:"Visual Overview",id:"visual-overview",level:3}];function c(e){const n={a:"a",br:"br",code:"code",details:"details",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",mermaid:"mermaid",p:"p",pre:"pre",summary:"summary",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"authelia-oidc",children:"Authelia OIDC"})}),"\n",(0,r.jsxs)(n.p,{children:["Dashy supports using ",(0,r.jsx)(n.a,{href:"https://www.authelia.com/",children:"Authelia"})," as its OIDC provider."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://www.authelia.com/",children:"Authelia"})," is an ",(0,r.jsx)(n.a,{href:"https://github.com/authelia/authelia",children:"open source"})," authentication and authorization server for self-hosted services. It speaks OIDC, supports MFA, and is configured via a single YAML file. Users live in a flat file, LDAP, or your own SQL store. It's a popular pairing with reverse proxies for forward-auth across many apps, and the same instance can act as your Dashy OIDC provider."]}),"\n",(0,r.jsx)(n.h3,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#1-deploy-authelia",children:"1. Deploy Authelia"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#2-configure-authelia",children:"2. Configure Authelia"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#generate-secrets-and-keys",children:"Generate secrets and keys"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#users-database",children:"Users database"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#main-configuration",children:"Main configuration"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#3-enabling-authelia-in-dashy",children:"3. Enabling Authelia in Dashy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#4-groups-and-visibility",children:"4. Groups and Visibility"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#5-silent-token-renewal-optional",children:"5. Silent token renewal (optional)"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#troubleshooting-common-authelia-issues",children:"Troubleshooting"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#how-it-works",children:"How it Works"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#client-side",children:"Client side"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#server-side",children:"Server side"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#visual-overview",children:"Visual Overview"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"1-deploy-authelia",children:"1. Deploy Authelia"}),"\n",(0,r.jsxs)(n.p,{children:["Authelia v4.38+ requires HTTPS for the session cookie URLs. In production, terminate TLS at a reverse proxy on a real domain. For local testing the example below uses a self-signed cert and ",(0,r.jsx)(n.code,{children:"lvh.me"}),", a wildcard DNS name that resolves to ",(0,r.jsx)(n.code,{children:"127.0.0.1"})," everywhere, so no /etc/hosts edits are needed."]}),"\n",(0,r.jsxs)(n.details,{children:["\n ",(0,r.jsxs)(n.summary,{children:["Example ",(0,r.jsx)(n.code,{children:"docker-compose.yml"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'name: dashy-authelia\n\nservices:\n authelia:\n image: authelia/authelia:4.38\n restart: unless-stopped\n ports:\n - "9091:9091"\n volumes:\n - ./config:/config\n environment:\n TZ: UTC\n healthcheck:\n test: ["CMD-SHELL", "wget --no-check-certificate -qO- https://127.0.0.1:9091/api/health >/dev/null 2>&1"]\n start_period: 20s\n interval: 10s\n timeout: 5s\n retries: 10\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"config/"})," directory holds Authelia's ",(0,r.jsx)(n.code,{children:"configuration.yml"}),", the user database, the OIDC signing key, and (for local testing) the TLS cert. Generate them in step 2."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"2-configure-authelia",children:"2. Configure Authelia"}),"\n",(0,r.jsx)(n.h3,{id:"generate-secrets-and-keys",children:"Generate secrets and keys"}),"\n",(0,r.jsx)(n.p,{children:"Authelia needs four secrets: a session secret, a storage encryption key, an OIDC HMAC secret, and an RSA private key for signing id_tokens. For local testing also generate a self-signed TLS cert."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'mkdir -p config\nSESSION_SECRET=$(openssl rand -hex 64)\nSTORAGE_KEY=$(openssl rand -hex 32)\nHMAC_SECRET=$(openssl rand -hex 64)\n\nopenssl genrsa -out config/oidc-private.pem 2048\n\n# Local testing only. In production use a real cert from your reverse proxy.\nopenssl req -x509 -newkey rsa:2048 -nodes \\\n -keyout config/tls.key -out config/tls.crt \\\n -days 365 -subj "/CN=authelia.lvh.me" \\\n -addext "subjectAltName=DNS:authelia.lvh.me,DNS:*.lvh.me"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Keep the three shell variables around: you'll paste them into ",(0,r.jsx)(n.code,{children:"configuration.yml"})," below."]}),"\n",(0,r.jsx)(n.h3,{id:"users-database",children:"Users database"}),"\n",(0,r.jsx)(n.p,{children:"Hash a password with Authelia's CLI:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run --rm authelia/authelia:4.38 \\\n authelia crypto hash generate argon2 --password 'YourPasswordHere'\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Copy the ",(0,r.jsx)(n.code,{children:"Digest:"})," value into ",(0,r.jsx)(n.code,{children:"config/users_database.yml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'users:\n authelia-admin:\n disabled: false\n displayname: "Authelia Admin"\n password: "$argon2id$v=19$m=65536,t=3,p=4$..."\n email: admin@example.com\n groups:\n - admins\n authelia-user:\n disabled: false\n displayname: "Authelia User"\n password: "$argon2id$v=19$m=65536,t=3,p=4$..."\n email: user@example.com\n groups:\n - users\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"admins"})," group name is what Dashy's ",(0,r.jsx)(n.code,{children:"adminGroup"})," will match."]}),"\n",(0,r.jsx)(n.h3,{id:"main-configuration",children:"Main configuration"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"config/configuration.yml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"theme: light\nserver:\n address: 'tcp://:9091'\n tls:\n certificate: /config/tls.crt\n key: /config/tls.key\nlog:\n level: info\ntotp:\n issuer: authelia.lvh.me\nauthentication_backend:\n password_reset:\n disable: true\n file:\n path: /config/users_database.yml\n password:\n algorithm: argon2id\naccess_control:\n default_policy: one_factor\nsession:\n secret: \n cookies:\n - domain: 'lvh.me'\n authelia_url: 'https://authelia.lvh.me:9091'\nregulation:\n max_retries: 3\n find_time: 2m\n ban_time: 5m\nstorage:\n encryption_key: \n local:\n path: /config/db.sqlite3\nnotifier:\n filesystem:\n filename: /config/notifications.txt\nidentity_providers:\n oidc:\n hmac_secret: \n cors:\n endpoints:\n - authorization\n - token\n - revocation\n - introspection\n - userinfo\n allowed_origins:\n - http://localhost:4000\n jwks:\n - key_id: dashy\n algorithm: RS256\n use: sig\n key: |\n -----BEGIN PRIVATE KEY-----\n (contents of config/oidc-private.pem)\n -----END PRIVATE KEY-----\n clients:\n - client_id: dashy\n client_name: Dashy\n public: true\n authorization_policy: one_factor\n require_pkce: true\n pkce_challenge_method: S256\n redirect_uris:\n - http://localhost:4000\n - http://localhost:4000/\n scopes:\n - openid\n - profile\n - email\n - groups\n userinfo_signed_response_alg: none\n"})}),"\n",(0,r.jsx)(n.p,{children:"Key things to note:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"public: true"})," and ",(0,r.jsx)(n.code,{children:"require_pkce: true"})," are required: Dashy is a SPA, so it must be a public client with PKCE"]}),"\n",(0,r.jsx)(n.li,{children:"Register the redirect URI both with and without a trailing slash, since browsers may strip or add one"}),"\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"groups"})," scope is required for the ",(0,r.jsx)(n.code,{children:"adminGroup"})," check"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"userinfo_signed_response_alg: none"})," keeps the userinfo response as JSON, which is what ",(0,r.jsx)(n.code,{children:"oidc-client-ts"})," (Dashy's OIDC client) expects"]}),"\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"cors"})," block is required: Dashy is a SPA and makes cross-origin calls from the browser to Authelia's token and userinfo endpoints, and without these origins allow-listed the browser blocks the token exchange after login. ",(0,r.jsx)(n.code,{children:"allowed_origins"})," must list Dashy's exact origin (scheme + host + port)"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["For production, swap ",(0,r.jsx)(n.code,{children:"server.tls"})," out for a reverse proxy doing HTTPS, change ",(0,r.jsx)(n.code,{children:"session.cookies"})," to your real domain (e.g. ",(0,r.jsx)(n.code,{children:"domain: example.com"}),", ",(0,r.jsx)(n.code,{children:"authelia_url: https://auth.example.com"}),"), and pick a real user backend (LDAP or SQL)."]}),"\n",(0,r.jsx)(n.p,{children:"Bring Authelia up:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker compose up -d\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Once it's healthy, ",(0,r.jsx)(n.code,{children:"https://authelia.lvh.me:9091/.well-known/openid-configuration"})," should return JSON with ",(0,r.jsx)(n.code,{children:"issuer: https://authelia.lvh.me:9091"}),". That issuer URL is what Dashy needs."]}),"\n",(0,r.jsx)(n.h3,{id:"summary",children:"Summary"}),"\n",(0,r.jsx)(n.p,{children:"Authelia is now configured with an OIDC client for Dashy."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"3-enabling-authelia-in-dashy",children:"3. Enabling Authelia in Dashy"}),"\n",(0,r.jsxs)(n.p,{children:["In ",(0,r.jsx)(n.code,{children:"/user-data/conf.yml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n ...\n disableConfigurationForNonAdmin: true\n auth:\n enableOidc: true\n oidc:\n clientId: dashy\n endpoint: https://authelia.lvh.me:9091\n adminGroup: admins\n scope: openid profile email groups\n"})}),"\n",(0,r.jsx)(n.p,{children:"Where:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"disableConfigurationForNonAdmin"})," - Prevent read/write config access to non-admin users"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"auth.enableOidc"})," - Set the auth mode to OIDC"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"clientId"})," - Must match the ",(0,r.jsx)(n.code,{children:"client_id"})," field of the Authelia client"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"endpoint"})," - The Authelia base URL (the OIDC issuer). Dashy appends ",(0,r.jsx)(n.code,{children:"/.well-known/openid-configuration"})," itself"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"adminGroup"})," - Name of the Authelia group that grants admin in Dashy"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"scope"})," - Must include ",(0,r.jsx)(n.code,{children:"groups"})," for the ",(0,r.jsx)(n.code,{children:"adminGroup"})," check to work"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Restart Dashy for these changes to take effect."}),"\n",(0,r.jsxs)(n.p,{children:["If Authelia uses a self-signed cert, Dashy's server has to trust it before it can fetch the discovery doc or JWKS. Start Dashy with ",(0,r.jsx)(n.code,{children:"NODE_EXTRA_CA_CERTS=/path/to/tls.crt"}),", or mount your CA bundle into the Dashy container and point that env var at it. With a real cert this isn't needed. Your browser also needs to trust the cert (visit ",(0,r.jsx)(n.code,{children:"https://authelia.lvh.me:9091"})," once and accept the warning, or import the cert into the browser's trust store)."]}),"\n",(0,r.jsx)(n.p,{children:"Everything should now be fully configured and working \ud83c\udf89\nWhen you load Dashy, you'll be redirected to Authelia's login page. After signing in, you'll land back on Dashy's homepage with full access, and all of Dashy's client, server and asset endpoints will be locked behind authentication."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"4-groups-and-visibility",children:"4. Groups and Visibility"}),"\n",(0,r.jsxs)(n.p,{children:["Once group membership is in the id_token, you can use it to hide or show pages, sections and items. The property name is ",(0,r.jsx)(n.code,{children:"hideForKeycloakUsers"})," / ",(0,r.jsx)(n.code,{children:"showForKeycloakUsers"})," (the name is historical; it works for any OIDC provider, including Authelia)."]}),"\n",(0,r.jsxs)(n.p,{children:["To make an Admin section visible only to members of ",(0,r.jsx)(n.code,{children:"admins"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"displayData:\n showForKeycloakUsers:\n groups:\n - admins\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Both ",(0,r.jsx)(n.code,{children:"showForKeycloakUsers"})," and ",(0,r.jsx)(n.code,{children:"hideForKeycloakUsers"})," accept lists of ",(0,r.jsx)(n.code,{children:"groups"})," and ",(0,r.jsx)(n.code,{children:"roles"}),". If a user matches an entry they're allowed or excluded as defined."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"sections:\n - name: Internal Tools\n displayData:\n showForKeycloakUsers:\n groups: ['admins']\n hideForKeycloakUsers:\n groups: ['users']\n items:\n - title: Hidden from interns\n displayData:\n hideForKeycloakUsers:\n groups: ['interns']\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"5-silent-token-renewal-optional",children:"5. Silent token renewal (optional)"}),"\n",(0,r.jsxs)(n.p,{children:["By default, when your token expires Dashy sends you back through Authelia's login to get a new one. Set ",(0,r.jsx)(n.code,{children:"enableSilentRenew: true"})," to have Dashy refresh the session quietly in the background instead, using a refresh token:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" oidc:\n clientId: dashy\n endpoint: https://authelia.lvh.me:9091\n adminGroup: admins\n scope: openid profile email groups\n enableSilentRenew: true\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Dashy adds the ",(0,r.jsx)(n.code,{children:"offline_access"})," scope to its request automatically, but Authelia only issues a refresh token if the client is allowed to. Add ",(0,r.jsx)(n.code,{children:"offline_access"})," to the client's ",(0,r.jsx)(n.code,{children:"scopes"})," and ",(0,r.jsx)(n.code,{children:"refresh_token"})," to its ",(0,r.jsx)(n.code,{children:"grant_types"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" clients:\n - client_id: dashy\n scopes:\n - openid\n - profile\n - email\n - groups\n - offline_access\n grant_types:\n - authorization_code\n - refresh_token\n"})}),"\n",(0,r.jsxs)(n.p,{children:["It's off by default, and if a refresh ever fails Dashy falls back to the normal sign-in. See ",(0,r.jsx)(n.a,{href:"/docs/oidc#silent-token-renewal",children:"silent token renewal"})," for the full notes and caveats."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"troubleshooting-common-authelia-issues",children:"Troubleshooting common Authelia Issues"}),"\n",(0,r.jsx)(n.h4,{id:"config-validation-fails-on-session-cookies",children:"Config validation fails on session cookies"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Authelia refuses to start, complaining ",(0,r.jsx)(n.code,{children:"option 'authelia_url' does not have a secure scheme"})," or ",(0,r.jsx)(n.code,{children:"option 'domain' is not a valid cookie domain"}),".",(0,r.jsx)(n.br,{}),"\nSolution: v4.38+ requires HTTPS for ",(0,r.jsx)(n.code,{children:"authelia_url"})," and a cookie domain with at least one period. Use a real domain (",(0,r.jsx)(n.code,{children:"example.com"}),", ",(0,r.jsx)(n.code,{children:"auth.example.com"}),") or ",(0,r.jsx)(n.code,{children:"lvh.me"})," for local testing, and enable TLS either on Authelia or in front of it."]}),"\n",(0,r.jsx)(n.h4,{id:"dashy-server-cant-fetch-authelias-discovery-doc",children:"Dashy server can't fetch Authelia's discovery doc"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Dashy server logs show TLS errors (",(0,r.jsx)(n.code,{children:"self-signed certificate"}),", ",(0,r.jsx)(n.code,{children:"UNABLE_TO_VERIFY_LEAF_SIGNATURE"}),") when fetching ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"})," or the JWKS.",(0,r.jsx)(n.br,{}),"\nSolution: Authelia is on a self-signed cert. Start Dashy with ",(0,r.jsx)(n.code,{children:"NODE_EXTRA_CA_CERTS=/path/to/tls.crt"}),", or use a real certificate. The signing cert for OIDC tokens is separate from the TLS cert: it's the TLS cert that needs to be trusted by Node."]}),"\n",(0,r.jsx)(n.h4,{id:"critical-configuration-load-error-with-a-cors-message-on-first-load",children:'"Critical Configuration Load Error" with a CORS message on first load'}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Browser shows ",(0,r.jsx)(n.code,{children:"Cross-Origin Request Blocked ... at https://authelia.lvh.me:9091/.well-known/openid-configuration. (Reason: CORS request did not succeed). Status code: (null)"})," before any redirect to Authelia, and Dashy renders a Critical Configuration Load Error.",(0,r.jsx)(n.br,{}),"\nSolution: Misleading error: this is the self-signed cert, not CORS. When the browser sees an untrusted HTTPS cert during a JS fetch it blocks the request silently with no chance for you to click through. Open ",(0,r.jsx)(n.code,{children:"https://authelia.lvh.me:9091"})," directly in the same browser, click through the warning, then reload Dashy. For a less hacky setup, use ",(0,r.jsx)(n.a,{href:"https://github.com/FiloSottile/mkcert",children:"mkcert"})," to generate a locally-trusted cert, or terminate TLS at a reverse proxy with a real Let's Encrypt cert."]}),"\n",(0,r.jsx)(n.h4,{id:"cors-error-on-the-token-endpoint-after-login",children:"CORS error on the token endpoint after login"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Login at Authelia succeeds, but the redirect back to Dashy fails with a CORS error on a POST to ",(0,r.jsx)(n.code,{children:"/api/oidc/token"}),".",(0,r.jsx)(n.br,{}),"\nSolution: Authelia's CORS allow-list doesn't include Dashy's origin. Add the ",(0,r.jsx)(n.code,{children:"identity_providers.oidc.cors"})," block from step 2 with ",(0,r.jsx)(n.code,{children:"token"})," (and ideally ",(0,r.jsx)(n.code,{children:"authorization"}),", ",(0,r.jsx)(n.code,{children:"userinfo"}),", ",(0,r.jsx)(n.code,{children:"revocation"}),", ",(0,r.jsx)(n.code,{children:"introspection"}),") under ",(0,r.jsx)(n.code,{children:"endpoints"}),", and Dashy's origin under ",(0,r.jsx)(n.code,{children:"allowed_origins"}),". Restart Authelia."]}),"\n",(0,r.jsx)(n.h4,{id:"redirect-loop-after-login",children:"Redirect loop after login"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Browser bounces between Dashy and Authelia repeatedly.",(0,r.jsx)(n.br,{}),"\nSolution: ",(0,r.jsx)(n.code,{children:"endpoint"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"})," probably includes ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"}),". Drop everything from ",(0,r.jsx)(n.code,{children:".well-known"})," onwards; Dashy appends it itself."]}),"\n",(0,r.jsx)(n.h4,{id:"invalid_request-redirect-uri-mismatch",children:"invalid_request: redirect URI mismatch"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Authelia returns ",(0,r.jsx)(n.code,{children:"invalid_request"})," saying the redirect URI is not registered.",(0,r.jsx)(n.br,{}),"\nSolution: Authelia matches redirect URIs exactly. Register both the bare URL and the trailing-slash variant (",(0,r.jsx)(n.code,{children:"http://localhost:4000"})," and ",(0,r.jsx)(n.code,{children:"http://localhost:4000/"}),") on the client."]}),"\n",(0,r.jsx)(n.h4,{id:"logged-in-but-config-saves-return-403",children:"Logged in but config saves return 403"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: User authenticates fine, but saving the dashboard returns 403.",(0,r.jsx)(n.br,{}),"\nSolution: The id_token isn't carrying the group claim. Paste the token (from localStorage, key ",(0,r.jsx)(n.code,{children:"ID_TOKEN"}),") into ",(0,r.jsx)(n.a,{href:"https://jwt.io",children:"jwt.io"})," and look for ",(0,r.jsx)(n.code,{children:"groups"}),". If it's missing, the client's ",(0,r.jsx)(n.code,{children:"scopes"})," list doesn't include ",(0,r.jsx)(n.code,{children:"groups"}),". If the claim is there but the user isn't in it, add them to the ",(0,r.jsx)(n.code,{children:"admins"})," group in ",(0,r.jsx)(n.code,{children:"users_database.yml"})," (or whatever backend you're using) and restart Authelia."]}),"\n",(0,r.jsx)(n.h4,{id:"issuer-mismatch-behind-a-reverse-proxy",children:"Issuer mismatch behind a reverse proxy"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Server logs show ",(0,r.jsx)(n.code,{children:'unexpected "iss" claim value'}),". The browser reaches Authelia at ",(0,r.jsx)(n.code,{children:"https://auth.example.com"})," but Authelia advertises a different issuer.",(0,r.jsx)(n.br,{}),"\nSolution: Make sure your reverse proxy forwards ",(0,r.jsx)(n.code,{children:"X-Forwarded-Proto: https"})," and ",(0,r.jsx)(n.code,{children:"X-Forwarded-Host"}),", and that ",(0,r.jsx)(n.code,{children:"session.cookies[].authelia_url"})," matches the public URL exactly."]}),"\n",(0,r.jsx)(n.h4,{id:"locked-out-after-a-few-wrong-passwords",children:"Locked out after a few wrong passwords"}),"\n",(0,r.jsxs)(n.p,{children:['Problem: Login fails with "User has been banned" even with the correct password.',(0,r.jsx)(n.br,{}),"\nSolution: The ",(0,r.jsx)(n.code,{children:"regulation"})," block bans an account after ",(0,r.jsx)(n.code,{children:"max_retries"})," failed attempts within ",(0,r.jsx)(n.code,{children:"find_time"}),". Default ban is 5 minutes. Wait it out or ",(0,r.jsx)(n.code,{children:"docker compose restart authelia"})," to clear the in-memory ban."]}),"\n",(0,r.jsx)(n.h4,{id:"two-factor-prompt-appears-unexpectedly",children:"Two-factor prompt appears unexpectedly"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: After password entry Authelia asks for a TOTP code or other 2FA, but you wanted password-only for Dashy.",(0,r.jsx)(n.br,{}),"\nSolution: ",(0,r.jsx)(n.code,{children:"access_control.default_policy"})," is set to ",(0,r.jsx)(n.code,{children:"two_factor"}),". Change it to ",(0,r.jsx)(n.code,{children:"one_factor"}),", or add a specific rule with ",(0,r.jsx)(n.code,{children:"policy: one_factor"})," matching the Dashy redirect."]}),"\n",(0,r.jsx)(n.h4,{id:"username-claim-is-a-uuid-not-the-username",children:"Username claim is a UUID, not the username"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Dashy logs show users with random-looking subject IDs.",(0,r.jsx)(n.br,{}),"\nSolution: Authelia's ",(0,r.jsx)(n.code,{children:"sub"})," claim is an opaque UUID per user. Dashy's OIDC client falls through ",(0,r.jsx)(n.code,{children:"preferred_username"}),", ",(0,r.jsx)(n.code,{children:"email"}),", then ",(0,r.jsx)(n.code,{children:"sub"}),", so as long as the ",(0,r.jsx)(n.code,{children:"profile"})," and ",(0,r.jsx)(n.code,{children:"email"})," scopes are requested the username should be human-readable. Confirm both scopes are in the client's ",(0,r.jsx)(n.code,{children:"scopes"})," list."]}),"\n",(0,r.jsx)(n.h4,{id:"token-expired--clock-skew",children:"Token expired / clock skew"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: 401s with ",(0,r.jsx)(n.code,{children:'"exp" claim timestamp check failed'})," or ",(0,r.jsx)(n.code,{children:'"iat" claim timestamp check failed'}),", even just after login.",(0,r.jsx)(n.br,{}),"\nSolution: Dashy allows 30 seconds of drift. Sync clocks on both hosts with NTP. Container clocks follow their host, so it's almost always the host that's drifted."]}),"\n",(0,r.jsx)(n.h4,{id:"numeric-client-id-truncated",children:"Numeric Client ID truncated"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Audience mismatch when ",(0,r.jsx)(n.code,{children:"clientId"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"})," is a long numeric string.",(0,r.jsx)(n.br,{}),"\nSolution: Wrap numeric Client IDs in quotes (e.g. ",(0,r.jsx)(n.code,{children:'clientId: "12345678901234567"'}),"). Without quotes YAML parses the value as a JS number and loses precision past around 15 digits."]}),"\n",(0,r.jsx)(n.h4,{id:"dashy-server-cant-reach-authelia",children:"Dashy server can't reach Authelia"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Auth'd API calls return 401 and Dashy logs show fetch errors for ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"}),".",(0,r.jsx)(n.br,{}),"\nSolution: ",(0,r.jsx)(n.code,{children:"endpoint"})," must be reachable from inside the Dashy container, not just from the browser. If both run in Docker, put them on the same network. Test with ",(0,r.jsx)(n.code,{children:'docker exec wget -qO- "$ENDPOINT/.well-known/openid-configuration"'}),"."]}),"\n",(0,r.jsx)(n.h4,{id:"config-change-to-authoidc-not-picked-up",children:"Config change to auth.oidc not picked up"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Updated ",(0,r.jsx)(n.code,{children:"clientId"}),", ",(0,r.jsx)(n.code,{children:"endpoint"}),", ",(0,r.jsx)(n.code,{children:"adminGroup"})," or ",(0,r.jsx)(n.code,{children:"scope"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"}),", but Dashy still uses the old values.",(0,r.jsx)(n.br,{}),"\nSolution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under ",(0,r.jsx)(n.code,{children:"auth.oidc"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"how-it-works",children:"How it Works"}),"\n",(0,r.jsx)(n.p,{children:"If you're a developer or contributor looking to understand or make changes to Dashy's OIDC implementation, the following outlines how it's wired together."}),"\n",(0,r.jsx)(n.p,{children:"The same OIDC pipeline backs Authelia, Authentik, Keycloak, and any other generic OIDC provider. The only Authelia-specific code is your configuration; everything else is shared."}),"\n",(0,r.jsx)(n.h3,{id:"client-side",children:"Client side"}),"\n",(0,r.jsxs)(n.p,{children:["Boot starts in ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/main.js",children:(0,r.jsx)(n.code,{children:"src/main.js"})}),". After the initial ",(0,r.jsx)(n.code,{children:"/conf.yml"})," fetch parses the auth block, ",(0,r.jsx)(n.code,{children:"isOidcEnabled()"})," decides whether to lazily import ",(0,r.jsx)(n.a,{href:"https://github.com/authts/oidc-client-ts",children:(0,r.jsx)(n.code,{children:"oidc-client-ts"})})," and call ",(0,r.jsx)(n.code,{children:"initOidcAuth()"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/OidcAuth.js",children:(0,r.jsx)(n.code,{children:"src/utils/auth/OidcAuth.js"})})," wraps ",(0,r.jsx)(n.code,{children:"oidc-client-ts"}),". On load it inspects the URL: if it sees a ",(0,r.jsx)(n.code,{children:"?code="})," callback it runs ",(0,r.jsx)(n.code,{children:"userManager.signinCallback()"})," to exchange the code (and PKCE verifier) for tokens, persists the user info, and hard-redirects to ",(0,r.jsx)(n.code,{children:"/"}),". Otherwise it calls ",(0,r.jsx)(n.code,{children:"userManager.getUser()"}),"; if there's no usable session it falls through to ",(0,r.jsx)(n.code,{children:"userManager.signinRedirect()"})," to send the browser to Authelia. A short-lived ",(0,r.jsx)(n.code,{children:"sessionStorage"})," guard prevents the redirect loop that would otherwise occur if the IdP returns without a usable user."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"persistUserInfo()"})," writes the raw ",(0,r.jsx)(n.code,{children:"id_token"}),", the user's ",(0,r.jsx)(n.code,{children:"groups"})," and ",(0,r.jsx)(n.code,{children:"roles"}),", a derived ",(0,r.jsx)(n.code,{children:"isAdmin"})," flag, and a username (falling back through ",(0,r.jsx)(n.code,{children:"preferred_username"}),", ",(0,r.jsx)(n.code,{children:"email"}),", and ",(0,r.jsx)(n.code,{children:"sub"}),") to localStorage. The keys (",(0,r.jsx)(n.code,{children:"ID_TOKEN"}),", ",(0,r.jsx)(n.code,{children:"KEYCLOAK_INFO"}),", ",(0,r.jsx)(n.code,{children:"USERNAME"}),", ",(0,r.jsx)(n.code,{children:"ISADMIN"}),") live in ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/config/defaults.js",children:(0,r.jsx)(n.code,{children:"src/utils/config/defaults.js"})}),"; the ",(0,r.jsx)(n.code,{children:"KEYCLOAK_INFO"})," name is historical and reused for all OIDC providers, including Authelia."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/getApiAuthHeader.js",children:(0,r.jsx)(n.code,{children:"src/utils/auth/getApiAuthHeader.js"})})," builds the Authorization header for every internal API call. It does a client-side ",(0,r.jsx)(n.code,{children:"exp"})," check and returns ",(0,r.jsx)(n.code,{children:"null"})," for missing or expired tokens, so the next request triggers a fresh login rather than a 401."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/IsVisibleToUser.js",children:(0,r.jsx)(n.code,{children:"src/utils/IsVisibleToUser.js"})})," reads ",(0,r.jsx)(n.code,{children:"KEYCLOAK_INFO"})," when evaluating ",(0,r.jsx)(n.code,{children:"showForKeycloakUsers"})," and ",(0,r.jsx)(n.code,{children:"hideForKeycloakUsers"})," rules."]}),"\n",(0,r.jsx)(n.h3,{id:"server-side",children:"Server side"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/auth-oidc.js",children:(0,r.jsx)(n.code,{children:"services/auth-oidc.js"})})," contains the entire server-side auth surface, in five small pieces:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"loadOidcSettings()"})," reads ",(0,r.jsx)(n.code,{children:"auth.oidc"})," (or ",(0,r.jsx)(n.code,{children:"auth.keycloak"}),") at boot and returns a normalised ",(0,r.jsx)(n.code,{children:"{ issuer, clientId, adminGroup, adminRole }"}),". For generic OIDC providers the ",(0,r.jsx)(n.code,{children:"issuer"})," is whatever you set as ",(0,r.jsx)(n.code,{children:"endpoint"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"}),", verbatim"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"createOidcMiddleware()"})," returns a Connect middleware. Permissive on no-token requests so the SPA can bootstrap; otherwise it verifies the Bearer token against the issuer's JWKS using ",(0,r.jsx)(n.a,{href:"https://github.com/panva/jose",children:(0,r.jsx)(n.code,{children:"jose"})}),". Checks cover signature, issuer (against the canonical value from the discovery doc), audience (must equal ",(0,r.jsx)(n.code,{children:"clientId"}),"), and expiry, with a 30-second clock-skew tolerance. Sets ",(0,r.jsx)(n.code,{children:"req.auth = { user, isAdmin, claims }"})," on success, ",(0,r.jsx)(n.code,{children:"401"})," on failure"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"getIssuerContext()"})," lazily fetches ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"})," on first use and wraps ",(0,r.jsx)(n.code,{children:"jwks_uri"})," in ",(0,r.jsx)(n.code,{children:"createRemoteJWKSet"}),", which handles JWKS caching and on-demand key rotation. The result is memoised per-issuer for the life of the process"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"deriveIsAdmin()"})," checks the token's ",(0,r.jsx)(n.code,{children:"groups"})," claim against ",(0,r.jsx)(n.code,{children:"adminGroup"}),", and the top-level ",(0,r.jsx)(n.code,{children:"roles"})," claim against ",(0,r.jsx)(n.code,{children:"adminRole"})," (for Keycloak it also folds in the nested ",(0,r.jsx)(n.code,{children:"realm_access.roles"})," / ",(0,r.jsx)(n.code,{children:"resource_access..roles"})," arrays). Authelia only emits ",(0,r.jsx)(n.code,{children:"groups"}),", so the group path is what's used in practice"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"maybeBootstrapConfig()"})," is the stripped-response helper. When auth is configured, guest access is off, and an unauthenticated request hits the root ",(0,r.jsx)(n.code,{children:"/conf.yml"}),", it returns a minimal copy with only ",(0,r.jsx)(n.code,{children:"appConfig.auth"}),", ",(0,r.jsx)(n.code,{children:"appConfig.enableServiceWorker"}),", and a ",(0,r.jsx)(n.code,{children:"pageInfo.title"})," of ",(0,r.jsx)(n.code,{children:"Login | "}),". Sections, items, hostnames and any other secrets never leave the server"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/app.js",children:(0,r.jsx)(n.code,{children:"services/app.js"})})," wires it all together. The middleware mounts as ",(0,r.jsx)(n.code,{children:"protectConfig"})," in front of every YAML route and config-mutating route. The ",(0,r.jsx)(n.code,{children:"/*.yml"})," handler sets ",(0,r.jsx)(n.code,{children:"Cache-Control: private, no-store"})," and ",(0,r.jsx)(n.code,{children:"Vary: Authorization"})," whenever auth is configured (so intermediate caches can never mix auth states), then calls ",(0,r.jsx)(n.code,{children:"maybeBootstrapConfig"}),"; a stripped result is sent as-is, otherwise ",(0,r.jsx)(n.code,{children:"res.sendFile"})," serves the full file. ",(0,r.jsx)(n.code,{children:"POST /config-manager/save"})," is additionally guarded by ",(0,r.jsx)(n.code,{children:"requireAdmin"}),", which returns ",(0,r.jsx)(n.code,{children:"401"})," if ",(0,r.jsx)(n.code,{children:"req.auth"})," is unset and ",(0,r.jsx)(n.code,{children:"403"})," if ",(0,r.jsx)(n.code,{children:"req.auth.isAdmin"})," is false."]}),"\n",(0,r.jsx)(n.h3,{id:"visual-overview",children:"Visual Overview"}),"\n",(0,r.jsxs)(n.details,{children:["\n",(0,r.jsx)(n.summary,{children:"End-to-end authentication flow"}),"\n",(0,r.jsx)(n.mermaid,{value:"sequenceDiagram\n autonumber\n actor User\n participant Browser as Browser (Dashy SPA)\n participant Dashy as Dashy Server\n participant Auth as Authelia\n\n Note over Browser,Dashy: 1. Bootstrap (no token yet)\n User->>Browser: Open Dashy\n Browser->>Dashy: GET /conf.yml\n Dashy--\x3e>Browser: 200 stripped conf
(auth block + minimal pageInfo)\n Browser->>Browser: Parse auth.oidc\n\n Note over Browser,Auth: 2. OIDC login (Authorization Code + PKCE)\n Browser->>Auth: 302 /api/oidc/authorization
client_id, code_challenge, redirect_uri\n User->>Auth: Submit credentials\n Auth--\x3e>Browser: 302 back with ?code=AUTH_CODE\n Browser->>Auth: POST /api/oidc/token
(code + code_verifier)\n Auth--\x3e>Browser: id_token + access_token\n Browser->>Browser: Store tokens in localStorage
hard reload to /\n\n Note over Browser,Dashy: 3. Authenticated read\n Browser->>Dashy: GET /conf.yml
Authorization: Bearer id_token\n Dashy->>Auth: Fetch discovery + JWKS
(lazy on first call, then cached)\n Auth--\x3e>Dashy: openid-configuration + JWKS\n Dashy->>Dashy: Verify signature / issuer / audience / expiry\n Dashy--\x3e>Browser: 200 full conf.yml\n\n Note over Browser,Dashy: 4. Write request (admin only)\n Browser->>Dashy: POST /config-manager/save
Authorization: Bearer id_token\n Dashy->>Dashy: Verify token, derive isAdmin from
adminGroup claim\n alt isAdmin\n Dashy--\x3e>Browser: 200 saved\n else not admin\n Dashy--\x3e>Browser: 403 Forbidden\n end"}),"\n"]}),"\n",(0,r.jsxs)(n.details,{children:["\n",(0,r.jsx)(n.summary,{children:"Server-side request handling"}),"\n",(0,r.jsx)(n.mermaid,{value:'flowchart TD\n Req([Incoming request to Dashy server])\n Req --\x3e Bearer{Authorization:
Bearer present?}\n\n Bearer -- No --\x3e NoAuth[req.auth unset
pass through]:::neutral\n Bearer -- Yes --\x3e Verify[/Verify JWT against cached JWKS:
signature \xb7 issuer \xb7 audience \xb7 expiry/]\n Verify --\x3e Valid{Valid?}\n Valid -- No --\x3e R401Bad[/401 Unauthorized/]:::err\n Valid -- Yes --\x3e SetAuth[req.auth = user + isAdmin
derived from claims]:::ok\n\n NoAuth --\x3e Route{Endpoint}\n SetAuth --\x3e Route\n\n Route -- "GET /conf.yml" --\x3e ConfGate{req.auth?}\n ConfGate -- No --\x3e Strip[/200 stripped conf
auth + minimal pageInfo/]:::ok\n ConfGate -- Yes --\x3e Full[/200 full conf.yml/]:::ok\n\n Route -- "POST /config-manager/save" --\x3e SaveGate{req.auth
and isAdmin?}\n SaveGate -- No req.auth --\x3e R401Save[/401 Unauthorized/]:::err\n SaveGate -- Not admin --\x3e R403[/403 Forbidden/]:::err\n SaveGate -- Admin --\x3e Saved[/200 saved/]:::ok\n\n classDef ok fill:#bbf7d0,stroke:#16a34a,color:#14532d\n classDef err fill:#fecaca,stroke:#dc2626,color:#7f1d1d\n classDef neutral fill:#dbeafe,stroke:#2563eb,color:#1e3a8a'}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(c,{...e})}):c(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>a});var i=s(96540);const r={},o=i.createContext(r);function t(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[6138],{53874(e,n,s){s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>t,metadata:()=>i,toc:()=>d});const i=JSON.parse('{"id":"authentication/authelia-oidc","title":"Authelia OIDC","description":"Dashy supports using Authelia as its OIDC provider.","source":"@site/docs/authentication/authelia-oidc.md","sourceDirName":"authentication","slug":"/authentication/authelia-oidc","permalink":"/docs/authentication/authelia-oidc","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/authelia-oidc.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Authentication","permalink":"/docs/authentication"},"next":{"title":"Authentik OIDC","permalink":"/docs/authentication/authentik"}}');var r=s(74848),o=s(28453);const t={},a="Authelia OIDC",l={},d=[{value:"Contents",id:"contents",level:3},{value:"1. Deploy Authelia",id:"1-deploy-authelia",level:2},{value:"2. Configure Authelia",id:"2-configure-authelia",level:2},{value:"Generate secrets and keys",id:"generate-secrets-and-keys",level:3},{value:"Users database",id:"users-database",level:3},{value:"Main configuration",id:"main-configuration",level:3},{value:"Summary",id:"summary",level:3},{value:"3. Enabling Authelia in Dashy",id:"3-enabling-authelia-in-dashy",level:2},{value:"4. Groups and Visibility",id:"4-groups-and-visibility",level:2},{value:"5. Silent token renewal (optional)",id:"5-silent-token-renewal-optional",level:2},{value:"Troubleshooting common Authelia Issues",id:"troubleshooting-common-authelia-issues",level:2},{value:"Config validation fails on session cookies",id:"config-validation-fails-on-session-cookies",level:4},{value:"Dashy server can't fetch Authelia's discovery doc",id:"dashy-server-cant-fetch-authelias-discovery-doc",level:4},{value:""Critical Configuration Load Error" with a CORS message on first load",id:"critical-configuration-load-error-with-a-cors-message-on-first-load",level:4},{value:"CORS error on the token endpoint after login",id:"cors-error-on-the-token-endpoint-after-login",level:4},{value:"Redirect loop after login",id:"redirect-loop-after-login",level:4},{value:"invalid_request: redirect URI mismatch",id:"invalid_request-redirect-uri-mismatch",level:4},{value:"Logged in but config saves return 403",id:"logged-in-but-config-saves-return-403",level:4},{value:"Issuer mismatch behind a reverse proxy",id:"issuer-mismatch-behind-a-reverse-proxy",level:4},{value:"Locked out after a few wrong passwords",id:"locked-out-after-a-few-wrong-passwords",level:4},{value:"Two-factor prompt appears unexpectedly",id:"two-factor-prompt-appears-unexpectedly",level:4},{value:"Username claim is a UUID, not the username",id:"username-claim-is-a-uuid-not-the-username",level:4},{value:"Token expired / clock skew",id:"token-expired--clock-skew",level:4},{value:"Numeric Client ID truncated",id:"numeric-client-id-truncated",level:4},{value:"Dashy server can't reach Authelia",id:"dashy-server-cant-reach-authelia",level:4},{value:"Config change to auth.oidc not picked up",id:"config-change-to-authoidc-not-picked-up",level:4},{value:"How it Works",id:"how-it-works",level:2},{value:"Client side",id:"client-side",level:3},{value:"Server side",id:"server-side",level:3},{value:"Visual Overview",id:"visual-overview",level:3}];function c(e){const n={a:"a",br:"br",code:"code",details:"details",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",mermaid:"mermaid",p:"p",pre:"pre",summary:"summary",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"authelia-oidc",children:"Authelia OIDC"})}),"\n",(0,r.jsxs)(n.p,{children:["Dashy supports using ",(0,r.jsx)(n.a,{href:"https://www.authelia.com/",children:"Authelia"})," as its OIDC provider."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://www.authelia.com/",children:"Authelia"})," is an ",(0,r.jsx)(n.a,{href:"https://github.com/authelia/authelia",children:"open source"})," authentication and authorization server for self-hosted services. It speaks OIDC, supports MFA, and is configured via a single YAML file. Users live in a flat file, LDAP, or your own SQL store. It's a popular pairing with reverse proxies for forward-auth across many apps, and the same instance can act as your Dashy OIDC provider."]}),"\n",(0,r.jsx)(n.h3,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#1-deploy-authelia",children:"1. Deploy Authelia"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#2-configure-authelia",children:"2. Configure Authelia"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#generate-secrets-and-keys",children:"Generate secrets and keys"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#users-database",children:"Users database"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#main-configuration",children:"Main configuration"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#3-enabling-authelia-in-dashy",children:"3. Enabling Authelia in Dashy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#4-groups-and-visibility",children:"4. Groups and Visibility"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#5-silent-token-renewal-optional",children:"5. Silent token renewal (optional)"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#troubleshooting-common-authelia-issues",children:"Troubleshooting"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#how-it-works",children:"How it Works"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#client-side",children:"Client side"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#server-side",children:"Server side"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#visual-overview",children:"Visual Overview"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"1-deploy-authelia",children:"1. Deploy Authelia"}),"\n",(0,r.jsxs)(n.p,{children:["Authelia v4.38+ requires HTTPS for the session cookie URLs. In production, terminate TLS at a reverse proxy on a real domain. For local testing the example below uses a self-signed cert and ",(0,r.jsx)(n.code,{children:"lvh.me"}),", a wildcard DNS name that resolves to ",(0,r.jsx)(n.code,{children:"127.0.0.1"})," everywhere, so no /etc/hosts edits are needed."]}),"\n",(0,r.jsxs)(n.details,{children:["\n ",(0,r.jsxs)(n.summary,{children:["Example ",(0,r.jsx)(n.code,{children:"docker-compose.yml"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'name: dashy-authelia\n\nservices:\n authelia:\n image: authelia/authelia:4.38\n restart: unless-stopped\n ports:\n - "9091:9091"\n volumes:\n - ./config:/config\n environment:\n TZ: UTC\n healthcheck:\n test: ["CMD-SHELL", "wget --no-check-certificate -qO- https://127.0.0.1:9091/api/health >/dev/null 2>&1"]\n start_period: 20s\n interval: 10s\n timeout: 5s\n retries: 10\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"config/"})," directory holds Authelia's ",(0,r.jsx)(n.code,{children:"configuration.yml"}),", the user database, the OIDC signing key, and (for local testing) the TLS cert. Generate them in step 2."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"2-configure-authelia",children:"2. Configure Authelia"}),"\n",(0,r.jsx)(n.h3,{id:"generate-secrets-and-keys",children:"Generate secrets and keys"}),"\n",(0,r.jsx)(n.p,{children:"Authelia needs four secrets: a session secret, a storage encryption key, an OIDC HMAC secret, and an RSA private key for signing id_tokens. For local testing also generate a self-signed TLS cert."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'mkdir -p config\nSESSION_SECRET=$(openssl rand -hex 64)\nSTORAGE_KEY=$(openssl rand -hex 32)\nHMAC_SECRET=$(openssl rand -hex 64)\n\nopenssl genrsa -out config/oidc-private.pem 2048\n\n# Local testing only. In production use a real cert from your reverse proxy.\nopenssl req -x509 -newkey rsa:2048 -nodes \\\n -keyout config/tls.key -out config/tls.crt \\\n -days 365 -subj "/CN=authelia.lvh.me" \\\n -addext "subjectAltName=DNS:authelia.lvh.me,DNS:*.lvh.me"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Keep the three shell variables around: you'll paste them into ",(0,r.jsx)(n.code,{children:"configuration.yml"})," below."]}),"\n",(0,r.jsx)(n.h3,{id:"users-database",children:"Users database"}),"\n",(0,r.jsx)(n.p,{children:"Hash a password with Authelia's CLI:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run --rm authelia/authelia:4.38 \\\n authelia crypto hash generate argon2 --password 'YourPasswordHere'\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Copy the ",(0,r.jsx)(n.code,{children:"Digest:"})," value into ",(0,r.jsx)(n.code,{children:"config/users_database.yml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'users:\n authelia-admin:\n disabled: false\n displayname: "Authelia Admin"\n password: "$argon2id$v=19$m=65536,t=3,p=4$..."\n email: admin@example.com\n groups:\n - admins\n authelia-user:\n disabled: false\n displayname: "Authelia User"\n password: "$argon2id$v=19$m=65536,t=3,p=4$..."\n email: user@example.com\n groups:\n - users\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"admins"})," group name is what Dashy's ",(0,r.jsx)(n.code,{children:"adminGroup"})," will match."]}),"\n",(0,r.jsx)(n.h3,{id:"main-configuration",children:"Main configuration"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"config/configuration.yml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"theme: light\nserver:\n address: 'tcp://:9091'\n tls:\n certificate: /config/tls.crt\n key: /config/tls.key\nlog:\n level: info\ntotp:\n issuer: authelia.lvh.me\nauthentication_backend:\n password_reset:\n disable: true\n file:\n path: /config/users_database.yml\n password:\n algorithm: argon2id\naccess_control:\n default_policy: one_factor\nsession:\n secret: \n cookies:\n - domain: 'lvh.me'\n authelia_url: 'https://authelia.lvh.me:9091'\nregulation:\n max_retries: 3\n find_time: 2m\n ban_time: 5m\nstorage:\n encryption_key: \n local:\n path: /config/db.sqlite3\nnotifier:\n filesystem:\n filename: /config/notifications.txt\nidentity_providers:\n oidc:\n hmac_secret: \n cors:\n endpoints:\n - authorization\n - token\n - revocation\n - introspection\n - userinfo\n allowed_origins:\n - http://localhost:4000\n jwks:\n - key_id: dashy\n algorithm: RS256\n use: sig\n key: |\n -----BEGIN PRIVATE KEY-----\n (contents of config/oidc-private.pem)\n -----END PRIVATE KEY-----\n clients:\n - client_id: dashy\n client_name: Dashy\n public: true\n authorization_policy: one_factor\n require_pkce: true\n pkce_challenge_method: S256\n redirect_uris:\n - http://localhost:4000\n - http://localhost:4000/\n scopes:\n - openid\n - profile\n - email\n - groups\n userinfo_signed_response_alg: none\n"})}),"\n",(0,r.jsx)(n.p,{children:"Key things to note:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"public: true"})," and ",(0,r.jsx)(n.code,{children:"require_pkce: true"})," are required: Dashy is a SPA, so it must be a public client with PKCE"]}),"\n",(0,r.jsx)(n.li,{children:"Register the redirect URI both with and without a trailing slash, since browsers may strip or add one"}),"\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"groups"})," scope is required for the ",(0,r.jsx)(n.code,{children:"adminGroup"})," check"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"userinfo_signed_response_alg: none"})," keeps the userinfo response as JSON, which is what ",(0,r.jsx)(n.code,{children:"oidc-client-ts"})," (Dashy's OIDC client) expects"]}),"\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"cors"})," block is required: Dashy is a SPA and makes cross-origin calls from the browser to Authelia's token and userinfo endpoints, and without these origins allow-listed the browser blocks the token exchange after login. ",(0,r.jsx)(n.code,{children:"allowed_origins"})," must list Dashy's exact origin (scheme + host + port)"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["For production, swap ",(0,r.jsx)(n.code,{children:"server.tls"})," out for a reverse proxy doing HTTPS, change ",(0,r.jsx)(n.code,{children:"session.cookies"})," to your real domain (e.g. ",(0,r.jsx)(n.code,{children:"domain: example.com"}),", ",(0,r.jsx)(n.code,{children:"authelia_url: https://auth.example.com"}),"), and pick a real user backend (LDAP or SQL)."]}),"\n",(0,r.jsx)(n.p,{children:"Bring Authelia up:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker compose up -d\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Once it's healthy, ",(0,r.jsx)(n.code,{children:"https://authelia.lvh.me:9091/.well-known/openid-configuration"})," should return JSON with ",(0,r.jsx)(n.code,{children:"issuer: https://authelia.lvh.me:9091"}),". That issuer URL is what Dashy needs."]}),"\n",(0,r.jsx)(n.h3,{id:"summary",children:"Summary"}),"\n",(0,r.jsx)(n.p,{children:"Authelia is now configured with an OIDC client for Dashy."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"3-enabling-authelia-in-dashy",children:"3. Enabling Authelia in Dashy"}),"\n",(0,r.jsxs)(n.p,{children:["In ",(0,r.jsx)(n.code,{children:"/user-data/conf.yml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n ...\n disableConfigurationForNonAdmin: true\n auth:\n enableOidc: true\n oidc:\n clientId: dashy\n endpoint: https://authelia.lvh.me:9091\n adminGroup: admins\n scope: openid profile email groups\n"})}),"\n",(0,r.jsx)(n.p,{children:"Where:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"disableConfigurationForNonAdmin"})," - Prevent read/write config access to non-admin users"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"auth.enableOidc"})," - Set the auth mode to OIDC"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"clientId"})," - Must match the ",(0,r.jsx)(n.code,{children:"client_id"})," field of the Authelia client"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"endpoint"})," - The Authelia base URL (the OIDC issuer). Dashy appends ",(0,r.jsx)(n.code,{children:"/.well-known/openid-configuration"})," itself"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"adminGroup"})," - Name of the Authelia group that grants admin in Dashy"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"scope"})," - Must include ",(0,r.jsx)(n.code,{children:"groups"})," for the ",(0,r.jsx)(n.code,{children:"adminGroup"})," check to work"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Restart Dashy for these changes to take effect."}),"\n",(0,r.jsxs)(n.p,{children:["If Authelia uses a self-signed cert, Dashy's server has to trust it before it can fetch the discovery doc or JWKS. Start Dashy with ",(0,r.jsx)(n.code,{children:"NODE_EXTRA_CA_CERTS=/path/to/tls.crt"}),", or mount your CA bundle into the Dashy container and point that env var at it. With a real cert this isn't needed. Your browser also needs to trust the cert (visit ",(0,r.jsx)(n.code,{children:"https://authelia.lvh.me:9091"})," once and accept the warning, or import the cert into the browser's trust store)."]}),"\n",(0,r.jsx)(n.p,{children:"Everything should now be fully configured and working \ud83c\udf89\nWhen you load Dashy, you'll be redirected to Authelia's login page. After signing in, you'll land back on Dashy's homepage with full access, and all of Dashy's client, server and asset endpoints will be locked behind authentication."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"4-groups-and-visibility",children:"4. Groups and Visibility"}),"\n",(0,r.jsxs)(n.p,{children:["Once group membership is in the id_token, you can use it to hide or show pages, sections and items. The property name is ",(0,r.jsx)(n.code,{children:"hideForKeycloakUsers"})," / ",(0,r.jsx)(n.code,{children:"showForKeycloakUsers"})," (the name is historical; it works for any OIDC provider, including Authelia)."]}),"\n",(0,r.jsxs)(n.p,{children:["To make an Admin section visible only to members of ",(0,r.jsx)(n.code,{children:"admins"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"displayData:\n showForKeycloakUsers:\n groups:\n - admins\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Both ",(0,r.jsx)(n.code,{children:"showForKeycloakUsers"})," and ",(0,r.jsx)(n.code,{children:"hideForKeycloakUsers"})," accept lists of ",(0,r.jsx)(n.code,{children:"groups"})," and ",(0,r.jsx)(n.code,{children:"roles"}),". If a user matches an entry they're allowed or excluded as defined."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"sections:\n - name: Internal Tools\n displayData:\n showForKeycloakUsers:\n groups: ['admins']\n hideForKeycloakUsers:\n groups: ['users']\n items:\n - title: Hidden from interns\n displayData:\n hideForKeycloakUsers:\n groups: ['interns']\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"5-silent-token-renewal-optional",children:"5. Silent token renewal (optional)"}),"\n",(0,r.jsxs)(n.p,{children:["By default, when your token expires Dashy sends you back through Authelia's login to get a new one. Set ",(0,r.jsx)(n.code,{children:"enableSilentRenew: true"})," to have Dashy refresh the session quietly in the background instead, using a refresh token:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" oidc:\n clientId: dashy\n endpoint: https://authelia.lvh.me:9091\n adminGroup: admins\n scope: openid profile email groups\n enableSilentRenew: true\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Dashy adds the ",(0,r.jsx)(n.code,{children:"offline_access"})," scope to its request automatically, but Authelia only issues a refresh token if the client is allowed to. Add ",(0,r.jsx)(n.code,{children:"offline_access"})," to the client's ",(0,r.jsx)(n.code,{children:"scopes"})," and ",(0,r.jsx)(n.code,{children:"refresh_token"})," to its ",(0,r.jsx)(n.code,{children:"grant_types"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" clients:\n - client_id: dashy\n scopes:\n - openid\n - profile\n - email\n - groups\n - offline_access\n grant_types:\n - authorization_code\n - refresh_token\n"})}),"\n",(0,r.jsxs)(n.p,{children:["It's off by default, and if a refresh ever fails Dashy falls back to the normal sign-in. See ",(0,r.jsx)(n.a,{href:"/docs/oidc#silent-token-renewal",children:"silent token renewal"})," for the full notes and caveats."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"troubleshooting-common-authelia-issues",children:"Troubleshooting common Authelia Issues"}),"\n",(0,r.jsx)(n.h4,{id:"config-validation-fails-on-session-cookies",children:"Config validation fails on session cookies"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Authelia refuses to start, complaining ",(0,r.jsx)(n.code,{children:"option 'authelia_url' does not have a secure scheme"})," or ",(0,r.jsx)(n.code,{children:"option 'domain' is not a valid cookie domain"}),".",(0,r.jsx)(n.br,{}),"\nSolution: v4.38+ requires HTTPS for ",(0,r.jsx)(n.code,{children:"authelia_url"})," and a cookie domain with at least one period. Use a real domain (",(0,r.jsx)(n.code,{children:"example.com"}),", ",(0,r.jsx)(n.code,{children:"auth.example.com"}),") or ",(0,r.jsx)(n.code,{children:"lvh.me"})," for local testing, and enable TLS either on Authelia or in front of it."]}),"\n",(0,r.jsx)(n.h4,{id:"dashy-server-cant-fetch-authelias-discovery-doc",children:"Dashy server can't fetch Authelia's discovery doc"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Dashy server logs show TLS errors (",(0,r.jsx)(n.code,{children:"self-signed certificate"}),", ",(0,r.jsx)(n.code,{children:"UNABLE_TO_VERIFY_LEAF_SIGNATURE"}),") when fetching ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"})," or the JWKS.",(0,r.jsx)(n.br,{}),"\nSolution: Authelia is on a self-signed cert. Start Dashy with ",(0,r.jsx)(n.code,{children:"NODE_EXTRA_CA_CERTS=/path/to/tls.crt"}),", or use a real certificate. The signing cert for OIDC tokens is separate from the TLS cert: it's the TLS cert that needs to be trusted by Node."]}),"\n",(0,r.jsx)(n.h4,{id:"critical-configuration-load-error-with-a-cors-message-on-first-load",children:'"Critical Configuration Load Error" with a CORS message on first load'}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Browser shows ",(0,r.jsx)(n.code,{children:"Cross-Origin Request Blocked ... at https://authelia.lvh.me:9091/.well-known/openid-configuration. (Reason: CORS request did not succeed). Status code: (null)"})," before any redirect to Authelia, and Dashy renders a Critical Configuration Load Error.",(0,r.jsx)(n.br,{}),"\nSolution: Misleading error: this is the self-signed cert, not CORS. When the browser sees an untrusted HTTPS cert during a JS fetch it blocks the request silently with no chance for you to click through. Open ",(0,r.jsx)(n.code,{children:"https://authelia.lvh.me:9091"})," directly in the same browser, click through the warning, then reload Dashy. For a less hacky setup, use ",(0,r.jsx)(n.a,{href:"https://github.com/FiloSottile/mkcert",children:"mkcert"})," to generate a locally-trusted cert, or terminate TLS at a reverse proxy with a real Let's Encrypt cert."]}),"\n",(0,r.jsx)(n.h4,{id:"cors-error-on-the-token-endpoint-after-login",children:"CORS error on the token endpoint after login"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Login at Authelia succeeds, but the redirect back to Dashy fails with a CORS error on a POST to ",(0,r.jsx)(n.code,{children:"/api/oidc/token"}),".",(0,r.jsx)(n.br,{}),"\nSolution: Authelia's CORS allow-list doesn't include Dashy's origin. Add the ",(0,r.jsx)(n.code,{children:"identity_providers.oidc.cors"})," block from step 2 with ",(0,r.jsx)(n.code,{children:"token"})," (and ideally ",(0,r.jsx)(n.code,{children:"authorization"}),", ",(0,r.jsx)(n.code,{children:"userinfo"}),", ",(0,r.jsx)(n.code,{children:"revocation"}),", ",(0,r.jsx)(n.code,{children:"introspection"}),") under ",(0,r.jsx)(n.code,{children:"endpoints"}),", and Dashy's origin under ",(0,r.jsx)(n.code,{children:"allowed_origins"}),". Restart Authelia."]}),"\n",(0,r.jsx)(n.h4,{id:"redirect-loop-after-login",children:"Redirect loop after login"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Browser bounces between Dashy and Authelia repeatedly.",(0,r.jsx)(n.br,{}),"\nSolution: ",(0,r.jsx)(n.code,{children:"endpoint"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"})," probably includes ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"}),". Drop everything from ",(0,r.jsx)(n.code,{children:".well-known"})," onwards; Dashy appends it itself."]}),"\n",(0,r.jsx)(n.h4,{id:"invalid_request-redirect-uri-mismatch",children:"invalid_request: redirect URI mismatch"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Authelia returns ",(0,r.jsx)(n.code,{children:"invalid_request"})," saying the redirect URI is not registered.",(0,r.jsx)(n.br,{}),"\nSolution: Authelia matches redirect URIs exactly. Register both the bare URL and the trailing-slash variant (",(0,r.jsx)(n.code,{children:"http://localhost:4000"})," and ",(0,r.jsx)(n.code,{children:"http://localhost:4000/"}),") on the client."]}),"\n",(0,r.jsx)(n.h4,{id:"logged-in-but-config-saves-return-403",children:"Logged in but config saves return 403"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: User authenticates fine, but saving the dashboard returns 403.",(0,r.jsx)(n.br,{}),"\nSolution: The id_token isn't carrying the group claim. Paste the token (from localStorage, key ",(0,r.jsx)(n.code,{children:"ID_TOKEN"}),") into ",(0,r.jsx)(n.a,{href:"https://jwt.io",children:"jwt.io"})," and look for ",(0,r.jsx)(n.code,{children:"groups"}),". If it's missing, the client's ",(0,r.jsx)(n.code,{children:"scopes"})," list doesn't include ",(0,r.jsx)(n.code,{children:"groups"}),". If the claim is there but the user isn't in it, add them to the ",(0,r.jsx)(n.code,{children:"admins"})," group in ",(0,r.jsx)(n.code,{children:"users_database.yml"})," (or whatever backend you're using) and restart Authelia."]}),"\n",(0,r.jsx)(n.h4,{id:"issuer-mismatch-behind-a-reverse-proxy",children:"Issuer mismatch behind a reverse proxy"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Server logs show ",(0,r.jsx)(n.code,{children:'unexpected "iss" claim value'}),". The browser reaches Authelia at ",(0,r.jsx)(n.code,{children:"https://auth.example.com"})," but Authelia advertises a different issuer.",(0,r.jsx)(n.br,{}),"\nSolution: Make sure your reverse proxy forwards ",(0,r.jsx)(n.code,{children:"X-Forwarded-Proto: https"})," and ",(0,r.jsx)(n.code,{children:"X-Forwarded-Host"}),", and that ",(0,r.jsx)(n.code,{children:"session.cookies[].authelia_url"})," matches the public URL exactly."]}),"\n",(0,r.jsx)(n.h4,{id:"locked-out-after-a-few-wrong-passwords",children:"Locked out after a few wrong passwords"}),"\n",(0,r.jsxs)(n.p,{children:['Problem: Login fails with "User has been banned" even with the correct password.',(0,r.jsx)(n.br,{}),"\nSolution: The ",(0,r.jsx)(n.code,{children:"regulation"})," block bans an account after ",(0,r.jsx)(n.code,{children:"max_retries"})," failed attempts within ",(0,r.jsx)(n.code,{children:"find_time"}),". Default ban is 5 minutes. Wait it out or ",(0,r.jsx)(n.code,{children:"docker compose restart authelia"})," to clear the in-memory ban."]}),"\n",(0,r.jsx)(n.h4,{id:"two-factor-prompt-appears-unexpectedly",children:"Two-factor prompt appears unexpectedly"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: After password entry Authelia asks for a TOTP code or other 2FA, but you wanted password-only for Dashy.",(0,r.jsx)(n.br,{}),"\nSolution: ",(0,r.jsx)(n.code,{children:"access_control.default_policy"})," is set to ",(0,r.jsx)(n.code,{children:"two_factor"}),". Change it to ",(0,r.jsx)(n.code,{children:"one_factor"}),", or add a specific rule with ",(0,r.jsx)(n.code,{children:"policy: one_factor"})," matching the Dashy redirect."]}),"\n",(0,r.jsx)(n.h4,{id:"username-claim-is-a-uuid-not-the-username",children:"Username claim is a UUID, not the username"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Dashy logs show users with random-looking subject IDs.",(0,r.jsx)(n.br,{}),"\nSolution: Authelia's ",(0,r.jsx)(n.code,{children:"sub"})," claim is an opaque UUID per user. Dashy's OIDC client falls through ",(0,r.jsx)(n.code,{children:"preferred_username"}),", ",(0,r.jsx)(n.code,{children:"email"}),", then ",(0,r.jsx)(n.code,{children:"sub"}),", so as long as the ",(0,r.jsx)(n.code,{children:"profile"})," and ",(0,r.jsx)(n.code,{children:"email"})," scopes are requested the username should be human-readable. Confirm both scopes are in the client's ",(0,r.jsx)(n.code,{children:"scopes"})," list."]}),"\n",(0,r.jsx)(n.h4,{id:"token-expired--clock-skew",children:"Token expired / clock skew"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: 401s with ",(0,r.jsx)(n.code,{children:'"exp" claim timestamp check failed'})," or ",(0,r.jsx)(n.code,{children:'"iat" claim timestamp check failed'}),", even just after login.",(0,r.jsx)(n.br,{}),"\nSolution: Dashy allows 30 seconds of drift. Sync clocks on both hosts with NTP. Container clocks follow their host, so it's almost always the host that's drifted."]}),"\n",(0,r.jsx)(n.h4,{id:"numeric-client-id-truncated",children:"Numeric Client ID truncated"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Audience mismatch when ",(0,r.jsx)(n.code,{children:"clientId"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"})," is a long numeric string.",(0,r.jsx)(n.br,{}),"\nSolution: Wrap numeric Client IDs in quotes (e.g. ",(0,r.jsx)(n.code,{children:'clientId: "12345678901234567"'}),"). Without quotes YAML parses the value as a JS number and loses precision past around 15 digits."]}),"\n",(0,r.jsx)(n.h4,{id:"dashy-server-cant-reach-authelia",children:"Dashy server can't reach Authelia"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Auth'd API calls return 401 and Dashy logs show fetch errors for ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"}),".",(0,r.jsx)(n.br,{}),"\nSolution: ",(0,r.jsx)(n.code,{children:"endpoint"})," must be reachable from inside the Dashy container, not just from the browser. If both run in Docker, put them on the same network. Test with ",(0,r.jsx)(n.code,{children:'docker exec wget -qO- "$ENDPOINT/.well-known/openid-configuration"'}),"."]}),"\n",(0,r.jsx)(n.h4,{id:"config-change-to-authoidc-not-picked-up",children:"Config change to auth.oidc not picked up"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Updated ",(0,r.jsx)(n.code,{children:"clientId"}),", ",(0,r.jsx)(n.code,{children:"endpoint"}),", ",(0,r.jsx)(n.code,{children:"adminGroup"})," or ",(0,r.jsx)(n.code,{children:"scope"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"}),", but Dashy still uses the old values.",(0,r.jsx)(n.br,{}),"\nSolution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under ",(0,r.jsx)(n.code,{children:"auth.oidc"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"how-it-works",children:"How it Works"}),"\n",(0,r.jsx)(n.p,{children:"If you're a developer or contributor looking to understand or make changes to Dashy's OIDC implementation, the following outlines how it's wired together."}),"\n",(0,r.jsx)(n.p,{children:"The same OIDC pipeline backs Authelia, Authentik, Keycloak, and any other generic OIDC provider. The only Authelia-specific code is your configuration; everything else is shared."}),"\n",(0,r.jsx)(n.h3,{id:"client-side",children:"Client side"}),"\n",(0,r.jsxs)(n.p,{children:["Boot starts in ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/main.js",children:(0,r.jsx)(n.code,{children:"src/main.js"})}),". After the initial ",(0,r.jsx)(n.code,{children:"/conf.yml"})," fetch parses the auth block, ",(0,r.jsx)(n.code,{children:"isOidcEnabled()"})," decides whether to lazily import ",(0,r.jsx)(n.a,{href:"https://github.com/authts/oidc-client-ts",children:(0,r.jsx)(n.code,{children:"oidc-client-ts"})})," and call ",(0,r.jsx)(n.code,{children:"initOidcAuth()"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/OidcAuth.js",children:(0,r.jsx)(n.code,{children:"src/utils/auth/OidcAuth.js"})})," wraps ",(0,r.jsx)(n.code,{children:"oidc-client-ts"}),". On load it inspects the URL: if it sees a ",(0,r.jsx)(n.code,{children:"?code="})," callback it runs ",(0,r.jsx)(n.code,{children:"userManager.signinCallback()"})," to exchange the code (and PKCE verifier) for tokens, persists the user info, and hard-redirects to ",(0,r.jsx)(n.code,{children:"/"}),". Otherwise it calls ",(0,r.jsx)(n.code,{children:"userManager.getUser()"}),"; if there's no usable session it falls through to ",(0,r.jsx)(n.code,{children:"userManager.signinRedirect()"})," to send the browser to Authelia. A short-lived ",(0,r.jsx)(n.code,{children:"sessionStorage"})," guard prevents the redirect loop that would otherwise occur if the IdP returns without a usable user."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"persistUserInfo()"})," writes the raw ",(0,r.jsx)(n.code,{children:"id_token"}),", the user's ",(0,r.jsx)(n.code,{children:"groups"})," and ",(0,r.jsx)(n.code,{children:"roles"}),", a derived ",(0,r.jsx)(n.code,{children:"isAdmin"})," flag, and a username (falling back through ",(0,r.jsx)(n.code,{children:"preferred_username"}),", ",(0,r.jsx)(n.code,{children:"email"}),", and ",(0,r.jsx)(n.code,{children:"sub"}),") to localStorage. The keys (",(0,r.jsx)(n.code,{children:"ID_TOKEN"}),", ",(0,r.jsx)(n.code,{children:"KEYCLOAK_INFO"}),", ",(0,r.jsx)(n.code,{children:"USERNAME"}),", ",(0,r.jsx)(n.code,{children:"ISADMIN"}),") live in ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/config/defaults.js",children:(0,r.jsx)(n.code,{children:"src/utils/config/defaults.js"})}),"; the ",(0,r.jsx)(n.code,{children:"KEYCLOAK_INFO"})," name is historical and reused for all OIDC providers, including Authelia."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/getApiAuthHeader.js",children:(0,r.jsx)(n.code,{children:"src/utils/auth/getApiAuthHeader.js"})})," builds the Authorization header for every internal API call. It does a client-side ",(0,r.jsx)(n.code,{children:"exp"})," check and returns ",(0,r.jsx)(n.code,{children:"null"})," for missing or expired tokens, so the next request triggers a fresh login rather than a 401."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/IsVisibleToUser.js",children:(0,r.jsx)(n.code,{children:"src/utils/IsVisibleToUser.js"})})," reads ",(0,r.jsx)(n.code,{children:"KEYCLOAK_INFO"})," when evaluating ",(0,r.jsx)(n.code,{children:"showForKeycloakUsers"})," and ",(0,r.jsx)(n.code,{children:"hideForKeycloakUsers"})," rules."]}),"\n",(0,r.jsx)(n.h3,{id:"server-side",children:"Server side"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/auth-oidc.js",children:(0,r.jsx)(n.code,{children:"services/auth-oidc.js"})})," contains the entire server-side auth surface, in five small pieces:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"loadOidcSettings()"})," reads ",(0,r.jsx)(n.code,{children:"auth.oidc"})," (or ",(0,r.jsx)(n.code,{children:"auth.keycloak"}),") at boot and returns a normalised ",(0,r.jsx)(n.code,{children:"{ issuer, clientId, adminGroup, adminRole }"}),". For generic OIDC providers the ",(0,r.jsx)(n.code,{children:"issuer"})," is whatever you set as ",(0,r.jsx)(n.code,{children:"endpoint"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"}),", verbatim"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"createOidcMiddleware()"})," returns a Connect middleware. Permissive on no-token requests so the SPA can bootstrap; otherwise it verifies the Bearer token against the issuer's JWKS using ",(0,r.jsx)(n.a,{href:"https://github.com/panva/jose",children:(0,r.jsx)(n.code,{children:"jose"})}),". Checks cover signature, issuer (against the canonical value from the discovery doc), audience (must equal ",(0,r.jsx)(n.code,{children:"clientId"}),"), and expiry, with a 30-second clock-skew tolerance. Sets ",(0,r.jsx)(n.code,{children:"req.auth = { user, isAdmin, claims }"})," on success, ",(0,r.jsx)(n.code,{children:"401"})," on failure"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"getIssuerContext()"})," lazily fetches ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"})," on first use and wraps ",(0,r.jsx)(n.code,{children:"jwks_uri"})," in ",(0,r.jsx)(n.code,{children:"createRemoteJWKSet"}),", which handles JWKS caching and on-demand key rotation. The result is memoised per-issuer for the life of the process"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"deriveIsAdmin()"})," checks the token's ",(0,r.jsx)(n.code,{children:"groups"})," claim against ",(0,r.jsx)(n.code,{children:"adminGroup"}),", and the top-level ",(0,r.jsx)(n.code,{children:"roles"})," claim against ",(0,r.jsx)(n.code,{children:"adminRole"})," (for Keycloak it also folds in the nested ",(0,r.jsx)(n.code,{children:"realm_access.roles"})," / ",(0,r.jsx)(n.code,{children:"resource_access..roles"})," arrays). Authelia only emits ",(0,r.jsx)(n.code,{children:"groups"}),", so the group path is what's used in practice"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"maybeBootstrapConfig()"})," is the stripped-response helper. When auth is configured, guest access is off, and an unauthenticated request hits the root ",(0,r.jsx)(n.code,{children:"/conf.yml"}),", it returns a minimal copy with only ",(0,r.jsx)(n.code,{children:"appConfig.auth"}),", ",(0,r.jsx)(n.code,{children:"appConfig.enableServiceWorker"}),", and a ",(0,r.jsx)(n.code,{children:"pageInfo.title"})," of ",(0,r.jsx)(n.code,{children:"Login | "}),". Sections, items, hostnames and any other secrets never leave the server"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/app.js",children:(0,r.jsx)(n.code,{children:"services/app.js"})})," wires it all together. The middleware mounts as ",(0,r.jsx)(n.code,{children:"protectConfig"})," in front of every YAML route and config-mutating route. The ",(0,r.jsx)(n.code,{children:"/*.yml"})," handler sets ",(0,r.jsx)(n.code,{children:"Cache-Control: private, no-store"})," and ",(0,r.jsx)(n.code,{children:"Vary: Authorization"})," whenever auth is configured (so intermediate caches can never mix auth states), then calls ",(0,r.jsx)(n.code,{children:"maybeBootstrapConfig"}),"; a stripped result is sent as-is, otherwise ",(0,r.jsx)(n.code,{children:"res.sendFile"})," serves the full file. ",(0,r.jsx)(n.code,{children:"POST /config-manager/save"})," is additionally guarded by ",(0,r.jsx)(n.code,{children:"requireAdmin"}),", which returns ",(0,r.jsx)(n.code,{children:"401"})," if ",(0,r.jsx)(n.code,{children:"req.auth"})," is unset and ",(0,r.jsx)(n.code,{children:"403"})," if ",(0,r.jsx)(n.code,{children:"req.auth.isAdmin"})," is false."]}),"\n",(0,r.jsx)(n.h3,{id:"visual-overview",children:"Visual Overview"}),"\n",(0,r.jsxs)(n.details,{children:["\n",(0,r.jsx)(n.summary,{children:"End-to-end authentication flow"}),"\n",(0,r.jsx)(n.mermaid,{value:"sequenceDiagram\n autonumber\n actor User\n participant Browser as Browser (Dashy SPA)\n participant Dashy as Dashy Server\n participant Auth as Authelia\n\n Note over Browser,Dashy: 1. Bootstrap (no token yet)\n User->>Browser: Open Dashy\n Browser->>Dashy: GET /conf.yml\n Dashy--\x3e>Browser: 200 stripped conf
(auth block + minimal pageInfo)\n Browser->>Browser: Parse auth.oidc\n\n Note over Browser,Auth: 2. OIDC login (Authorization Code + PKCE)\n Browser->>Auth: 302 /api/oidc/authorization
client_id, code_challenge, redirect_uri\n User->>Auth: Submit credentials\n Auth--\x3e>Browser: 302 back with ?code=AUTH_CODE\n Browser->>Auth: POST /api/oidc/token
(code + code_verifier)\n Auth--\x3e>Browser: id_token + access_token\n Browser->>Browser: Store tokens in localStorage
hard reload to /\n\n Note over Browser,Dashy: 3. Authenticated read\n Browser->>Dashy: GET /conf.yml
Authorization: Bearer id_token\n Dashy->>Auth: Fetch discovery + JWKS
(lazy on first call, then cached)\n Auth--\x3e>Dashy: openid-configuration + JWKS\n Dashy->>Dashy: Verify signature / issuer / audience / expiry\n Dashy--\x3e>Browser: 200 full conf.yml\n\n Note over Browser,Dashy: 4. Write request (admin only)\n Browser->>Dashy: POST /config-manager/save
Authorization: Bearer id_token\n Dashy->>Dashy: Verify token, derive isAdmin from
adminGroup claim\n alt isAdmin\n Dashy--\x3e>Browser: 200 saved\n else not admin\n Dashy--\x3e>Browser: 403 Forbidden\n end"}),"\n"]}),"\n",(0,r.jsxs)(n.details,{children:["\n",(0,r.jsx)(n.summary,{children:"Server-side request handling"}),"\n",(0,r.jsx)(n.mermaid,{value:'flowchart TD\n Req([Incoming request to Dashy server])\n Req --\x3e Bearer{Authorization:
Bearer present?}\n\n Bearer -- No --\x3e NoAuth[req.auth unset
pass through]:::neutral\n Bearer -- Yes --\x3e Verify[/Verify JWT against cached JWKS:
signature \xb7 issuer \xb7 audience \xb7 expiry/]\n Verify --\x3e Valid{Valid?}\n Valid -- No --\x3e R401Bad[/401 Unauthorized/]:::err\n Valid -- Yes --\x3e SetAuth[req.auth = user + isAdmin
derived from claims]:::ok\n\n NoAuth --\x3e Route{Endpoint}\n SetAuth --\x3e Route\n\n Route -- "GET /conf.yml" --\x3e ConfGate{req.auth?}\n ConfGate -- No --\x3e Strip[/200 stripped conf
auth + minimal pageInfo/]:::ok\n ConfGate -- Yes --\x3e Full[/200 full conf.yml/]:::ok\n\n Route -- "POST /config-manager/save" --\x3e SaveGate{req.auth
and isAdmin?}\n SaveGate -- No req.auth --\x3e R401Save[/401 Unauthorized/]:::err\n SaveGate -- Not admin --\x3e R403[/403 Forbidden/]:::err\n SaveGate -- Admin --\x3e Saved[/200 saved/]:::ok\n\n classDef ok fill:#bbf7d0,stroke:#16a34a,color:#14532d\n classDef err fill:#fecaca,stroke:#dc2626,color:#7f1d1d\n classDef neutral fill:#dbeafe,stroke:#2563eb,color:#1e3a8a'}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(c,{...e})}):c(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>a});var i=s(96540);const r={},o=i.createContext(r);function t(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/8051faeb.0845962f.js b/assets/js/8051faeb.5111aada.js similarity index 99% rename from assets/js/8051faeb.0845962f.js rename to assets/js/8051faeb.5111aada.js index 8e649494..baaf9530 100644 --- a/assets/js/8051faeb.0845962f.js +++ b/assets/js/8051faeb.5111aada.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[1751],{34610(e,t,s){s.r(t),s.d(t,{assets:()=>h,contentTitle:()=>a,default:()=>l,frontMatter:()=>i,metadata:()=>r,toc:()=>c});const r=JSON.parse('{"id":"authentication/other-auth-methods","title":"Other Auth Methods","description":"If OIDC or our built-in auth doesn\'t suit your needs, there\'s plenty of other ways of protecting your dashboard from unauthenticated access.","source":"@site/docs/authentication/other-auth-methods.md","sourceDirName":"authentication","slug":"/authentication/other-auth-methods","permalink":"/docs/authentication/other-auth-methods","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/other-auth-methods.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"OIDC","permalink":"/docs/authentication/oidc"},"next":{"title":"Pocket ID OIDC","permalink":"/docs/authentication/pocketid"}}');var n=s(74848),o=s(28453);const i={},a="Other Auth Methods",h={},c=[{value:"Reverse proxy auth",id:"reverse-proxy-auth",level:2},{value:"Zero-trust tunnels",id:"zero-trust-tunnels",level:2},{value:"Service worker & offline use",id:"service-worker--offline-use",level:3},{value:"VPN",id:"vpn",level:2},{value:"IP-based access",id:"ip-based-access",level:2},{value:"Web server authentication",id:"web-server-authentication",level:2},{value:"Client certificates (mTLS)",id:"client-certificates-mtls",level:2},{value:"SSO / OAuth providers",id:"sso--oauth-providers",level:2},{value:"Cloud hosting providers",id:"cloud-hosting-providers",level:2}];function d(e){const t={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.header,{children:(0,n.jsx)(t.h1,{id:"other-auth-methods",children:"Other Auth Methods"})}),"\n",(0,n.jsx)(t.p,{children:"If OIDC or our built-in auth doesn't suit your needs, there's plenty of other ways of protecting your dashboard from unauthenticated access."}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#reverse-proxy-auth",children:"Reverse Proxy Auth"})," - Authelia, Authentik, or similar sitting in front of Dashy"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#zero-trust-tunnels",children:"Zero-Trust Tunnels"})," - Cloudflare Tunnel, Tailscale Funnel"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#vpn",children:"VPN"})," - Keep Dashy off the internet entirely"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#ip-based-access",children:"IP-Based Access"})," - Restrict by source IP in your web server"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#web-server-authentication",children:"Web Server Authentication"})," - HTTP basic auth at the proxy level"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#client-certificates-mtls",children:"Client Certificates (mTLS)"})," - Require a client TLS certificate to connect"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#sso--oauth-providers",children:"SSO / OAuth Providers"})," - Cloud-hosted identity providers"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#cloud-hosting-providers",children:"Cloud Hosting Providers"})," - Built-in auth on hosting platforms"]}),"\n"]}),"\n",(0,n.jsx)(t.admonition,{type:"important",children:(0,n.jsxs)(t.p,{children:["Every method on this page except header auth authenticates at the network or proxy edge, not inside Dashy. Dashy's own server stays open to anything that can reach it directly, so don't expose its port publicly: bind it to localhost or an internal network and only let the proxy or tunnel through. If you want app-level auth with per-user roles, use ",(0,n.jsx)(t.a,{href:"/docs/oidc",children:"OIDC"}),", ",(0,n.jsx)(t.a,{href:"/docs/built-in",children:"built-in auth"}),", or ",(0,n.jsx)(t.a,{href:"/docs/header-auth",children:"header auth"})," instead."]})}),"\n",(0,n.jsx)(t.h2,{id:"reverse-proxy-auth",children:"Reverse proxy auth"}),"\n",(0,n.jsx)(t.p,{children:"The most common setup for self-hosters running multiple services. Your reverse proxy delegates login to an auth server (forward auth), which handles login, 2FA, and sessions for everything behind it. You configure it once, and all your apps get protected."}),"\n",(0,n.jsxs)(t.p,{children:["Dashy has ",(0,n.jsx)(t.a,{href:"/docs/header-auth",children:"Header Authentication"})," support, so when your proxy authenticates a user and forwards their identity via a header, Dashy picks up the username and maps it to a configured user automatically. No separate Dashy login needed."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Authelia"})," is lightweight and Docker-friendly. It supports 2FA, per-path access rules, and multiple user backends. To get started quickly:"]}),"\n",(0,n.jsxs)(t.ol,{children:["\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"git clone https://github.com/authelia/authelia.git"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"cd authelia/examples/compose/lite"})}),"\n",(0,n.jsxs)(t.li,{children:["Edit ",(0,n.jsx)(t.code,{children:"users_database.yml"}),", ",(0,n.jsx)(t.code,{children:"configuration.yml"}),", and ",(0,n.jsx)(t.code,{children:"docker-compose.yml"})," for your domain and users"]}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"docker compose up -d"})}),"\n"]}),"\n",(0,n.jsxs)(t.p,{children:["See the ",(0,n.jsx)(t.a,{href:"https://www.authelia.com/docs/",children:"Authelia docs"})," for the full setup guide."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Authentik"})," is heavier but gives you a proper admin UI, built-in OIDC/SAML support, and user self-service (password resets, enrollment flows, etc). Good if you want a single identity provider across many apps. See the ",(0,n.jsx)(t.a,{href:"https://docs.goauthentik.io/docs/installation/docker-compose",children:"authentik Docker Compose install"})," to get started, and the ",(0,n.jsx)(t.a,{href:"/docs/authentik",children:"authentik guide"})," for Dashy-specific OIDC config."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"OAuth2 Proxy"})," (",(0,n.jsx)(t.a,{href:"https://oauth2-proxy.github.io/oauth2-proxy/",children:"docs"}),") is a thin forward-auth layer that puts any OIDC or OAuth2 provider (Google, GitHub, your own IdP) in front of apps that can't do it themselves. Point it at Dashy's ",(0,n.jsx)(t.a,{href:"/docs/header-auth",children:"header auth"})," and it forwards the authenticated username, so you get provider login without Dashy needing to reach the provider directly."]}),"\n",(0,n.jsx)(t.h2,{id:"zero-trust-tunnels",children:"Zero-trust tunnels"}),"\n",(0,n.jsx)(t.p,{children:"These let you expose Dashy to the internet without opening inbound ports or configuring port forwarding. Auth is handled by the tunnel provider before traffic ever reaches your server."}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Cloudflare Tunnel"})," connects Dashy to Cloudflare's edge network via an outbound-only ",(0,n.jsx)(t.code,{children:"cloudflared"})," daemon (runs nicely as a Docker sidecar). Cloudflare handles DNS, TLS, and DDoS protection. Pair it with Cloudflare Access to require identity provider login before anyone reaches Dashy. The free tier covers most home setups. Dashy has a full ",(0,n.jsx)(t.a,{href:"/docs/cloudflare-tunnel",children:"Cloudflare Tunnel guide"}),", or see the ",(0,n.jsx)(t.a,{href:"https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/",children:"upstream docs"}),"."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Tailscale Funnel"})," exposes Dashy through your Tailscale mesh to the public internet, with automatic TLS. Simpler to set up than Cloudflare but you get less control over access policies. See Dashy's ",(0,n.jsx)(t.a,{href:"/docs/tailscale",children:"Tailscale guide"}),", or the ",(0,n.jsx)(t.a,{href:"https://tailscale.com/kb/1223/funnel",children:"upstream Funnel docs"}),"."]}),"\n",(0,n.jsx)(t.h3,{id:"service-worker--offline-use",children:"Service worker & offline use"}),"\n",(0,n.jsxs)(t.p,{children:["If you run Dashy behind any of the redirect-based proxies or tunnels above and also enable the service worker for offline use (",(0,n.jsx)(t.code,{children:"appConfig.enableServiceWorker: true"}),"), set ",(0,n.jsx)(t.code,{children:"appConfig.enableAuthProxyCompat: true"})," as well. Without it, when your proxy session expires the cached app can get stuck, with the service worker serving the old page instead of letting the proxy redirect you to its login screen. With it enabled, Dashy detects the expiry on load and reloads so you can sign in again."]}),"\n",(0,n.jsx)(t.h2,{id:"vpn",children:"VPN"}),"\n",(0,n.jsx)(t.p,{children:"A VPN keeps Dashy off the public internet entirely. You connect to your home network remotely and access Dashy like you're on the LAN. No auth to configure, no attack surface to worry about. The downside: you need the VPN running to see anything, and some networks (corporate WiFi, hotels) block VPN traffic."}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.a,{href:"https://www.wireguard.com/",children:"WireGuard"})," is fast and minimal. Most self-hosters run it through a UI like ",(0,n.jsx)(t.a,{href:"https://github.com/wg-easy/wg-easy",children:"wg-easy"}),", which gives you a web interface for managing peers and generating QR codes for mobile."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.a,{href:"https://tailscale.com/",children:"Tailscale"})," wraps WireGuard and takes care of NAT traversal, key exchange, and device management. No port forwarding needed, works across networks with zero config. There's a generous free tier, and Dashy has a dedicated ",(0,n.jsx)(t.a,{href:"/docs/tailscale",children:"Tailscale guide"}),". ",(0,n.jsx)(t.a,{href:"https://github.com/juanfont/headscale",children:"Headscale"})," is a self-hosted coordination server if you want to keep everything on your own infrastructure."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.a,{href:"https://openvpn.net/",children:"OpenVPN"})," still works fine if you already have it running, but for a new setup WireGuard or Tailscale are easier to get going."]}),"\n",(0,n.jsx)(t.h2,{id:"ip-based-access",children:"IP-based access"}),"\n",(0,n.jsx)(t.p,{children:"If you have a static IP or are already on a VPN, you can restrict access to Dashy by source IP at the web server level. This works well as an extra layer on top of other auth methods."}),"\n",(0,n.jsx)(t.p,{children:"NGINX:"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:"location / {\n proxy_pass http://dashy:8080;\n allow 192.168.1.0/24;\n allow 203.0.113.50;\n deny all;\n}\n"})}),"\n",(0,n.jsxs)(t.p,{children:["Caddy (",(0,n.jsx)(t.a,{href:"https://caddyserver.com/docs/caddyfile/matchers",children:"request matchers docs"}),"):"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:'dashy.example.com {\n @blocked not remote_ip 192.168.1.0/24 203.0.113.50\n respond @blocked "Access denied" 403\n reverse_proxy dashy:8080\n}\n'})}),"\n",(0,n.jsx)(t.p,{children:"Apache (2.4+):"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:"\n Require ip 192.168.1.0/24\n Require ip 203.0.113.50\n\n"})}),"\n",(0,n.jsx)(t.h2,{id:"web-server-authentication",children:"Web server authentication"}),"\n",(0,n.jsx)(t.p,{children:"Your reverse proxy can handle HTTP basic auth directly, no extra services needed. This gives you a browser login prompt in front of Dashy. Make sure you're using HTTPS, as basic auth sends credentials base64-encoded (not encrypted) with every request."}),"\n",(0,n.jsxs)(t.p,{children:["NGINX (",(0,n.jsx)(t.a,{href:"https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html",children:"auth module docs"}),"):"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:'location / {\n auth_basic "Dashy";\n auth_basic_user_file /etc/nginx/conf.d/.htpasswd;\n proxy_pass http://dashy:8080;\n}\n'})}),"\n",(0,n.jsxs)(t.p,{children:["Generate the password file with ",(0,n.jsx)(t.code,{children:"htpasswd -c /etc/nginx/conf.d/.htpasswd alicia"}),"."]}),"\n",(0,n.jsxs)(t.p,{children:["Caddy (",(0,n.jsx)(t.a,{href:"https://caddyserver.com/docs/caddyfile/directives/basicauth",children:"basicauth directive"}),"):"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:"dashy.example.com {\n basicauth {\n alicia $2a$14$... # generate with: caddy hash-password\n }\n reverse_proxy dashy:8080\n}\n"})}),"\n",(0,n.jsx)(t.p,{children:"Apache:"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:'AuthType Basic\nAuthName "Dashy"\nAuthUserFile /path/to/.htpasswd\nRequire valid-user\n'})}),"\n",(0,n.jsxs)(t.p,{children:["Generate the password file with ",(0,n.jsx)(t.code,{children:"htpasswd -c /path/to/.htpasswd alicia"}),"."]}),"\n",(0,n.jsx)(t.h2,{id:"client-certificates-mtls",children:"Client certificates (mTLS)"}),"\n",(0,n.jsx)(t.p,{children:"Instead of a password, you can require a client TLS certificate to reach Dashy. The browser presents a cert you've issued, the proxy rejects anyone without a valid one. It's strong and can't be phished, but you have to generate, hand out, and occasionally revoke the certs, so it fits a small fixed set of devices better than a large user base."}),"\n",(0,n.jsxs)(t.p,{children:["You'll need your own CA to sign the client certs. ",(0,n.jsx)(t.a,{href:"https://github.com/FiloSottile/mkcert",children:"mkcert"})," is fine for a handful, ",(0,n.jsx)(t.a,{href:"https://github.com/smallstep/certificates",children:"step-ca"})," if you want to manage them properly."]}),"\n",(0,n.jsx)(t.p,{children:"NGINX:"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:"server {\n ssl_client_certificate /etc/nginx/certs/ca.crt;\n ssl_verify_client on;\n location / {\n proxy_pass http://dashy:8080;\n }\n}\n"})}),"\n",(0,n.jsxs)(t.p,{children:["Caddy and Apache do mTLS too, via ",(0,n.jsx)(t.code,{children:"client_auth"})," and ",(0,n.jsx)(t.code,{children:"SSLVerifyClient require"})," respectively."]}),"\n",(0,n.jsx)(t.h2,{id:"sso--oauth-providers",children:"SSO / OAuth providers"}),"\n",(0,n.jsxs)(t.p,{children:["Cloud identity providers like ",(0,n.jsx)(t.a,{href:"https://auth0.com/",children:"Auth0"}),", ",(0,n.jsx)(t.a,{href:"https://developer.okta.com/",children:"Okta"}),", ",(0,n.jsx)(t.a,{href:"https://www.ory.sh/",children:"Ory"}),", and ",(0,n.jsx)(t.a,{href:"https://cloud.google.com/identity",children:"Google Cloud Identity"})," can work with Dashy through its ",(0,n.jsx)(t.a,{href:"/docs/oidc",children:"OIDC support"}),". If your provider speaks OIDC (most do), just configure it as described in the OIDC section and you're set."]}),"\n",(0,n.jsx)(t.p,{children:"For providers that only support OAuth2 or SAML without an OIDC layer, you'll need something in between to translate. Authentik, Keycloak, and Authelia can all bridge from SAML/OAuth2 to OIDC."}),"\n",(0,n.jsx)(t.h2,{id:"cloud-hosting-providers",children:"Cloud hosting providers"}),"\n",(0,n.jsxs)(t.p,{children:["If you're running Dashy on a cloud platform, most have their own auth options you can enable without touching Dashy's config. See your provider's docs: ",(0,n.jsx)(t.a,{href:"https://www.cloudflare.com/teams/access/",children:"Cloudflare Access"}),", ",(0,n.jsx)(t.a,{href:"https://docs.netlify.com/visitor-access/password-protection/",children:"Netlify Password Protection"}),", ",(0,n.jsx)(t.a,{href:"https://aws.amazon.com/cognito/",children:"AWS Cognito"}),", ",(0,n.jsx)(t.a,{href:"https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization",children:"Azure App Service Authentication"}),", and ",(0,n.jsx)(t.a,{href:"https://vercel.com/docs/security/password-protection",children:"Vercel Password Protection"}),"."]})]})}function l(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},28453(e,t,s){s.d(t,{R:()=>i,x:()=>a});var r=s(96540);const n={},o=r.createContext(n);function i(e){const t=r.useContext(o);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:i(e.components),r.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[1751],{34610(e,t,s){s.r(t),s.d(t,{assets:()=>h,contentTitle:()=>a,default:()=>l,frontMatter:()=>i,metadata:()=>r,toc:()=>c});const r=JSON.parse('{"id":"authentication/other-auth-methods","title":"Other Auth Methods","description":"If OIDC or our built-in auth doesn\'t suit your needs, there\'s plenty of other ways of protecting your dashboard from unauthenticated access.","source":"@site/docs/authentication/other-auth-methods.md","sourceDirName":"authentication","slug":"/authentication/other-auth-methods","permalink":"/docs/authentication/other-auth-methods","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/other-auth-methods.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"OIDC","permalink":"/docs/authentication/oidc"},"next":{"title":"Pocket ID OIDC","permalink":"/docs/authentication/pocketid"}}');var n=s(74848),o=s(28453);const i={},a="Other Auth Methods",h={},c=[{value:"Reverse proxy auth",id:"reverse-proxy-auth",level:2},{value:"Zero-trust tunnels",id:"zero-trust-tunnels",level:2},{value:"Service worker & offline use",id:"service-worker--offline-use",level:3},{value:"VPN",id:"vpn",level:2},{value:"IP-based access",id:"ip-based-access",level:2},{value:"Web server authentication",id:"web-server-authentication",level:2},{value:"Client certificates (mTLS)",id:"client-certificates-mtls",level:2},{value:"SSO / OAuth providers",id:"sso--oauth-providers",level:2},{value:"Cloud hosting providers",id:"cloud-hosting-providers",level:2}];function d(e){const t={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.header,{children:(0,n.jsx)(t.h1,{id:"other-auth-methods",children:"Other Auth Methods"})}),"\n",(0,n.jsx)(t.p,{children:"If OIDC or our built-in auth doesn't suit your needs, there's plenty of other ways of protecting your dashboard from unauthenticated access."}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#reverse-proxy-auth",children:"Reverse Proxy Auth"})," - Authelia, Authentik, or similar sitting in front of Dashy"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#zero-trust-tunnels",children:"Zero-Trust Tunnels"})," - Cloudflare Tunnel, Tailscale Funnel"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#vpn",children:"VPN"})," - Keep Dashy off the internet entirely"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#ip-based-access",children:"IP-Based Access"})," - Restrict by source IP in your web server"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#web-server-authentication",children:"Web Server Authentication"})," - HTTP basic auth at the proxy level"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#client-certificates-mtls",children:"Client Certificates (mTLS)"})," - Require a client TLS certificate to connect"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#sso--oauth-providers",children:"SSO / OAuth Providers"})," - Cloud-hosted identity providers"]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"#cloud-hosting-providers",children:"Cloud Hosting Providers"})," - Built-in auth on hosting platforms"]}),"\n"]}),"\n",(0,n.jsx)(t.admonition,{type:"important",children:(0,n.jsxs)(t.p,{children:["Every method on this page except header auth authenticates at the network or proxy edge, not inside Dashy. Dashy's own server stays open to anything that can reach it directly, so don't expose its port publicly: bind it to localhost or an internal network and only let the proxy or tunnel through. If you want app-level auth with per-user roles, use ",(0,n.jsx)(t.a,{href:"/docs/oidc",children:"OIDC"}),", ",(0,n.jsx)(t.a,{href:"/docs/built-in",children:"built-in auth"}),", or ",(0,n.jsx)(t.a,{href:"/docs/header-auth",children:"header auth"})," instead."]})}),"\n",(0,n.jsx)(t.h2,{id:"reverse-proxy-auth",children:"Reverse proxy auth"}),"\n",(0,n.jsx)(t.p,{children:"The most common setup for self-hosters running multiple services. Your reverse proxy delegates login to an auth server (forward auth), which handles login, 2FA, and sessions for everything behind it. You configure it once, and all your apps get protected."}),"\n",(0,n.jsxs)(t.p,{children:["Dashy has ",(0,n.jsx)(t.a,{href:"/docs/header-auth",children:"Header Authentication"})," support, so when your proxy authenticates a user and forwards their identity via a header, Dashy picks up the username and maps it to a configured user automatically. No separate Dashy login needed."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Authelia"})," is lightweight and Docker-friendly. It supports 2FA, per-path access rules, and multiple user backends. To get started quickly:"]}),"\n",(0,n.jsxs)(t.ol,{children:["\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"git clone https://github.com/authelia/authelia.git"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"cd authelia/examples/compose/lite"})}),"\n",(0,n.jsxs)(t.li,{children:["Edit ",(0,n.jsx)(t.code,{children:"users_database.yml"}),", ",(0,n.jsx)(t.code,{children:"configuration.yml"}),", and ",(0,n.jsx)(t.code,{children:"docker-compose.yml"})," for your domain and users"]}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"docker compose up -d"})}),"\n"]}),"\n",(0,n.jsxs)(t.p,{children:["See the ",(0,n.jsx)(t.a,{href:"https://www.authelia.com/docs/",children:"Authelia docs"})," for the full setup guide."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Authentik"})," is heavier but gives you a proper admin UI, built-in OIDC/SAML support, and user self-service (password resets, enrollment flows, etc). Good if you want a single identity provider across many apps. See the ",(0,n.jsx)(t.a,{href:"https://docs.goauthentik.io/docs/installation/docker-compose",children:"authentik Docker Compose install"})," to get started, and the ",(0,n.jsx)(t.a,{href:"/docs/authentik",children:"authentik guide"})," for Dashy-specific OIDC config."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"OAuth2 Proxy"})," (",(0,n.jsx)(t.a,{href:"https://oauth2-proxy.github.io/oauth2-proxy/",children:"docs"}),") is a thin forward-auth layer that puts any OIDC or OAuth2 provider (Google, GitHub, your own IdP) in front of apps that can't do it themselves. Point it at Dashy's ",(0,n.jsx)(t.a,{href:"/docs/header-auth",children:"header auth"})," and it forwards the authenticated username, so you get provider login without Dashy needing to reach the provider directly."]}),"\n",(0,n.jsx)(t.h2,{id:"zero-trust-tunnels",children:"Zero-trust tunnels"}),"\n",(0,n.jsx)(t.p,{children:"These let you expose Dashy to the internet without opening inbound ports or configuring port forwarding. Auth is handled by the tunnel provider before traffic ever reaches your server."}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Cloudflare Tunnel"})," connects Dashy to Cloudflare's edge network via an outbound-only ",(0,n.jsx)(t.code,{children:"cloudflared"})," daemon (runs nicely as a Docker sidecar). Cloudflare handles DNS, TLS, and DDoS protection. Pair it with Cloudflare Access to require identity provider login before anyone reaches Dashy. The free tier covers most home setups. Dashy has a full ",(0,n.jsx)(t.a,{href:"/docs/cloudflare-tunnel",children:"Cloudflare Tunnel guide"}),", or see the ",(0,n.jsx)(t.a,{href:"https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/",children:"upstream docs"}),"."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Tailscale Funnel"})," exposes Dashy through your Tailscale mesh to the public internet, with automatic TLS. Simpler to set up than Cloudflare but you get less control over access policies. See Dashy's ",(0,n.jsx)(t.a,{href:"/docs/tailscale",children:"Tailscale guide"}),", or the ",(0,n.jsx)(t.a,{href:"https://tailscale.com/kb/1223/funnel",children:"upstream Funnel docs"}),"."]}),"\n",(0,n.jsx)(t.h3,{id:"service-worker--offline-use",children:"Service worker & offline use"}),"\n",(0,n.jsxs)(t.p,{children:["If you run Dashy behind any of the redirect-based proxies or tunnels above and also enable the service worker for offline use (",(0,n.jsx)(t.code,{children:"appConfig.enableServiceWorker: true"}),"), set ",(0,n.jsx)(t.code,{children:"appConfig.enableAuthProxyCompat: true"})," as well. Without it, when your proxy session expires the cached app can get stuck, with the service worker serving the old page instead of letting the proxy redirect you to its login screen. With it enabled, Dashy detects the expiry on load and reloads so you can sign in again."]}),"\n",(0,n.jsx)(t.h2,{id:"vpn",children:"VPN"}),"\n",(0,n.jsx)(t.p,{children:"A VPN keeps Dashy off the public internet entirely. You connect to your home network remotely and access Dashy like you're on the LAN. No auth to configure, no attack surface to worry about. The downside: you need the VPN running to see anything, and some networks (corporate WiFi, hotels) block VPN traffic."}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.a,{href:"https://www.wireguard.com/",children:"WireGuard"})," is fast and minimal. Most self-hosters run it through a UI like ",(0,n.jsx)(t.a,{href:"https://github.com/wg-easy/wg-easy",children:"wg-easy"}),", which gives you a web interface for managing peers and generating QR codes for mobile."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.a,{href:"https://tailscale.com/",children:"Tailscale"})," wraps WireGuard and takes care of NAT traversal, key exchange, and device management. No port forwarding needed, works across networks with zero config. There's a generous free tier, and Dashy has a dedicated ",(0,n.jsx)(t.a,{href:"/docs/tailscale",children:"Tailscale guide"}),". ",(0,n.jsx)(t.a,{href:"https://github.com/juanfont/headscale",children:"Headscale"})," is a self-hosted coordination server if you want to keep everything on your own infrastructure."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.a,{href:"https://openvpn.net/",children:"OpenVPN"})," still works fine if you already have it running, but for a new setup WireGuard or Tailscale are easier to get going."]}),"\n",(0,n.jsx)(t.h2,{id:"ip-based-access",children:"IP-based access"}),"\n",(0,n.jsx)(t.p,{children:"If you have a static IP or are already on a VPN, you can restrict access to Dashy by source IP at the web server level. This works well as an extra layer on top of other auth methods."}),"\n",(0,n.jsx)(t.p,{children:"NGINX:"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:"location / {\n proxy_pass http://dashy:8080;\n allow 192.168.1.0/24;\n allow 203.0.113.50;\n deny all;\n}\n"})}),"\n",(0,n.jsxs)(t.p,{children:["Caddy (",(0,n.jsx)(t.a,{href:"https://caddyserver.com/docs/caddyfile/matchers",children:"request matchers docs"}),"):"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:'dashy.example.com {\n @blocked not remote_ip 192.168.1.0/24 203.0.113.50\n respond @blocked "Access denied" 403\n reverse_proxy dashy:8080\n}\n'})}),"\n",(0,n.jsx)(t.p,{children:"Apache (2.4+):"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:"\n Require ip 192.168.1.0/24\n Require ip 203.0.113.50\n\n"})}),"\n",(0,n.jsx)(t.h2,{id:"web-server-authentication",children:"Web server authentication"}),"\n",(0,n.jsx)(t.p,{children:"Your reverse proxy can handle HTTP basic auth directly, no extra services needed. This gives you a browser login prompt in front of Dashy. Make sure you're using HTTPS, as basic auth sends credentials base64-encoded (not encrypted) with every request."}),"\n",(0,n.jsxs)(t.p,{children:["NGINX (",(0,n.jsx)(t.a,{href:"https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html",children:"auth module docs"}),"):"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:'location / {\n auth_basic "Dashy";\n auth_basic_user_file /etc/nginx/conf.d/.htpasswd;\n proxy_pass http://dashy:8080;\n}\n'})}),"\n",(0,n.jsxs)(t.p,{children:["Generate the password file with ",(0,n.jsx)(t.code,{children:"htpasswd -c /etc/nginx/conf.d/.htpasswd alicia"}),"."]}),"\n",(0,n.jsxs)(t.p,{children:["Caddy (",(0,n.jsx)(t.a,{href:"https://caddyserver.com/docs/caddyfile/directives/basicauth",children:"basicauth directive"}),"):"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:"dashy.example.com {\n basicauth {\n alicia $2a$14$... # generate with: caddy hash-password\n }\n reverse_proxy dashy:8080\n}\n"})}),"\n",(0,n.jsx)(t.p,{children:"Apache:"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:'AuthType Basic\nAuthName "Dashy"\nAuthUserFile /path/to/.htpasswd\nRequire valid-user\n'})}),"\n",(0,n.jsxs)(t.p,{children:["Generate the password file with ",(0,n.jsx)(t.code,{children:"htpasswd -c /path/to/.htpasswd alicia"}),"."]}),"\n",(0,n.jsx)(t.h2,{id:"client-certificates-mtls",children:"Client certificates (mTLS)"}),"\n",(0,n.jsx)(t.p,{children:"Instead of a password, you can require a client TLS certificate to reach Dashy. The browser presents a cert you've issued, the proxy rejects anyone without a valid one. It's strong and can't be phished, but you have to generate, hand out, and occasionally revoke the certs, so it fits a small fixed set of devices better than a large user base."}),"\n",(0,n.jsxs)(t.p,{children:["You'll need your own CA to sign the client certs. ",(0,n.jsx)(t.a,{href:"https://github.com/FiloSottile/mkcert",children:"mkcert"})," is fine for a handful, ",(0,n.jsx)(t.a,{href:"https://github.com/smallstep/certificates",children:"step-ca"})," if you want to manage them properly."]}),"\n",(0,n.jsx)(t.p,{children:"NGINX:"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-text",children:"server {\n ssl_client_certificate /etc/nginx/certs/ca.crt;\n ssl_verify_client on;\n location / {\n proxy_pass http://dashy:8080;\n }\n}\n"})}),"\n",(0,n.jsxs)(t.p,{children:["Caddy and Apache do mTLS too, via ",(0,n.jsx)(t.code,{children:"client_auth"})," and ",(0,n.jsx)(t.code,{children:"SSLVerifyClient require"})," respectively."]}),"\n",(0,n.jsx)(t.h2,{id:"sso--oauth-providers",children:"SSO / OAuth providers"}),"\n",(0,n.jsxs)(t.p,{children:["Cloud identity providers like ",(0,n.jsx)(t.a,{href:"https://auth0.com/",children:"Auth0"}),", ",(0,n.jsx)(t.a,{href:"https://developer.okta.com/",children:"Okta"}),", ",(0,n.jsx)(t.a,{href:"https://www.ory.sh/",children:"Ory"}),", and ",(0,n.jsx)(t.a,{href:"https://cloud.google.com/identity",children:"Google Cloud Identity"})," can work with Dashy through its ",(0,n.jsx)(t.a,{href:"/docs/oidc",children:"OIDC support"}),". If your provider speaks OIDC (most do), just configure it as described in the OIDC section and you're set."]}),"\n",(0,n.jsx)(t.p,{children:"For providers that only support OAuth2 or SAML without an OIDC layer, you'll need something in between to translate. Authentik, Keycloak, and Authelia can all bridge from SAML/OAuth2 to OIDC."}),"\n",(0,n.jsx)(t.h2,{id:"cloud-hosting-providers",children:"Cloud hosting providers"}),"\n",(0,n.jsxs)(t.p,{children:["If you're running Dashy on a cloud platform, most have their own auth options you can enable without touching Dashy's config. See your provider's docs: ",(0,n.jsx)(t.a,{href:"https://www.cloudflare.com/teams/access/",children:"Cloudflare Access"}),", ",(0,n.jsx)(t.a,{href:"https://docs.netlify.com/visitor-access/password-protection/",children:"Netlify Password Protection"}),", ",(0,n.jsx)(t.a,{href:"https://aws.amazon.com/cognito/",children:"AWS Cognito"}),", ",(0,n.jsx)(t.a,{href:"https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization",children:"Azure App Service Authentication"}),", and ",(0,n.jsx)(t.a,{href:"https://vercel.com/docs/security/password-protection",children:"Vercel Password Protection"}),"."]})]})}function l(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},28453(e,t,s){s.d(t,{R:()=>i,x:()=>a});var r=s(96540);const n={},o=r.createContext(n);function i(e){const t=r.useContext(o);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:i(e.components),r.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/83d908d2.ac570d16.js b/assets/js/83d908d2.ef084f2e.js similarity index 99% rename from assets/js/83d908d2.ac570d16.js rename to assets/js/83d908d2.ef084f2e.js index f83cac74..40211c93 100644 --- a/assets/js/83d908d2.ac570d16.js +++ b/assets/js/83d908d2.ef084f2e.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[8559],{29535(e,n,s){s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>t,default:()=>h,frontMatter:()=>l,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"authentication/keycloak","title":"Keycloak","description":"Dashy supports using a Keycloak (V17+) authentication server.","source":"@site/docs/authentication/keycloak.md","sourceDirName":"authentication","slug":"/authentication/keycloak","permalink":"/docs/authentication/keycloak","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/keycloak.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Header Authentication","permalink":"/docs/authentication/header-auth"},"next":{"title":"OIDC","permalink":"/docs/authentication/oidc"}}');var r=s(74848),o=s(28453);const l={},t="Keycloak",a={},c=[{value:"Contents",id:"contents",level:3},{value:"1. Deploy Keycloak",id:"1-deploy-keycloak",level:2},{value:"2. Configure Keycloak",id:"2-configure-keycloak",level:2},{value:"Create the Keycloak realm",id:"create-the-keycloak-realm",level:3},{value:"Allow Dashy's local origin",id:"allow-dashys-local-origin",level:3},{value:"Create the Dashy client",id:"create-the-dashy-client",level:3},{value:"Add the realm-role mapper",id:"add-the-realm-role-mapper",level:3},{value:"Create the Dashy admin role",id:"create-the-dashy-admin-role",level:3},{value:"(Alternative) Use a group for admin",id:"alternative-use-a-group-for-admin",level:3},{value:"Create test users",id:"create-test-users",level:3},{value:"Summary",id:"summary",level:3},{value:"3. Enabling Keycloak in Dashy",id:"3-enabling-keycloak-in-dashy",level:2},{value:"4. Groups and Roles",id:"4-groups-and-roles",level:2},{value:"Troubleshooting common Keycloak Issues",id:"troubleshooting-common-keycloak-issues",level:2},{value:"Client Authentication Issue",id:"client-authentication-issue",level:4},{value:"Double URL",id:"double-url",level:4},{value:"Problems with multiple Dashy Pages",id:"problems-with-multiple-dashy-pages",level:4},{value:"403 on login-status-iframe.html/init",id:"403-on-login-status-iframehtmlinit",level:4},{value:"CSP error for /3p-cookies/step1.html or "Authentication failed (Keycloak)"",id:"csp-error-for-3p-cookiesstep1html-or-authentication-failed-keycloak",level:4},{value:"Dashy server can't reach Keycloak",id:"dashy-server-cant-reach-keycloak",level:4},{value:"Logged in but config saves return 403",id:"logged-in-but-config-saves-return-403",level:4},{value:"Issuer mismatch behind a reverse proxy",id:"issuer-mismatch-behind-a-reverse-proxy",level:4},{value:"Audience mismatch on token verification",id:"audience-mismatch-on-token-verification",level:4},{value:"Self-signed Keycloak certificate rejected",id:"self-signed-keycloak-certificate-rejected",level:4},{value:"Token expired / clock skew",id:"token-expired--clock-skew",level:4},{value:"Mixed content blocked by the browser",id:"mixed-content-blocked-by-the-browser",level:4},{value:"Numeric Client ID truncated",id:"numeric-client-id-truncated",level:4},{value:"Logout lands on a broken page",id:"logout-lands-on-a-broken-page",level:4},{value:"check-sso hangs in strict browsers",id:"check-sso-hangs-in-strict-browsers",level:4},{value:"Config change to auth.keycloak not picked up",id:"config-change-to-authkeycloak-not-picked-up",level:4},{value:"How it Works",id:"how-it-works",level:2},{value:"Client side",id:"client-side",level:3},{value:"Server side",id:"server-side",level:3},{value:"Why the mapper matters",id:"why-the-mapper-matters",level:3},{value:"Visual Overview",id:"visual-overview",level:3}];function d(e){const n={a:"a",blockquote:"blockquote",br:"br",code:"code",details:"details",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",mermaid:"mermaid",ol:"ol",p:"p",pre:"pre",strong:"strong",summary:"summary",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"keycloak",children:"Keycloak"})}),"\n",(0,r.jsxs)(n.p,{children:["Dashy supports using a ",(0,r.jsx)(n.a,{href:"https://www.keycloak.org/",children:"Keycloak"})," (V17+) authentication server."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://www.keycloak.org/about.html",children:"Keycloak"})," is a Java-based ",(0,r.jsx)(n.a,{href:"https://github.com/keycloak/keycloak",children:"open source"}),", high-performance, secure authentication system, supported by ",(0,r.jsx)(n.a,{href:"https://www.redhat.com/en",children:"RedHat"}),". It can be deployed with Docker, and enables you to secure multiple self-hosted applications with single-sign-on using standard protocols (OpenID Connect, OAuth 2.0, SAML 2.0 and social login)."]}),"\n",(0,r.jsx)(n.h3,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#1-deploy-keycloak",children:"1. Deploy Keycloak"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#2-configure-keycloak",children:"2. Configure Keycloak"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#3-enabling-keycloak-in-dashy",children:"3. Enabling Keycloak in Dashy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#4-groups-and-roles",children:"4. Groups and Roles"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#troubleshooting-common-keycloak-issues",children:"Troubleshooting"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#how-it-works",children:"How it Works"})}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"1-deploy-keycloak",children:"1. Deploy Keycloak"}),"\n",(0,r.jsxs)(n.p,{children:["If you've not already done so, spin up a Keycloak instance.\nYou can do this by following the ",(0,r.jsx)(n.a,{href:"https://www.keycloak.org/guides.html#getting-started",children:"Keycloak Docs"}),", or use the following Docker examples:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d \\\n -p 9100:8080 \\\n --name keycloak \\\n -e KEYCLOAK_ADMIN=kc-admin \\\n -e KEYCLOAK_ADMIN_PASSWORD=KeycloakAdmin2026! \\\n quay.io/keycloak/keycloak:25.0 start-dev\n"})}),"\n",(0,r.jsxs)(n.details,{children:["\n ",(0,r.jsxs)(n.summary,{children:["Example ",(0,r.jsx)(n.code,{children:"docker-compose.yml"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-env",children:"KEYCLOAK_ADMIN=kc-admin\nKEYCLOAK_ADMIN_PASSWORD=KeycloakAdmin2026!\n"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'name: dashy-keycloak\nservices:\n keycloak:\n image: quay.io/keycloak/keycloak:25.0\n command:\n - start-dev\n - --http-port=9100\n - --hostname-strict=false\n - --health-enabled=true\n restart: unless-stopped\n ports:\n - "9100:9100"\n - "4000:8080"\n volumes:\n - keycloak-data:/opt/keycloak/data\n environment:\n KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}\n KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}\n KC_HTTP_ENABLED: "true"\n KC_HOSTNAME_STRICT: "false"\n KC_HEALTH_ENABLED: "true"\n healthcheck:\n test: ["CMD-SHELL", "timeout 2 bash -c \'/dev/null 2>&1"]\n start_period: 30s\n interval: 10s\n timeout: 5s\n retries: 15\n\nvolumes:\n keycloak-data:\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["You should now be able to access the Keycloak web interface at ",(0,r.jsx)(n.code,{children:"http://127.0.0.1:9100"}),", log in with your admin credentials above, and create a new password when prompted."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"2-configure-keycloak",children:"2. Configure Keycloak"}),"\n",(0,r.jsx)(n.h3,{id:"create-the-keycloak-realm",children:"Create the Keycloak realm"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.code,{children:"http://localhost:9100"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Administration Console"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Log in as ",(0,r.jsx)(n.code,{children:"kc-admin"}),"."]}),"\n",(0,r.jsx)(n.li,{children:"Open the realm selector in the top-left."}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create realm"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Realm name"})," to ",(0,r.jsx)(n.code,{children:"dashy"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"allow-dashys-local-origin",children:"Allow Dashy's local origin"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["In the ",(0,r.jsx)(n.code,{children:"dashy"})," realm, open ",(0,r.jsx)(n.strong,{children:"Realm settings"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Security defenses"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Headers"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Clear ",(0,r.jsx)(n.strong,{children:"X-Frame-Options"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Content-Security-Policy"})," to:"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"frame-src 'self' http://localhost:4000 http://127.0.0.1:4000; frame-ancestors 'self' http://localhost:4000 http://127.0.0.1:4000; object-src 'none';\n"})}),"\n",(0,r.jsxs)(n.ol,{start:"6",children:["\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Save"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["If Dashy and Keycloak are served from the same origin in production, you can skip this step. Clearing X-Frame-Options and allow-listing origins is only needed when Dashy frames Keycloak's ",(0,r.jsx)(n.code,{children:"check-sso"})," iframe across origins, as in this localhost setup."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"create-the-dashy-client",children:"Create the Dashy client"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["In the ",(0,r.jsx)(n.code,{children:"dashy"})," realm, open ",(0,r.jsx)(n.strong,{children:"Clients"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create client"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Client type"})," to ",(0,r.jsx)(n.code,{children:"OpenID Connect"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Client ID"})," to ",(0,r.jsx)(n.code,{children:"dashy"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Next"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Client authentication"})," off. Dashy is a SPA using PKCE, so it must be a public client."]}),"\n",(0,r.jsxs)(n.li,{children:["Leave ",(0,r.jsx)(n.strong,{children:"Standard flow"})," on."]}),"\n",(0,r.jsxs)(n.li,{children:["Leave ",(0,r.jsx)(n.strong,{children:"Direct access grants"})," on or off; Dashy does not require it."]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Next"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Valid redirect URIs"})," to:\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://localhost:4000/*"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://127.0.0.1:4000/*"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Valid post logout redirect URIs"})," to:\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://localhost:4000/*"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://127.0.0.1:4000/*"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Web origins"})," to:\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://localhost:4000"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://127.0.0.1:4000"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Save"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"add-the-realm-role-mapper",children:"Add the realm-role mapper"}),"\n",(0,r.jsxs)(n.p,{children:["Dashy uses ",(0,r.jsx)(n.code,{children:"adminRole: dashy-admin"})," in ",(0,r.jsx)(n.code,{children:"user-data/conf.yml"}),". For server-side admin checks to work, Keycloak must include realm roles in the ID token"]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Clients"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click the ",(0,r.jsx)(n.code,{children:"dashy"})," client"]}),"\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Client scopes"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click the dedicated scope row, usually named ",(0,r.jsx)(n.code,{children:"dashy-dedicated"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Add mapper"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"By configuration"})]}),"\n",(0,r.jsxs)(n.li,{children:["Select ",(0,r.jsx)(n.strong,{children:"User Realm Role"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Name"})," to ",(0,r.jsx)(n.code,{children:"dashy-realm-roles"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Token Claim Name"})," to ",(0,r.jsx)(n.code,{children:"realm_access.roles"})]}),"\n",(0,r.jsxs)(n.li,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Multivalued"})," on"]}),"\n",(0,r.jsxs)(n.li,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Add to ID token"})," on"]}),"\n",(0,r.jsxs)(n.li,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Add to access token"})," on"]}),"\n",(0,r.jsxs)(n.li,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Add to userinfo"})," on"]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Save"})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"create-the-dashy-admin-role",children:"Create the Dashy admin role"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["In the ",(0,r.jsx)(n.code,{children:"dashy"})," realm, open ",(0,r.jsx)(n.strong,{children:"Realm roles"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create role"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Role name"})," to ",(0,r.jsx)(n.code,{children:"dashy-admin"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Save"})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"alternative-use-a-group-for-admin",children:"(Alternative) Use a group for admin"}),"\n",(0,r.jsxs)(n.p,{children:["To grant admin via group membership instead of a realm role, add a second mapper of type ",(0,r.jsx)(n.em,{children:"Group Membership"})," with claim name ",(0,r.jsx)(n.code,{children:"groups"}),", create the group under ",(0,r.jsx)(n.em,{children:"Groups"}),", assign your admin users to it, then set ",(0,r.jsx)(n.code,{children:"adminGroup: "})," (in place of ",(0,r.jsx)(n.code,{children:"adminRole"}),") in your Dashy config later."]}),"\n",(0,r.jsx)(n.h3,{id:"create-test-users",children:"Create test users"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["On Keycloak 25 and newer, ",(0,r.jsx)(n.strong,{children:"First name"})," and ",(0,r.jsx)(n.strong,{children:"Last name"}),' are required by the default user-profile schema. Skip them and the user can authenticate, but login then fails with "Account is not fully set up". The steps below set both.']}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Create an admin user:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Users"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Add user"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Username"})," to ",(0,r.jsx)(n.code,{children:"keycloak-admin"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Email verified"})," to on"]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"First name"})," to ",(0,r.jsx)(n.code,{children:"Keycloak"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Last name"})," to ",(0,r.jsx)(n.code,{children:"Admin"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create"})]}),"\n",(0,r.jsxs)(n.li,{children:["Open the ",(0,r.jsx)(n.strong,{children:"Credentials"})," tab"]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Set password"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set a password, turn ",(0,r.jsx)(n.strong,{children:"Temporary"})," off, and save it"]}),"\n",(0,r.jsxs)(n.li,{children:["Open the ",(0,r.jsx)(n.strong,{children:"Role mapping"})," tab"]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Assign role"})]}),"\n",(0,r.jsxs)(n.li,{children:["Filter by realm roles, select ",(0,r.jsx)(n.code,{children:"dashy-admin"}),", and assign it"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Create a normal user:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Users"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Add user"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Username"})," to ",(0,r.jsx)(n.code,{children:"keycloak-user"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Email verified"})," to on"]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"First name"})," to ",(0,r.jsx)(n.code,{children:"Keycloak"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Last name"})," to ",(0,r.jsx)(n.code,{children:"User"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create"})]}),"\n",(0,r.jsxs)(n.li,{children:["Open the ",(0,r.jsx)(n.strong,{children:"Credentials"})," tab"]}),"\n",(0,r.jsx)(n.li,{children:"Set a non-temporary password"}),"\n",(0,r.jsxs)(n.li,{children:["Do not assign ",(0,r.jsx)(n.code,{children:"dashy-admin"})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"summary",children:"Summary"}),"\n",(0,r.jsx)(n.p,{children:"Keycloak should now be configured, and ready to go!"}),"\n",(0,r.jsx)(n.p,{children:"The Keycloak UI is not super intuitive, so if you're struggling to find where to configure any of the above options, below is a full start-to-end walkthrough video:"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.a,{href:"https://github.com/user-attachments/assets/12b6a596-1ec6-453a-9ff7-d4e2c3aa69f7",children:"https://github.com/user-attachments/assets/12b6a596-1ec6-453a-9ff7-d4e2c3aa69f7"})}),"\n",(0,r.jsx)(n.p,{children:"If you need to, you can make a backup of your Keycloak config, with their built-in backup tool. Something like:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker compose run --rm --no-deps \\\n -v ./backup:/backup \\\n keycloak export \\\n --realm dashy \\\n --dir /backup \\\n --users realm_file \\\n --optimized\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Here's an example of a configured ",(0,r.jsx)(n.a,{href:"https://github.com/user-attachments/files/27861822/dashy-realm.json",children:(0,r.jsx)(n.code,{children:"dashy-realm.json"})})," backup."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"3-enabling-keycloak-in-dashy",children:"3. Enabling Keycloak in Dashy"}),"\n",(0,r.jsxs)(n.p,{children:["Finally, you need to tell Dashy to use the new Keycloak setup, and only allow access from authorized Keycloak users. This is done in the ",(0,r.jsx)(n.code,{children:"appConfig.auth"})," section of your main ",(0,r.jsx)(n.code,{children:"/user-data/conf.yml"})," file."]}),"\n",(0,r.jsxs)(n.p,{children:["As an example, you can view this ",(0,r.jsx)(n.a,{href:"https://github.com/user-attachments/files/27861628/conf.yml",children:(0,r.jsx)(n.code,{children:"conf.yml"})}),", which is fully-configured with Keycloak auth using the info from the above steps."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n ...\n disableConfigurationForNonAdmin: true\n auth:\n enableKeycloak: true\n keycloak:\n serverUrl: http://localhost:9100\n realm: dashy\n clientId: dashy\n adminRole: dashy-admin\n"})}),"\n",(0,r.jsx)(n.p,{children:"Where:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"disableConfigurationForNonAdmin"})," - Prevent read/write config access to non-admin Keycloak users"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"auth.enableKeycloak"})," - Set the auth mode to Keycloak"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"serverUrl"})," - The host (no paths) to your Keycloak instance, accessible from the Dashy container"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"realm"})," - The name (case sensitive) of the Keycloak realm to use"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"clientId"})," - Client ID that you created for Dashy"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"adminRole"})," - The role name that grants admin"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"adminGroup"})," - (Alternative to ",(0,r.jsx)(n.code,{children:"adminRole"}),") Group name that grants admin"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"idpHint"})," - (Optional) Alias of an external IdP federated through Keycloak; skips the Keycloak login page and redirects straight to that provider"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Note that a restart is required for these changes to take effect."}),"\n",(0,r.jsxs)(n.p,{children:["If Keycloak runs on a different host or behind a reverse proxy, make sure ",(0,r.jsx)(n.code,{children:"serverUrl"})," is reachable from inside the Dashy container, and that the realm's redirect URIs and Web Origins match Dashy's public URL."]}),"\n",(0,r.jsxs)(n.p,{children:["Everything should now be fully configured and working \ud83c\udf89",(0,r.jsx)(n.br,{}),"\nNow, when you load Dashy, you'll be redirected to your Keycloak login page, after logging in you will then land back on Dashy's homepage with full access! Until you're authenticated, Dashy's config and API endpoints return a 401 (a write attempt by a non-admin returns a 403)."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"4-groups-and-roles",children:"4. Groups and Roles"}),"\n",(0,r.jsx)(n.p,{children:"Keycloak allows you to assign users roles and groups. Dashy supports using these roles, to configure who can access various sections or items in Dashy's frontend."}),"\n",(0,r.jsxs)(n.p,{children:["For example, to make any given section only visible to admins, simply add the following to the section's ",(0,r.jsx)(n.code,{children:"displayData"})," section:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"displayData:\n showForKeycloakUsers:\n roles:\n - dashy-admin # ID of the admin role you created earlier\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Keycloak server administration and configuration is a deep topic; please refer to the ",(0,r.jsx)(n.a,{href:"https://www.keycloak.org/docs/latest/server_admin/index.html#assigning-permissions-and-access-using-roles-and-groups",children:"server admin guide"})," to see details about creating and assigning roles and groups."]}),"\n",(0,r.jsxs)(n.p,{children:["Once you have groups or roles assigned to users you can configure access under each section or item ",(0,r.jsx)(n.code,{children:"displayData.showForKeycloakUsers"})," and ",(0,r.jsx)(n.code,{children:"displayData.hideForKeycloakUsers"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Both show and hide configurations accept a list of ",(0,r.jsx)(n.code,{children:"groups"})," and ",(0,r.jsx)(n.code,{children:"roles"})," that limit access. If a users data matches one or more items in these lists they will be allowed or excluded as defined."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"sections:\n - name: DeveloperResources\n displayData:\n showForKeycloakUsers:\n roles: ['canViewDevResources']\n hideForKeycloakUsers:\n groups: ['ProductTeam']\n items:\n - title: Not Visible for developers\n displayData:\n hideForKeycloakUsers:\n groups: ['DevelopmentTeam']\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"troubleshooting-common-keycloak-issues",children:"Troubleshooting common Keycloak Issues"}),"\n",(0,r.jsx)(n.h4,{id:"client-authentication-issue",children:"Client Authentication Issue"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Redirect loop, if client authentication is enabled.",(0,r.jsx)(n.br,{}),'\nSolution: Switch off "Client authentication" in the dashy client\'s "Advanced" settings.']}),"\n",(0,r.jsx)(n.h4,{id:"double-url",children:"Double URL"}),"\n",(0,r.jsxs)(n.p,{children:['Problem: If you get redirected to "',(0,r.jsx)(n.a,{href:"https://dashy.my.domain/#iss=https://keycloak.my.domain/realms/dashy",children:"https://dashy.my.domain/#iss=https://keycloak.my.domain/realms/dashy"}),'"',(0,r.jsx)(n.br,{}),'\nSolution: Turn on "Exclude Issuer From Authentication Response" in the dashy client\'s "Advanced" -> "OpenID Connect Compatibility Modes".']}),"\n",(0,r.jsx)(n.h4,{id:"problems-with-multiple-dashy-pages",children:"Problems with multiple Dashy Pages"}),"\n",(0,r.jsxs)(n.p,{children:['Problem: Refreshing or logging out of dashy results in an "invalid_redirect_uri" error.',(0,r.jsx)(n.br,{}),'\nSolution: In the dashy client\'s "Access settings", set "Root URL" to ',(0,r.jsx)(n.a,{href:"https://dashy.my.domain/",children:"https://dashy.my.domain/"}),", and make sure the valid redirect URIs end in /*."]}),"\n",(0,r.jsx)(n.h4,{id:"403-on-login-status-iframehtmlinit",children:"403 on login-status-iframe.html/init"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Browser console shows a 403 from Keycloak when the SPA loads.",(0,r.jsx)(n.br,{}),'\nSolution: Open the dashy client\'s "Web origins" and remove any trailing ',(0,r.jsx)(n.code,{children:"/*"}),". Web Origins must be bare origins (e.g. ",(0,r.jsx)(n.a,{href:"http://localhost:4000",children:"http://localhost:4000"}),"), not ",(0,r.jsx)(n.a,{href:"http://localhost:4000/",children:"http://localhost:4000/"}),"*."]}),"\n",(0,r.jsx)(n.h4,{id:"csp-error-for-3p-cookiesstep1html-or-authentication-failed-keycloak",children:'CSP error for /3p-cookies/step1.html or "Authentication failed (Keycloak)"'}),"\n",(0,r.jsxs)(n.p,{children:["Problem: The hidden Keycloak iframe is blocked by frame-ancestors.",(0,r.jsx)(n.br,{}),"\nSolution: In the dashy realm (not master), open Realm settings -> Security defenses -> Headers. Clear X-Frame-Options and set the Content-Security-Policy as described earlier in this section."]}),"\n",(0,r.jsx)(n.h4,{id:"dashy-server-cant-reach-keycloak",children:"Dashy server can't reach Keycloak"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: SPA loads fine but every authenticated API call returns 401, and the Dashy server logs show ",(0,r.jsx)(n.code,{children:"[auth-oidc] token verification failed"})," or fetch errors for ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"}),".",(0,r.jsx)(n.br,{}),"\nSolution: ",(0,r.jsx)(n.code,{children:"serverUrl"})," must be reachable from inside the Dashy container, not just from the browser. In Docker, put both services on the same network and use the service name (e.g. ",(0,r.jsx)(n.code,{children:"http://keycloak:8080"}),"). Test with ",(0,r.jsx)(n.code,{children:'docker exec wget -qO- "$SERVER_URL/realms/dashy/.well-known/openid-configuration"'}),"."]}),"\n",(0,r.jsx)(n.h4,{id:"logged-in-but-config-saves-return-403",children:"Logged in but config saves return 403"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: User authenticates fine, but saving the dashboard returns 403.",(0,r.jsx)(n.br,{}),"\nSolution: The id_token doesn't carry the admin claim. Confirm the ",(0,r.jsx)(n.em,{children:"Add to ID token"})," toggle on the Step 2 mapper is on. Paste the token (from localStorage, key ",(0,r.jsx)(n.code,{children:"ID_TOKEN"}),") into ",(0,r.jsx)(n.a,{href:"https://jwt.io",children:"jwt.io"})," and look for ",(0,r.jsx)(n.code,{children:"realm_access.roles"})," (or ",(0,r.jsx)(n.code,{children:"groups"})," if you're using ",(0,r.jsx)(n.code,{children:"adminGroup"}),")."]}),"\n",(0,r.jsx)(n.h4,{id:"issuer-mismatch-behind-a-reverse-proxy",children:"Issuer mismatch behind a reverse proxy"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Server logs show ",(0,r.jsx)(n.code,{children:'unexpected "iss" claim value'}),". The browser reaches Keycloak over HTTPS but Keycloak advertises an HTTP issuer in its discovery document.",(0,r.jsx)(n.br,{}),"\nSolution: Set ",(0,r.jsx)(n.code,{children:"KC_HOSTNAME="})," on Keycloak so the issuer matches the public URL, and ensure your reverse proxy forwards ",(0,r.jsx)(n.code,{children:"X-Forwarded-Proto: https"}),"."]}),"\n",(0,r.jsx)(n.h4,{id:"audience-mismatch-on-token-verification",children:"Audience mismatch on token verification"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Server logs show ",(0,r.jsx)(n.code,{children:'unexpected "aud" claim value'}),". Every auth'd API call returns 401.",(0,r.jsx)(n.br,{}),"\nSolution: ",(0,r.jsx)(n.code,{children:"clientId"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"})," must exactly match the Keycloak client's Client ID. Check for trailing whitespace, case mismatches, or accidentally using the client's internal UUID instead of the Client ID string."]}),"\n",(0,r.jsx)(n.h4,{id:"self-signed-keycloak-certificate-rejected",children:"Self-signed Keycloak certificate rejected"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Dashy server logs show TLS errors (",(0,r.jsx)(n.code,{children:"self-signed certificate"}),", ",(0,r.jsx)(n.code,{children:"UNABLE_TO_VERIFY_LEAF_SIGNATURE"}),") when fetching JWKS or discovery.",(0,r.jsx)(n.br,{}),"\nSolution: Use a real certificate on Keycloak (Let's Encrypt, or your homelab CA), or mount your CA bundle into the Dashy container and set ",(0,r.jsx)(n.code,{children:"NODE_EXTRA_CA_CERTS=/path/to/ca.pem"}),"."]}),"\n",(0,r.jsx)(n.h4,{id:"token-expired--clock-skew",children:"Token expired / clock skew"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: 401s with ",(0,r.jsx)(n.code,{children:'"exp" claim timestamp check failed'})," or ",(0,r.jsx)(n.code,{children:'"iat" claim timestamp check failed'}),", even just after login.",(0,r.jsx)(n.br,{}),"\nSolution: Dashy allows 30 seconds of drift. Sync clocks on both hosts with NTP. Container clocks follow their host, so it's almost always the host that's drifted."]}),"\n",(0,r.jsx)(n.h4,{id:"mixed-content-blocked-by-the-browser",children:"Mixed content blocked by the browser"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Dashy served over HTTPS, Keycloak over HTTP. The browser blocks the token or JWKS endpoint with a mixed-content error.",(0,r.jsx)(n.br,{}),"\nSolution: Terminate both behind HTTPS. For local testing, use HTTP on both, but never mix schemes in the same flow."]}),"\n",(0,r.jsx)(n.h4,{id:"numeric-client-id-truncated",children:"Numeric Client ID truncated"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Token verification fails with audience mismatch when ",(0,r.jsx)(n.code,{children:"clientId"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"})," is a long numeric string.",(0,r.jsx)(n.br,{}),"\nSolution: Wrap numeric Client IDs in quotes (e.g. ",(0,r.jsx)(n.code,{children:'clientId: "12345678901234567"'}),"). Without quotes YAML parses the value as a JS number and loses precision past about 15 digits."]}),"\n",(0,r.jsx)(n.h4,{id:"logout-lands-on-a-broken-page",children:"Logout lands on a broken page"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Clicking Logout in Dashy ends on a Keycloak error or 404 instead of returning to Dashy.",(0,r.jsx)(n.br,{}),"\nSolution: Add Dashy's URL to ",(0,r.jsx)(n.strong,{children:"Valid post logout redirect URIs"})," on the ",(0,r.jsx)(n.code,{children:"dashy"})," client. Keycloak v18+ requires registered, exact-match post-logout URIs."]}),"\n",(0,r.jsx)(n.h4,{id:"check-sso-hangs-in-strict-browsers",children:"check-sso hangs in strict browsers"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: First load spins indefinitely. Browser console reports blocked third-party cookies. Common in Safari, Brave, or Firefox with strict tracking protection.",(0,r.jsx)(n.br,{}),"\nSolution: Serve Dashy and Keycloak under the same registrable domain (e.g. ",(0,r.jsx)(n.code,{children:"dashy.example.com"})," and ",(0,r.jsx)(n.code,{children:"auth.example.com"}),") so the session cookie is first-party."]}),"\n",(0,r.jsx)(n.h4,{id:"config-change-to-authkeycloak-not-picked-up",children:"Config change to auth.keycloak not picked up"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Updated ",(0,r.jsx)(n.code,{children:"serverUrl"}),", ",(0,r.jsx)(n.code,{children:"realm"}),", ",(0,r.jsx)(n.code,{children:"clientId"}),", ",(0,r.jsx)(n.code,{children:"adminRole"}),", or ",(0,r.jsx)(n.code,{children:"adminGroup"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"}),", but Dashy still authenticates against the old values.",(0,r.jsx)(n.br,{}),"\nSolution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under ",(0,r.jsx)(n.code,{children:"auth.keycloak"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"how-it-works",children:"How it Works"}),"\n",(0,r.jsx)(n.p,{children:"If you're a developer or contributor looking to understand or make changes to Dashy's Keycloak implementation, the following outlines how it's wired together."}),"\n",(0,r.jsxs)(n.p,{children:["The same OIDC pipeline backs both Keycloak and generic OIDC providers. The only Keycloak-specific code is the SPA adapter, which uses ",(0,r.jsx)(n.code,{children:"keycloak-js"})," so it can take advantage of ",(0,r.jsx)(n.code,{children:"check-sso"})," and silent token renewal."]}),"\n",(0,r.jsx)(n.h3,{id:"client-side",children:"Client side"}),"\n",(0,r.jsxs)(n.p,{children:["Boot starts in ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/main.js",children:(0,r.jsx)(n.code,{children:"src/main.js"})}),". After the initial ",(0,r.jsx)(n.code,{children:"/conf.yml"})," fetch parses the auth block, ",(0,r.jsx)(n.code,{children:"isKeycloakEnabled()"})," decides whether to lazily import ",(0,r.jsx)(n.code,{children:"keycloak-js"})," and call ",(0,r.jsx)(n.code,{children:"initKeycloakAuth()"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/KeycloakAuth.js",children:(0,r.jsx)(n.code,{children:"src/utils/auth/KeycloakAuth.js"})})," wraps the adapter. On load it calls ",(0,r.jsx)(n.code,{children:"keycloakClient.init({ onLoad: 'check-sso', responseMode: 'query' })"}),". If a Keycloak session already exists the user is silently authenticated; otherwise the SPA redirects to the login page with PKCE. On return, ",(0,r.jsx)(n.code,{children:"storeKeycloakInfo()"})," persists the raw ",(0,r.jsx)(n.code,{children:"id_token"}),", the user's ",(0,r.jsx)(n.code,{children:"groups"})," and ",(0,r.jsx)(n.code,{children:"roles"}),", ",(0,r.jsx)(n.code,{children:"preferred_username"}),", and a derived ",(0,r.jsx)(n.code,{children:"isAdmin"})," flag to localStorage, then hard-redirects to ",(0,r.jsx)(n.code,{children:"/"})," so the SPA boots a second time with the Bearer token in place."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/getApiAuthHeader.js",children:(0,r.jsx)(n.code,{children:"src/utils/auth/getApiAuthHeader.js"})})," builds the Authorization header for every internal API call. It does a client-side ",(0,r.jsx)(n.code,{children:"exp"})," check and returns ",(0,r.jsx)(n.code,{children:"null"})," for missing or expired tokens, so the next request triggers a fresh login rather than a 401."]}),"\n",(0,r.jsxs)(n.p,{children:["The localStorage keys (",(0,r.jsx)(n.code,{children:"ID_TOKEN"}),", ",(0,r.jsx)(n.code,{children:"KEYCLOAK_INFO"}),", ",(0,r.jsx)(n.code,{children:"USERNAME"}),", ",(0,r.jsx)(n.code,{children:"ISADMIN"}),") live in ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/config/defaults.js",children:(0,r.jsx)(n.code,{children:"src/utils/config/defaults.js"})}),". ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/IsVisibleToUser.js",children:(0,r.jsx)(n.code,{children:"src/utils/IsVisibleToUser.js"})})," reads ",(0,r.jsx)(n.code,{children:"KEYCLOAK_INFO"})," when evaluating ",(0,r.jsx)(n.code,{children:"showForKeycloakUsers"})," and ",(0,r.jsx)(n.code,{children:"hideForKeycloakUsers"})," rules."]}),"\n",(0,r.jsx)(n.h3,{id:"server-side",children:"Server side"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/auth-oidc.js",children:(0,r.jsx)(n.code,{children:"services/auth-oidc.js"})})," contains the entire server-side auth surface, in five small pieces:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"loadOidcSettings()"})," reads ",(0,r.jsx)(n.code,{children:"auth.keycloak"})," (or ",(0,r.jsx)(n.code,{children:"auth.oidc"}),") at boot and returns a normalised ",(0,r.jsx)(n.code,{children:"{ issuer, clientId, adminGroup, adminRole }"}),". For Keycloak the issuer is ",(0,r.jsx)(n.code,{children:"/realms/"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"createOidcMiddleware()"})," returns a Connect middleware. Permissive on no-token requests so the SPA can bootstrap; otherwise it verifies the Bearer token against the realm's JWKS using ",(0,r.jsx)(n.a,{href:"https://github.com/panva/jose",children:(0,r.jsx)(n.code,{children:"jose"})}),". Checks cover signature, issuer (against the canonical value from the discovery doc), audience (must equal ",(0,r.jsx)(n.code,{children:"clientId"}),"), and expiry, with a 30-second clock-skew tolerance. Sets ",(0,r.jsx)(n.code,{children:"req.auth = { user, isAdmin, claims }"})," on success, ",(0,r.jsx)(n.code,{children:"401"})," on failure."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"getIssuerContext()"})," lazily fetches ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"})," on first use and wraps ",(0,r.jsx)(n.code,{children:"jwks_uri"})," in ",(0,r.jsx)(n.code,{children:"createRemoteJWKSet"}),", which handles JWKS caching and on-demand key rotation. The result is memoised per-issuer for the life of the process."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"deriveIsAdmin()"})," checks the token's ",(0,r.jsx)(n.code,{children:"groups"})," claim against ",(0,r.jsx)(n.code,{children:"adminGroup"}),", and the union of ",(0,r.jsx)(n.code,{children:"realm_access.roles"})," and ",(0,r.jsx)(n.code,{children:"resource_access..roles"})," against ",(0,r.jsx)(n.code,{children:"adminRole"}),". Either match returns true."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"maybeBootstrapConfig()"})," is the stripped-response helper. When auth is configured, guest access is off, and an unauthenticated request hits the root ",(0,r.jsx)(n.code,{children:"/conf.yml"}),", it returns a minimal copy with only ",(0,r.jsx)(n.code,{children:"appConfig.auth"}),", ",(0,r.jsx)(n.code,{children:"appConfig.enableServiceWorker"}),", and a ",(0,r.jsx)(n.code,{children:"pageInfo.title"})," of ",(0,r.jsx)(n.code,{children:"Login | "}),". Sections, items, hostnames and any other secrets never leave the server."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/app.js",children:(0,r.jsx)(n.code,{children:"services/app.js"})})," wires it all together. The middleware mounts as ",(0,r.jsx)(n.code,{children:"protectConfig"})," in front of every YAML route and config-mutating route. The ",(0,r.jsx)(n.code,{children:"/*.yml"})," handler sets ",(0,r.jsx)(n.code,{children:"Cache-Control: private, no-store"})," and ",(0,r.jsx)(n.code,{children:"Vary: Authorization"})," whenever auth is configured (so intermediate caches can never mix auth states), then calls ",(0,r.jsx)(n.code,{children:"maybeBootstrapConfig"}),"; a stripped result is sent as-is, otherwise ",(0,r.jsx)(n.code,{children:"res.sendFile"})," serves the full file. ",(0,r.jsx)(n.code,{children:"POST /config-manager/save"})," is additionally guarded by ",(0,r.jsx)(n.code,{children:"requireAdmin"}),", which returns ",(0,r.jsx)(n.code,{children:"401"})," if ",(0,r.jsx)(n.code,{children:"req.auth"})," is unset and ",(0,r.jsx)(n.code,{children:"403"})," if ",(0,r.jsx)(n.code,{children:"req.auth.isAdmin"})," is false."]}),"\n",(0,r.jsx)(n.h3,{id:"why-the-mapper-matters",children:"Why the mapper matters"}),"\n",(0,r.jsxs)(n.p,{children:["The server's admin check reads from the ",(0,r.jsx)(n.code,{children:"id_token"})," only. Keycloak's default mapper adds realm roles to the access token but not the id token, so without the Step 2 ",(0,r.jsx)(n.em,{children:"Add to ID token"})," toggle, ",(0,r.jsx)(n.code,{children:"realm_access.roles"})," is absent and every user is treated as non-admin. The same applies to ",(0,r.jsx)(n.code,{children:"groups"})," if you use ",(0,r.jsx)(n.code,{children:"adminGroup"}),' instead. This is the single most common cause of "logged in fine, but can\'t save changes".']}),"\n",(0,r.jsx)(n.h3,{id:"visual-overview",children:"Visual Overview"}),"\n",(0,r.jsxs)(n.details,{children:["\n",(0,r.jsx)(n.summary,{children:"End-to-end authentication flow"}),"\n",(0,r.jsx)(n.mermaid,{value:"sequenceDiagram\n autonumber\n actor User\n participant Browser as Browser (Dashy SPA)\n participant Dashy as Dashy Server\n participant KC as Keycloak\n\n Note over Browser,Dashy: 1. Bootstrap (no token yet)\n User->>Browser: Open Dashy\n Browser->>Dashy: GET /conf.yml\n Dashy--\x3e>Browser: 200 stripped conf
(auth block + minimal pageInfo)\n Browser->>Browser: Parse auth.keycloak\n\n Note over Browser,KC: 2. OIDC login (Authorization Code + PKCE)\n Browser->>KC: 302 /realms/dashy/protocol/openid-connect/auth
client_id, code_challenge, redirect_uri\n User->>KC: Submit credentials\n KC--\x3e>Browser: 302 back with ?code=AUTH_CODE\n Browser->>KC: POST /token (code + code_verifier)\n KC--\x3e>Browser: id_token + access_token + refresh_token\n Browser->>Browser: Store tokens in localStorage\n\n Note over Browser,Dashy: 3. Authenticated read\n Browser->>Dashy: GET /conf.yml
Authorization: Bearer id_token\n Dashy->>KC: Fetch discovery + JWKS
(lazy on first call, then cached)\n KC--\x3e>Dashy: openid-configuration + JWKS\n Dashy->>Dashy: Verify signature / issuer / audience / expiry\n Dashy--\x3e>Browser: 200 full conf.yml\n\n Note over Browser,Dashy: 4. Write request (admin only)\n Browser->>Dashy: POST /config-manager/save
Authorization: Bearer id_token\n Dashy->>Dashy: Verify token, derive isAdmin from
adminRole / adminGroup claims\n alt isAdmin\n Dashy--\x3e>Browser: 200 saved\n else not admin\n Dashy--\x3e>Browser: 403 Forbidden\n end"}),"\n"]}),"\n",(0,r.jsxs)(n.details,{children:["\n",(0,r.jsx)(n.summary,{children:"Server-side request handling"}),"\n",(0,r.jsx)(n.mermaid,{value:'flowchart TD\n Req([Incoming request to Dashy server])\n Req --\x3e Bearer{Authorization:
Bearer present?}\n\n Bearer -- No --\x3e NoAuth[req.auth unset
pass through]:::neutral\n Bearer -- Yes --\x3e Verify[/Verify JWT against cached JWKS:
signature \xb7 issuer \xb7 audience \xb7 expiry/]\n Verify --\x3e Valid{Valid?}\n Valid -- No --\x3e R401Bad[/401 Unauthorized/]:::err\n Valid -- Yes --\x3e SetAuth[req.auth = user + isAdmin
derived from claims]:::ok\n\n NoAuth --\x3e Route{Endpoint}\n SetAuth --\x3e Route\n\n Route -- "GET /conf.yml" --\x3e ConfGate{req.auth?}\n ConfGate -- No --\x3e Strip[/200 stripped conf
auth + minimal pageInfo/]:::ok\n ConfGate -- Yes --\x3e Full[/200 full conf.yml/]:::ok\n\n Route -- "POST /config-manager/save" --\x3e SaveGate{req.auth
and isAdmin?}\n SaveGate -- No req.auth --\x3e R401Save[/401 Unauthorized/]:::err\n SaveGate -- Not admin --\x3e R403[/403 Forbidden/]:::err\n SaveGate -- Admin --\x3e Saved[/200 saved/]:::ok\n\n classDef ok fill:#bbf7d0,stroke:#16a34a,color:#14532d\n classDef err fill:#fecaca,stroke:#dc2626,color:#7f1d1d\n classDef neutral fill:#dbeafe,stroke:#2563eb,color:#1e3a8a'}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>l,x:()=>t});var i=s(96540);const r={},o=i.createContext(r);function l(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[8559],{29535(e,n,s){s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>t,default:()=>h,frontMatter:()=>l,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"authentication/keycloak","title":"Keycloak","description":"Dashy supports using a Keycloak (V17+) authentication server.","source":"@site/docs/authentication/keycloak.md","sourceDirName":"authentication","slug":"/authentication/keycloak","permalink":"/docs/authentication/keycloak","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/keycloak.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Header Authentication","permalink":"/docs/authentication/header-auth"},"next":{"title":"OIDC","permalink":"/docs/authentication/oidc"}}');var r=s(74848),o=s(28453);const l={},t="Keycloak",a={},c=[{value:"Contents",id:"contents",level:3},{value:"1. Deploy Keycloak",id:"1-deploy-keycloak",level:2},{value:"2. Configure Keycloak",id:"2-configure-keycloak",level:2},{value:"Create the Keycloak realm",id:"create-the-keycloak-realm",level:3},{value:"Allow Dashy's local origin",id:"allow-dashys-local-origin",level:3},{value:"Create the Dashy client",id:"create-the-dashy-client",level:3},{value:"Add the realm-role mapper",id:"add-the-realm-role-mapper",level:3},{value:"Create the Dashy admin role",id:"create-the-dashy-admin-role",level:3},{value:"(Alternative) Use a group for admin",id:"alternative-use-a-group-for-admin",level:3},{value:"Create test users",id:"create-test-users",level:3},{value:"Summary",id:"summary",level:3},{value:"3. Enabling Keycloak in Dashy",id:"3-enabling-keycloak-in-dashy",level:2},{value:"4. Groups and Roles",id:"4-groups-and-roles",level:2},{value:"Troubleshooting common Keycloak Issues",id:"troubleshooting-common-keycloak-issues",level:2},{value:"Client Authentication Issue",id:"client-authentication-issue",level:4},{value:"Double URL",id:"double-url",level:4},{value:"Problems with multiple Dashy Pages",id:"problems-with-multiple-dashy-pages",level:4},{value:"403 on login-status-iframe.html/init",id:"403-on-login-status-iframehtmlinit",level:4},{value:"CSP error for /3p-cookies/step1.html or "Authentication failed (Keycloak)"",id:"csp-error-for-3p-cookiesstep1html-or-authentication-failed-keycloak",level:4},{value:"Dashy server can't reach Keycloak",id:"dashy-server-cant-reach-keycloak",level:4},{value:"Logged in but config saves return 403",id:"logged-in-but-config-saves-return-403",level:4},{value:"Issuer mismatch behind a reverse proxy",id:"issuer-mismatch-behind-a-reverse-proxy",level:4},{value:"Audience mismatch on token verification",id:"audience-mismatch-on-token-verification",level:4},{value:"Self-signed Keycloak certificate rejected",id:"self-signed-keycloak-certificate-rejected",level:4},{value:"Token expired / clock skew",id:"token-expired--clock-skew",level:4},{value:"Mixed content blocked by the browser",id:"mixed-content-blocked-by-the-browser",level:4},{value:"Numeric Client ID truncated",id:"numeric-client-id-truncated",level:4},{value:"Logout lands on a broken page",id:"logout-lands-on-a-broken-page",level:4},{value:"check-sso hangs in strict browsers",id:"check-sso-hangs-in-strict-browsers",level:4},{value:"Config change to auth.keycloak not picked up",id:"config-change-to-authkeycloak-not-picked-up",level:4},{value:"How it Works",id:"how-it-works",level:2},{value:"Client side",id:"client-side",level:3},{value:"Server side",id:"server-side",level:3},{value:"Why the mapper matters",id:"why-the-mapper-matters",level:3},{value:"Visual Overview",id:"visual-overview",level:3}];function d(e){const n={a:"a",blockquote:"blockquote",br:"br",code:"code",details:"details",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",mermaid:"mermaid",ol:"ol",p:"p",pre:"pre",strong:"strong",summary:"summary",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"keycloak",children:"Keycloak"})}),"\n",(0,r.jsxs)(n.p,{children:["Dashy supports using a ",(0,r.jsx)(n.a,{href:"https://www.keycloak.org/",children:"Keycloak"})," (V17+) authentication server."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://www.keycloak.org/about.html",children:"Keycloak"})," is a Java-based ",(0,r.jsx)(n.a,{href:"https://github.com/keycloak/keycloak",children:"open source"}),", high-performance, secure authentication system, supported by ",(0,r.jsx)(n.a,{href:"https://www.redhat.com/en",children:"RedHat"}),". It can be deployed with Docker, and enables you to secure multiple self-hosted applications with single-sign-on using standard protocols (OpenID Connect, OAuth 2.0, SAML 2.0 and social login)."]}),"\n",(0,r.jsx)(n.h3,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#1-deploy-keycloak",children:"1. Deploy Keycloak"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#2-configure-keycloak",children:"2. Configure Keycloak"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#3-enabling-keycloak-in-dashy",children:"3. Enabling Keycloak in Dashy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#4-groups-and-roles",children:"4. Groups and Roles"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#troubleshooting-common-keycloak-issues",children:"Troubleshooting"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#how-it-works",children:"How it Works"})}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"1-deploy-keycloak",children:"1. Deploy Keycloak"}),"\n",(0,r.jsxs)(n.p,{children:["If you've not already done so, spin up a Keycloak instance.\nYou can do this by following the ",(0,r.jsx)(n.a,{href:"https://www.keycloak.org/guides.html#getting-started",children:"Keycloak Docs"}),", or use the following Docker examples:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker run -d \\\n -p 9100:8080 \\\n --name keycloak \\\n -e KEYCLOAK_ADMIN=kc-admin \\\n -e KEYCLOAK_ADMIN_PASSWORD=KeycloakAdmin2026! \\\n quay.io/keycloak/keycloak:25.0 start-dev\n"})}),"\n",(0,r.jsxs)(n.details,{children:["\n ",(0,r.jsxs)(n.summary,{children:["Example ",(0,r.jsx)(n.code,{children:"docker-compose.yml"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-env",children:"KEYCLOAK_ADMIN=kc-admin\nKEYCLOAK_ADMIN_PASSWORD=KeycloakAdmin2026!\n"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'name: dashy-keycloak\nservices:\n keycloak:\n image: quay.io/keycloak/keycloak:25.0\n command:\n - start-dev\n - --http-port=9100\n - --hostname-strict=false\n - --health-enabled=true\n restart: unless-stopped\n ports:\n - "9100:9100"\n - "4000:8080"\n volumes:\n - keycloak-data:/opt/keycloak/data\n environment:\n KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}\n KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}\n KC_HTTP_ENABLED: "true"\n KC_HOSTNAME_STRICT: "false"\n KC_HEALTH_ENABLED: "true"\n healthcheck:\n test: ["CMD-SHELL", "timeout 2 bash -c \'/dev/null 2>&1"]\n start_period: 30s\n interval: 10s\n timeout: 5s\n retries: 15\n\nvolumes:\n keycloak-data:\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["You should now be able to access the Keycloak web interface at ",(0,r.jsx)(n.code,{children:"http://127.0.0.1:9100"}),", log in with your admin credentials above, and create a new password when prompted."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"2-configure-keycloak",children:"2. Configure Keycloak"}),"\n",(0,r.jsx)(n.h3,{id:"create-the-keycloak-realm",children:"Create the Keycloak realm"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.code,{children:"http://localhost:9100"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Administration Console"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Log in as ",(0,r.jsx)(n.code,{children:"kc-admin"}),"."]}),"\n",(0,r.jsx)(n.li,{children:"Open the realm selector in the top-left."}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create realm"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Realm name"})," to ",(0,r.jsx)(n.code,{children:"dashy"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"allow-dashys-local-origin",children:"Allow Dashy's local origin"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["In the ",(0,r.jsx)(n.code,{children:"dashy"})," realm, open ",(0,r.jsx)(n.strong,{children:"Realm settings"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Security defenses"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Headers"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Clear ",(0,r.jsx)(n.strong,{children:"X-Frame-Options"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Content-Security-Policy"})," to:"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"frame-src 'self' http://localhost:4000 http://127.0.0.1:4000; frame-ancestors 'self' http://localhost:4000 http://127.0.0.1:4000; object-src 'none';\n"})}),"\n",(0,r.jsxs)(n.ol,{start:"6",children:["\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Save"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["If Dashy and Keycloak are served from the same origin in production, you can skip this step. Clearing X-Frame-Options and allow-listing origins is only needed when Dashy frames Keycloak's ",(0,r.jsx)(n.code,{children:"check-sso"})," iframe across origins, as in this localhost setup."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"create-the-dashy-client",children:"Create the Dashy client"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["In the ",(0,r.jsx)(n.code,{children:"dashy"})," realm, open ",(0,r.jsx)(n.strong,{children:"Clients"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create client"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Client type"})," to ",(0,r.jsx)(n.code,{children:"OpenID Connect"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Client ID"})," to ",(0,r.jsx)(n.code,{children:"dashy"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Next"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Client authentication"})," off. Dashy is a SPA using PKCE, so it must be a public client."]}),"\n",(0,r.jsxs)(n.li,{children:["Leave ",(0,r.jsx)(n.strong,{children:"Standard flow"})," on."]}),"\n",(0,r.jsxs)(n.li,{children:["Leave ",(0,r.jsx)(n.strong,{children:"Direct access grants"})," on or off; Dashy does not require it."]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Next"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Valid redirect URIs"})," to:\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://localhost:4000/*"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://127.0.0.1:4000/*"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Valid post logout redirect URIs"})," to:\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://localhost:4000/*"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://127.0.0.1:4000/*"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Web origins"})," to:\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://localhost:4000"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"http://127.0.0.1:4000"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Save"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"add-the-realm-role-mapper",children:"Add the realm-role mapper"}),"\n",(0,r.jsxs)(n.p,{children:["Dashy uses ",(0,r.jsx)(n.code,{children:"adminRole: dashy-admin"})," in ",(0,r.jsx)(n.code,{children:"user-data/conf.yml"}),". For server-side admin checks to work, Keycloak must include realm roles in the ID token"]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Clients"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click the ",(0,r.jsx)(n.code,{children:"dashy"})," client"]}),"\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Client scopes"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click the dedicated scope row, usually named ",(0,r.jsx)(n.code,{children:"dashy-dedicated"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Add mapper"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"By configuration"})]}),"\n",(0,r.jsxs)(n.li,{children:["Select ",(0,r.jsx)(n.strong,{children:"User Realm Role"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Name"})," to ",(0,r.jsx)(n.code,{children:"dashy-realm-roles"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Token Claim Name"})," to ",(0,r.jsx)(n.code,{children:"realm_access.roles"})]}),"\n",(0,r.jsxs)(n.li,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Multivalued"})," on"]}),"\n",(0,r.jsxs)(n.li,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Add to ID token"})," on"]}),"\n",(0,r.jsxs)(n.li,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Add to access token"})," on"]}),"\n",(0,r.jsxs)(n.li,{children:["Turn ",(0,r.jsx)(n.strong,{children:"Add to userinfo"})," on"]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Save"})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"create-the-dashy-admin-role",children:"Create the Dashy admin role"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["In the ",(0,r.jsx)(n.code,{children:"dashy"})," realm, open ",(0,r.jsx)(n.strong,{children:"Realm roles"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create role"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Role name"})," to ",(0,r.jsx)(n.code,{children:"dashy-admin"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Save"})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"alternative-use-a-group-for-admin",children:"(Alternative) Use a group for admin"}),"\n",(0,r.jsxs)(n.p,{children:["To grant admin via group membership instead of a realm role, add a second mapper of type ",(0,r.jsx)(n.em,{children:"Group Membership"})," with claim name ",(0,r.jsx)(n.code,{children:"groups"}),", create the group under ",(0,r.jsx)(n.em,{children:"Groups"}),", assign your admin users to it, then set ",(0,r.jsx)(n.code,{children:"adminGroup: "})," (in place of ",(0,r.jsx)(n.code,{children:"adminRole"}),") in your Dashy config later."]}),"\n",(0,r.jsx)(n.h3,{id:"create-test-users",children:"Create test users"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:["On Keycloak 25 and newer, ",(0,r.jsx)(n.strong,{children:"First name"})," and ",(0,r.jsx)(n.strong,{children:"Last name"}),' are required by the default user-profile schema. Skip them and the user can authenticate, but login then fails with "Account is not fully set up". The steps below set both.']}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Create an admin user:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Users"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Add user"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Username"})," to ",(0,r.jsx)(n.code,{children:"keycloak-admin"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Email verified"})," to on"]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"First name"})," to ",(0,r.jsx)(n.code,{children:"Keycloak"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Last name"})," to ",(0,r.jsx)(n.code,{children:"Admin"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create"})]}),"\n",(0,r.jsxs)(n.li,{children:["Open the ",(0,r.jsx)(n.strong,{children:"Credentials"})," tab"]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Set password"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set a password, turn ",(0,r.jsx)(n.strong,{children:"Temporary"})," off, and save it"]}),"\n",(0,r.jsxs)(n.li,{children:["Open the ",(0,r.jsx)(n.strong,{children:"Role mapping"})," tab"]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Assign role"})]}),"\n",(0,r.jsxs)(n.li,{children:["Filter by realm roles, select ",(0,r.jsx)(n.code,{children:"dashy-admin"}),", and assign it"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Create a normal user:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Open ",(0,r.jsx)(n.strong,{children:"Users"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Add user"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Username"})," to ",(0,r.jsx)(n.code,{children:"keycloak-user"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Email verified"})," to on"]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"First name"})," to ",(0,r.jsx)(n.code,{children:"Keycloak"})]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.strong,{children:"Last name"})," to ",(0,r.jsx)(n.code,{children:"User"})]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Create"})]}),"\n",(0,r.jsxs)(n.li,{children:["Open the ",(0,r.jsx)(n.strong,{children:"Credentials"})," tab"]}),"\n",(0,r.jsx)(n.li,{children:"Set a non-temporary password"}),"\n",(0,r.jsxs)(n.li,{children:["Do not assign ",(0,r.jsx)(n.code,{children:"dashy-admin"})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"summary",children:"Summary"}),"\n",(0,r.jsx)(n.p,{children:"Keycloak should now be configured, and ready to go!"}),"\n",(0,r.jsx)(n.p,{children:"The Keycloak UI is not super intuitive, so if you're struggling to find where to configure any of the above options, below is a full start-to-end walkthrough video:"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.a,{href:"https://github.com/user-attachments/assets/12b6a596-1ec6-453a-9ff7-d4e2c3aa69f7",children:"https://github.com/user-attachments/assets/12b6a596-1ec6-453a-9ff7-d4e2c3aa69f7"})}),"\n",(0,r.jsx)(n.p,{children:"If you need to, you can make a backup of your Keycloak config, with their built-in backup tool. Something like:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker compose run --rm --no-deps \\\n -v ./backup:/backup \\\n keycloak export \\\n --realm dashy \\\n --dir /backup \\\n --users realm_file \\\n --optimized\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Here's an example of a configured ",(0,r.jsx)(n.a,{href:"https://github.com/user-attachments/files/27861822/dashy-realm.json",children:(0,r.jsx)(n.code,{children:"dashy-realm.json"})})," backup."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"3-enabling-keycloak-in-dashy",children:"3. Enabling Keycloak in Dashy"}),"\n",(0,r.jsxs)(n.p,{children:["Finally, you need to tell Dashy to use the new Keycloak setup, and only allow access from authorized Keycloak users. This is done in the ",(0,r.jsx)(n.code,{children:"appConfig.auth"})," section of your main ",(0,r.jsx)(n.code,{children:"/user-data/conf.yml"})," file."]}),"\n",(0,r.jsxs)(n.p,{children:["As an example, you can view this ",(0,r.jsx)(n.a,{href:"https://github.com/user-attachments/files/27861628/conf.yml",children:(0,r.jsx)(n.code,{children:"conf.yml"})}),", which is fully-configured with Keycloak auth using the info from the above steps."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"appConfig:\n ...\n disableConfigurationForNonAdmin: true\n auth:\n enableKeycloak: true\n keycloak:\n serverUrl: http://localhost:9100\n realm: dashy\n clientId: dashy\n adminRole: dashy-admin\n"})}),"\n",(0,r.jsx)(n.p,{children:"Where:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"disableConfigurationForNonAdmin"})," - Prevent read/write config access to non-admin Keycloak users"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"auth.enableKeycloak"})," - Set the auth mode to Keycloak"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"serverUrl"})," - The host (no paths) to your Keycloak instance, accessible from the Dashy container"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"realm"})," - The name (case sensitive) of the Keycloak realm to use"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"clientId"})," - Client ID that you created for Dashy"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"adminRole"})," - The role name that grants admin"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"adminGroup"})," - (Alternative to ",(0,r.jsx)(n.code,{children:"adminRole"}),") Group name that grants admin"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"idpHint"})," - (Optional) Alias of an external IdP federated through Keycloak; skips the Keycloak login page and redirects straight to that provider"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Note that a restart is required for these changes to take effect."}),"\n",(0,r.jsxs)(n.p,{children:["If Keycloak runs on a different host or behind a reverse proxy, make sure ",(0,r.jsx)(n.code,{children:"serverUrl"})," is reachable from inside the Dashy container, and that the realm's redirect URIs and Web Origins match Dashy's public URL."]}),"\n",(0,r.jsxs)(n.p,{children:["Everything should now be fully configured and working \ud83c\udf89",(0,r.jsx)(n.br,{}),"\nNow, when you load Dashy, you'll be redirected to your Keycloak login page, after logging in you will then land back on Dashy's homepage with full access! Until you're authenticated, Dashy's config and API endpoints return a 401 (a write attempt by a non-admin returns a 403)."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"4-groups-and-roles",children:"4. Groups and Roles"}),"\n",(0,r.jsx)(n.p,{children:"Keycloak allows you to assign users roles and groups. Dashy supports using these roles, to configure who can access various sections or items in Dashy's frontend."}),"\n",(0,r.jsxs)(n.p,{children:["For example, to make any given section only visible to admins, simply add the following to the section's ",(0,r.jsx)(n.code,{children:"displayData"})," section:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"displayData:\n showForKeycloakUsers:\n roles:\n - dashy-admin # ID of the admin role you created earlier\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Keycloak server administration and configuration is a deep topic; please refer to the ",(0,r.jsx)(n.a,{href:"https://www.keycloak.org/docs/latest/server_admin/index.html#assigning-permissions-and-access-using-roles-and-groups",children:"server admin guide"})," to see details about creating and assigning roles and groups."]}),"\n",(0,r.jsxs)(n.p,{children:["Once you have groups or roles assigned to users you can configure access under each section or item ",(0,r.jsx)(n.code,{children:"displayData.showForKeycloakUsers"})," and ",(0,r.jsx)(n.code,{children:"displayData.hideForKeycloakUsers"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Both show and hide configurations accept a list of ",(0,r.jsx)(n.code,{children:"groups"})," and ",(0,r.jsx)(n.code,{children:"roles"})," that limit access. If a users data matches one or more items in these lists they will be allowed or excluded as defined."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"sections:\n - name: DeveloperResources\n displayData:\n showForKeycloakUsers:\n roles: ['canViewDevResources']\n hideForKeycloakUsers:\n groups: ['ProductTeam']\n items:\n - title: Not Visible for developers\n displayData:\n hideForKeycloakUsers:\n groups: ['DevelopmentTeam']\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"troubleshooting-common-keycloak-issues",children:"Troubleshooting common Keycloak Issues"}),"\n",(0,r.jsx)(n.h4,{id:"client-authentication-issue",children:"Client Authentication Issue"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Redirect loop, if client authentication is enabled.",(0,r.jsx)(n.br,{}),'\nSolution: Switch off "Client authentication" in the dashy client\'s "Advanced" settings.']}),"\n",(0,r.jsx)(n.h4,{id:"double-url",children:"Double URL"}),"\n",(0,r.jsxs)(n.p,{children:['Problem: If you get redirected to "',(0,r.jsx)(n.a,{href:"https://dashy.my.domain/#iss=https://keycloak.my.domain/realms/dashy",children:"https://dashy.my.domain/#iss=https://keycloak.my.domain/realms/dashy"}),'"',(0,r.jsx)(n.br,{}),'\nSolution: Turn on "Exclude Issuer From Authentication Response" in the dashy client\'s "Advanced" -> "OpenID Connect Compatibility Modes".']}),"\n",(0,r.jsx)(n.h4,{id:"problems-with-multiple-dashy-pages",children:"Problems with multiple Dashy Pages"}),"\n",(0,r.jsxs)(n.p,{children:['Problem: Refreshing or logging out of dashy results in an "invalid_redirect_uri" error.',(0,r.jsx)(n.br,{}),'\nSolution: In the dashy client\'s "Access settings", set "Root URL" to ',(0,r.jsx)(n.a,{href:"https://dashy.my.domain/",children:"https://dashy.my.domain/"}),", and make sure the valid redirect URIs end in /*."]}),"\n",(0,r.jsx)(n.h4,{id:"403-on-login-status-iframehtmlinit",children:"403 on login-status-iframe.html/init"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Browser console shows a 403 from Keycloak when the SPA loads.",(0,r.jsx)(n.br,{}),'\nSolution: Open the dashy client\'s "Web origins" and remove any trailing ',(0,r.jsx)(n.code,{children:"/*"}),". Web Origins must be bare origins (e.g. ",(0,r.jsx)(n.a,{href:"http://localhost:4000",children:"http://localhost:4000"}),"), not ",(0,r.jsx)(n.a,{href:"http://localhost:4000/",children:"http://localhost:4000/"}),"*."]}),"\n",(0,r.jsx)(n.h4,{id:"csp-error-for-3p-cookiesstep1html-or-authentication-failed-keycloak",children:'CSP error for /3p-cookies/step1.html or "Authentication failed (Keycloak)"'}),"\n",(0,r.jsxs)(n.p,{children:["Problem: The hidden Keycloak iframe is blocked by frame-ancestors.",(0,r.jsx)(n.br,{}),"\nSolution: In the dashy realm (not master), open Realm settings -> Security defenses -> Headers. Clear X-Frame-Options and set the Content-Security-Policy as described earlier in this section."]}),"\n",(0,r.jsx)(n.h4,{id:"dashy-server-cant-reach-keycloak",children:"Dashy server can't reach Keycloak"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: SPA loads fine but every authenticated API call returns 401, and the Dashy server logs show ",(0,r.jsx)(n.code,{children:"[auth-oidc] token verification failed"})," or fetch errors for ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"}),".",(0,r.jsx)(n.br,{}),"\nSolution: ",(0,r.jsx)(n.code,{children:"serverUrl"})," must be reachable from inside the Dashy container, not just from the browser. In Docker, put both services on the same network and use the service name (e.g. ",(0,r.jsx)(n.code,{children:"http://keycloak:8080"}),"). Test with ",(0,r.jsx)(n.code,{children:'docker exec wget -qO- "$SERVER_URL/realms/dashy/.well-known/openid-configuration"'}),"."]}),"\n",(0,r.jsx)(n.h4,{id:"logged-in-but-config-saves-return-403",children:"Logged in but config saves return 403"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: User authenticates fine, but saving the dashboard returns 403.",(0,r.jsx)(n.br,{}),"\nSolution: The id_token doesn't carry the admin claim. Confirm the ",(0,r.jsx)(n.em,{children:"Add to ID token"})," toggle on the Step 2 mapper is on. Paste the token (from localStorage, key ",(0,r.jsx)(n.code,{children:"ID_TOKEN"}),") into ",(0,r.jsx)(n.a,{href:"https://jwt.io",children:"jwt.io"})," and look for ",(0,r.jsx)(n.code,{children:"realm_access.roles"})," (or ",(0,r.jsx)(n.code,{children:"groups"})," if you're using ",(0,r.jsx)(n.code,{children:"adminGroup"}),")."]}),"\n",(0,r.jsx)(n.h4,{id:"issuer-mismatch-behind-a-reverse-proxy",children:"Issuer mismatch behind a reverse proxy"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Server logs show ",(0,r.jsx)(n.code,{children:'unexpected "iss" claim value'}),". The browser reaches Keycloak over HTTPS but Keycloak advertises an HTTP issuer in its discovery document.",(0,r.jsx)(n.br,{}),"\nSolution: Set ",(0,r.jsx)(n.code,{children:"KC_HOSTNAME="})," on Keycloak so the issuer matches the public URL, and ensure your reverse proxy forwards ",(0,r.jsx)(n.code,{children:"X-Forwarded-Proto: https"}),"."]}),"\n",(0,r.jsx)(n.h4,{id:"audience-mismatch-on-token-verification",children:"Audience mismatch on token verification"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Server logs show ",(0,r.jsx)(n.code,{children:'unexpected "aud" claim value'}),". Every auth'd API call returns 401.",(0,r.jsx)(n.br,{}),"\nSolution: ",(0,r.jsx)(n.code,{children:"clientId"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"})," must exactly match the Keycloak client's Client ID. Check for trailing whitespace, case mismatches, or accidentally using the client's internal UUID instead of the Client ID string."]}),"\n",(0,r.jsx)(n.h4,{id:"self-signed-keycloak-certificate-rejected",children:"Self-signed Keycloak certificate rejected"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Dashy server logs show TLS errors (",(0,r.jsx)(n.code,{children:"self-signed certificate"}),", ",(0,r.jsx)(n.code,{children:"UNABLE_TO_VERIFY_LEAF_SIGNATURE"}),") when fetching JWKS or discovery.",(0,r.jsx)(n.br,{}),"\nSolution: Use a real certificate on Keycloak (Let's Encrypt, or your homelab CA), or mount your CA bundle into the Dashy container and set ",(0,r.jsx)(n.code,{children:"NODE_EXTRA_CA_CERTS=/path/to/ca.pem"}),"."]}),"\n",(0,r.jsx)(n.h4,{id:"token-expired--clock-skew",children:"Token expired / clock skew"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: 401s with ",(0,r.jsx)(n.code,{children:'"exp" claim timestamp check failed'})," or ",(0,r.jsx)(n.code,{children:'"iat" claim timestamp check failed'}),", even just after login.",(0,r.jsx)(n.br,{}),"\nSolution: Dashy allows 30 seconds of drift. Sync clocks on both hosts with NTP. Container clocks follow their host, so it's almost always the host that's drifted."]}),"\n",(0,r.jsx)(n.h4,{id:"mixed-content-blocked-by-the-browser",children:"Mixed content blocked by the browser"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Dashy served over HTTPS, Keycloak over HTTP. The browser blocks the token or JWKS endpoint with a mixed-content error.",(0,r.jsx)(n.br,{}),"\nSolution: Terminate both behind HTTPS. For local testing, use HTTP on both, but never mix schemes in the same flow."]}),"\n",(0,r.jsx)(n.h4,{id:"numeric-client-id-truncated",children:"Numeric Client ID truncated"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Token verification fails with audience mismatch when ",(0,r.jsx)(n.code,{children:"clientId"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"})," is a long numeric string.",(0,r.jsx)(n.br,{}),"\nSolution: Wrap numeric Client IDs in quotes (e.g. ",(0,r.jsx)(n.code,{children:'clientId: "12345678901234567"'}),"). Without quotes YAML parses the value as a JS number and loses precision past about 15 digits."]}),"\n",(0,r.jsx)(n.h4,{id:"logout-lands-on-a-broken-page",children:"Logout lands on a broken page"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Clicking Logout in Dashy ends on a Keycloak error or 404 instead of returning to Dashy.",(0,r.jsx)(n.br,{}),"\nSolution: Add Dashy's URL to ",(0,r.jsx)(n.strong,{children:"Valid post logout redirect URIs"})," on the ",(0,r.jsx)(n.code,{children:"dashy"})," client. Keycloak v18+ requires registered, exact-match post-logout URIs."]}),"\n",(0,r.jsx)(n.h4,{id:"check-sso-hangs-in-strict-browsers",children:"check-sso hangs in strict browsers"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: First load spins indefinitely. Browser console reports blocked third-party cookies. Common in Safari, Brave, or Firefox with strict tracking protection.",(0,r.jsx)(n.br,{}),"\nSolution: Serve Dashy and Keycloak under the same registrable domain (e.g. ",(0,r.jsx)(n.code,{children:"dashy.example.com"})," and ",(0,r.jsx)(n.code,{children:"auth.example.com"}),") so the session cookie is first-party."]}),"\n",(0,r.jsx)(n.h4,{id:"config-change-to-authkeycloak-not-picked-up",children:"Config change to auth.keycloak not picked up"}),"\n",(0,r.jsxs)(n.p,{children:["Problem: Updated ",(0,r.jsx)(n.code,{children:"serverUrl"}),", ",(0,r.jsx)(n.code,{children:"realm"}),", ",(0,r.jsx)(n.code,{children:"clientId"}),", ",(0,r.jsx)(n.code,{children:"adminRole"}),", or ",(0,r.jsx)(n.code,{children:"adminGroup"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"}),", but Dashy still authenticates against the old values.",(0,r.jsx)(n.br,{}),"\nSolution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under ",(0,r.jsx)(n.code,{children:"auth.keycloak"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"how-it-works",children:"How it Works"}),"\n",(0,r.jsx)(n.p,{children:"If you're a developer or contributor looking to understand or make changes to Dashy's Keycloak implementation, the following outlines how it's wired together."}),"\n",(0,r.jsxs)(n.p,{children:["The same OIDC pipeline backs both Keycloak and generic OIDC providers. The only Keycloak-specific code is the SPA adapter, which uses ",(0,r.jsx)(n.code,{children:"keycloak-js"})," so it can take advantage of ",(0,r.jsx)(n.code,{children:"check-sso"})," and silent token renewal."]}),"\n",(0,r.jsx)(n.h3,{id:"client-side",children:"Client side"}),"\n",(0,r.jsxs)(n.p,{children:["Boot starts in ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/main.js",children:(0,r.jsx)(n.code,{children:"src/main.js"})}),". After the initial ",(0,r.jsx)(n.code,{children:"/conf.yml"})," fetch parses the auth block, ",(0,r.jsx)(n.code,{children:"isKeycloakEnabled()"})," decides whether to lazily import ",(0,r.jsx)(n.code,{children:"keycloak-js"})," and call ",(0,r.jsx)(n.code,{children:"initKeycloakAuth()"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/KeycloakAuth.js",children:(0,r.jsx)(n.code,{children:"src/utils/auth/KeycloakAuth.js"})})," wraps the adapter. On load it calls ",(0,r.jsx)(n.code,{children:"keycloakClient.init({ onLoad: 'check-sso', responseMode: 'query' })"}),". If a Keycloak session already exists the user is silently authenticated; otherwise the SPA redirects to the login page with PKCE. On return, ",(0,r.jsx)(n.code,{children:"storeKeycloakInfo()"})," persists the raw ",(0,r.jsx)(n.code,{children:"id_token"}),", the user's ",(0,r.jsx)(n.code,{children:"groups"})," and ",(0,r.jsx)(n.code,{children:"roles"}),", ",(0,r.jsx)(n.code,{children:"preferred_username"}),", and a derived ",(0,r.jsx)(n.code,{children:"isAdmin"})," flag to localStorage, then hard-redirects to ",(0,r.jsx)(n.code,{children:"/"})," so the SPA boots a second time with the Bearer token in place."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/getApiAuthHeader.js",children:(0,r.jsx)(n.code,{children:"src/utils/auth/getApiAuthHeader.js"})})," builds the Authorization header for every internal API call. It does a client-side ",(0,r.jsx)(n.code,{children:"exp"})," check and returns ",(0,r.jsx)(n.code,{children:"null"})," for missing or expired tokens, so the next request triggers a fresh login rather than a 401."]}),"\n",(0,r.jsxs)(n.p,{children:["The localStorage keys (",(0,r.jsx)(n.code,{children:"ID_TOKEN"}),", ",(0,r.jsx)(n.code,{children:"KEYCLOAK_INFO"}),", ",(0,r.jsx)(n.code,{children:"USERNAME"}),", ",(0,r.jsx)(n.code,{children:"ISADMIN"}),") live in ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/config/defaults.js",children:(0,r.jsx)(n.code,{children:"src/utils/config/defaults.js"})}),". ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/IsVisibleToUser.js",children:(0,r.jsx)(n.code,{children:"src/utils/IsVisibleToUser.js"})})," reads ",(0,r.jsx)(n.code,{children:"KEYCLOAK_INFO"})," when evaluating ",(0,r.jsx)(n.code,{children:"showForKeycloakUsers"})," and ",(0,r.jsx)(n.code,{children:"hideForKeycloakUsers"})," rules."]}),"\n",(0,r.jsx)(n.h3,{id:"server-side",children:"Server side"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/auth-oidc.js",children:(0,r.jsx)(n.code,{children:"services/auth-oidc.js"})})," contains the entire server-side auth surface, in five small pieces:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"loadOidcSettings()"})," reads ",(0,r.jsx)(n.code,{children:"auth.keycloak"})," (or ",(0,r.jsx)(n.code,{children:"auth.oidc"}),") at boot and returns a normalised ",(0,r.jsx)(n.code,{children:"{ issuer, clientId, adminGroup, adminRole }"}),". For Keycloak the issuer is ",(0,r.jsx)(n.code,{children:"/realms/"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"createOidcMiddleware()"})," returns a Connect middleware. Permissive on no-token requests so the SPA can bootstrap; otherwise it verifies the Bearer token against the realm's JWKS using ",(0,r.jsx)(n.a,{href:"https://github.com/panva/jose",children:(0,r.jsx)(n.code,{children:"jose"})}),". Checks cover signature, issuer (against the canonical value from the discovery doc), audience (must equal ",(0,r.jsx)(n.code,{children:"clientId"}),"), and expiry, with a 30-second clock-skew tolerance. Sets ",(0,r.jsx)(n.code,{children:"req.auth = { user, isAdmin, claims }"})," on success, ",(0,r.jsx)(n.code,{children:"401"})," on failure."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"getIssuerContext()"})," lazily fetches ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"})," on first use and wraps ",(0,r.jsx)(n.code,{children:"jwks_uri"})," in ",(0,r.jsx)(n.code,{children:"createRemoteJWKSet"}),", which handles JWKS caching and on-demand key rotation. The result is memoised per-issuer for the life of the process."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"deriveIsAdmin()"})," checks the token's ",(0,r.jsx)(n.code,{children:"groups"})," claim against ",(0,r.jsx)(n.code,{children:"adminGroup"}),", and the union of ",(0,r.jsx)(n.code,{children:"realm_access.roles"})," and ",(0,r.jsx)(n.code,{children:"resource_access..roles"})," against ",(0,r.jsx)(n.code,{children:"adminRole"}),". Either match returns true."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"maybeBootstrapConfig()"})," is the stripped-response helper. When auth is configured, guest access is off, and an unauthenticated request hits the root ",(0,r.jsx)(n.code,{children:"/conf.yml"}),", it returns a minimal copy with only ",(0,r.jsx)(n.code,{children:"appConfig.auth"}),", ",(0,r.jsx)(n.code,{children:"appConfig.enableServiceWorker"}),", and a ",(0,r.jsx)(n.code,{children:"pageInfo.title"})," of ",(0,r.jsx)(n.code,{children:"Login | "}),". Sections, items, hostnames and any other secrets never leave the server."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/app.js",children:(0,r.jsx)(n.code,{children:"services/app.js"})})," wires it all together. The middleware mounts as ",(0,r.jsx)(n.code,{children:"protectConfig"})," in front of every YAML route and config-mutating route. The ",(0,r.jsx)(n.code,{children:"/*.yml"})," handler sets ",(0,r.jsx)(n.code,{children:"Cache-Control: private, no-store"})," and ",(0,r.jsx)(n.code,{children:"Vary: Authorization"})," whenever auth is configured (so intermediate caches can never mix auth states), then calls ",(0,r.jsx)(n.code,{children:"maybeBootstrapConfig"}),"; a stripped result is sent as-is, otherwise ",(0,r.jsx)(n.code,{children:"res.sendFile"})," serves the full file. ",(0,r.jsx)(n.code,{children:"POST /config-manager/save"})," is additionally guarded by ",(0,r.jsx)(n.code,{children:"requireAdmin"}),", which returns ",(0,r.jsx)(n.code,{children:"401"})," if ",(0,r.jsx)(n.code,{children:"req.auth"})," is unset and ",(0,r.jsx)(n.code,{children:"403"})," if ",(0,r.jsx)(n.code,{children:"req.auth.isAdmin"})," is false."]}),"\n",(0,r.jsx)(n.h3,{id:"why-the-mapper-matters",children:"Why the mapper matters"}),"\n",(0,r.jsxs)(n.p,{children:["The server's admin check reads from the ",(0,r.jsx)(n.code,{children:"id_token"})," only. Keycloak's default mapper adds realm roles to the access token but not the id token, so without the Step 2 ",(0,r.jsx)(n.em,{children:"Add to ID token"})," toggle, ",(0,r.jsx)(n.code,{children:"realm_access.roles"})," is absent and every user is treated as non-admin. The same applies to ",(0,r.jsx)(n.code,{children:"groups"})," if you use ",(0,r.jsx)(n.code,{children:"adminGroup"}),' instead. This is the single most common cause of "logged in fine, but can\'t save changes".']}),"\n",(0,r.jsx)(n.h3,{id:"visual-overview",children:"Visual Overview"}),"\n",(0,r.jsxs)(n.details,{children:["\n",(0,r.jsx)(n.summary,{children:"End-to-end authentication flow"}),"\n",(0,r.jsx)(n.mermaid,{value:"sequenceDiagram\n autonumber\n actor User\n participant Browser as Browser (Dashy SPA)\n participant Dashy as Dashy Server\n participant KC as Keycloak\n\n Note over Browser,Dashy: 1. Bootstrap (no token yet)\n User->>Browser: Open Dashy\n Browser->>Dashy: GET /conf.yml\n Dashy--\x3e>Browser: 200 stripped conf
(auth block + minimal pageInfo)\n Browser->>Browser: Parse auth.keycloak\n\n Note over Browser,KC: 2. OIDC login (Authorization Code + PKCE)\n Browser->>KC: 302 /realms/dashy/protocol/openid-connect/auth
client_id, code_challenge, redirect_uri\n User->>KC: Submit credentials\n KC--\x3e>Browser: 302 back with ?code=AUTH_CODE\n Browser->>KC: POST /token (code + code_verifier)\n KC--\x3e>Browser: id_token + access_token + refresh_token\n Browser->>Browser: Store tokens in localStorage\n\n Note over Browser,Dashy: 3. Authenticated read\n Browser->>Dashy: GET /conf.yml
Authorization: Bearer id_token\n Dashy->>KC: Fetch discovery + JWKS
(lazy on first call, then cached)\n KC--\x3e>Dashy: openid-configuration + JWKS\n Dashy->>Dashy: Verify signature / issuer / audience / expiry\n Dashy--\x3e>Browser: 200 full conf.yml\n\n Note over Browser,Dashy: 4. Write request (admin only)\n Browser->>Dashy: POST /config-manager/save
Authorization: Bearer id_token\n Dashy->>Dashy: Verify token, derive isAdmin from
adminRole / adminGroup claims\n alt isAdmin\n Dashy--\x3e>Browser: 200 saved\n else not admin\n Dashy--\x3e>Browser: 403 Forbidden\n end"}),"\n"]}),"\n",(0,r.jsxs)(n.details,{children:["\n",(0,r.jsx)(n.summary,{children:"Server-side request handling"}),"\n",(0,r.jsx)(n.mermaid,{value:'flowchart TD\n Req([Incoming request to Dashy server])\n Req --\x3e Bearer{Authorization:
Bearer present?}\n\n Bearer -- No --\x3e NoAuth[req.auth unset
pass through]:::neutral\n Bearer -- Yes --\x3e Verify[/Verify JWT against cached JWKS:
signature \xb7 issuer \xb7 audience \xb7 expiry/]\n Verify --\x3e Valid{Valid?}\n Valid -- No --\x3e R401Bad[/401 Unauthorized/]:::err\n Valid -- Yes --\x3e SetAuth[req.auth = user + isAdmin
derived from claims]:::ok\n\n NoAuth --\x3e Route{Endpoint}\n SetAuth --\x3e Route\n\n Route -- "GET /conf.yml" --\x3e ConfGate{req.auth?}\n ConfGate -- No --\x3e Strip[/200 stripped conf
auth + minimal pageInfo/]:::ok\n ConfGate -- Yes --\x3e Full[/200 full conf.yml/]:::ok\n\n Route -- "POST /config-manager/save" --\x3e SaveGate{req.auth
and isAdmin?}\n SaveGate -- No req.auth --\x3e R401Save[/401 Unauthorized/]:::err\n SaveGate -- Not admin --\x3e R403[/403 Forbidden/]:::err\n SaveGate -- Admin --\x3e Saved[/200 saved/]:::ok\n\n classDef ok fill:#bbf7d0,stroke:#16a34a,color:#14532d\n classDef err fill:#fecaca,stroke:#dc2626,color:#7f1d1d\n classDef neutral fill:#dbeafe,stroke:#2563eb,color:#1e3a8a'}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>l,x:()=>t});var i=s(96540);const r={},o=i.createContext(r);function l(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/87124b4e.6737270d.js b/assets/js/87124b4e.a107ba1f.js similarity index 99% rename from assets/js/87124b4e.6737270d.js rename to assets/js/87124b4e.a107ba1f.js index 15f65042..1a332a4f 100644 --- a/assets/js/87124b4e.6737270d.js +++ b/assets/js/87124b4e.a107ba1f.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[8278],{98030(e,t,s){s.r(t),s.d(t,{assets:()=>c,contentTitle:()=>a,default:()=>d,frontMatter:()=>r,metadata:()=>n,toc:()=>l});const n=JSON.parse('{"id":"status-indicators","title":"Status Indicators","description":"Dashy has an optional feature that can display a small icon next to each of your running service or host, indicating it\'s current status (operation or online). This can be useful if you are using Dashy as your homelab\'s start page, as it gives you an overview of the health of each of your running services and hosts. The status and ping features will show response time, response code, online / offline check and if applicable, a relevant error message.","source":"@site/docs/status-indicators.md","sourceDirName":".","slug":"/status-indicators","permalink":"/docs/status-indicators","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/status-indicators.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Theming","permalink":"/docs/theming"},"next":{"title":"Authentication","permalink":"/docs/authentication"}}');var i=s(74848),o=s(28453);const r={},a="Status Indicators",c={},l=[{value:"Enabling Status Indicators",id:"enabling-status-indicators",level:2},{value:"Continuous Checking",id:"continuous-checking",level:2},{value:"Using a Different Endpoint",id:"using-a-different-endpoint",level:2},{value:"Setting Custom Headers",id:"setting-custom-headers",level:2},{value:"Disabling Security",id:"disabling-security",level:2},{value:"Allowing Alternative Status Codes",id:"allowing-alternative-status-codes",level:2},{value:"Troubleshooting Failing Status Checks",id:"troubleshooting-failing-status-checks",level:2},{value:"Color-Blind Accessibility",id:"color-blind-accessibility",level:2},{value:"How it Works",id:"how-it-works",level:2}];function h(e){const t={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",img:"img",p:"p",pre:"pre",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.header,{children:(0,i.jsx)(t.h1,{id:"status-indicators",children:"Status Indicators"})}),"\n",(0,i.jsx)(t.p,{children:"Dashy has an optional feature that can display a small icon next to each of your running service or host, indicating it's current status (operation or online). This can be useful if you are using Dashy as your homelab's start page, as it gives you an overview of the health of each of your running services and hosts. The status and ping features will show response time, response code, online / offline check and if applicable, a relevant error message."}),"\n ",(0,i.jsx)(t.img,{width:"800",src:"/docs/assets/status-check-demo.gif"}),"\n",(0,i.jsx)(t.h2,{id:"enabling-status-indicators",children:"Enabling Status Indicators"}),"\n",(0,i.jsxs)(t.p,{children:["By default, this feature is off. If you do not want this feature, just don't add the ",(0,i.jsx)(t.code,{children:"statusCheck"})," or ",(0,i.jsx)(t.code,{children:"pingCheckEnabled"})," to your conf.yml file, then no requests will be made."]}),"\n",(0,i.jsxs)(t.p,{children:["To enable status checks, you can either turn it on for all items, by setting ",(0,i.jsx)(t.code,{children:"appConfig.statusCheck: true"}),", like:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-yaml",children:"appConfig:\n statusCheck: true\n"})}),"\n",(0,i.jsxs)(t.p,{children:["To enable ping checks, you can either turn it on for all items, by setting ",(0,i.jsx)(t.code,{children:"appConfig.pingCheckEnabled: true"}),", like:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-yaml",children:"appConfig:\n pingCheckEnabled: true\n"})}),"\n",(0,i.jsxs)(t.p,{children:["Or you can enable/disable it on a per-item basis, with the ",(0,i.jsx)(t.code,{children:"item[n].statusCheck"})," and ",(0,i.jsx)(t.code,{children:"item[n].pingCheckEnabled"})," attributes"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-yaml",children:"sections:\n- name: Firewall\n items:\n - title: OPNsense\n description: Firewall Central Management\n icon: networking/opnsense.png\n url: https://192.168.1.1\n statusCheck: false\n - title: MalTrail\n description: Malicious traffic detection system\n icon: networking/maltrail.png\n url: http://192.168.1.1:8338\n statusCheck: true\n - title: Ntopng\n description: Network traffic probe and network use monitor\n icon: networking/ntop.png\n url: http://192.168.1.1:3001\n statusCheck: true\n pingCheckEnabled: true\n pingCheckHost: 192.168.1.1\n"})}),"\n",(0,i.jsx)(t.h2,{id:"continuous-checking",children:"Continuous Checking"}),"\n",(0,i.jsxs)(t.p,{children:["By default, with status indicators enabled Dashy will check an application or host status on page load, and will not keep indicators updated. This is usually the desirable behavior. However, if you do want the status indicators to continue to poll your running services or hosts, this can be enabled by setting the ",(0,i.jsx)(t.code,{children:"statusCheckInterval"})," or ",(0,i.jsx)(t.code,{children:"pingCheckInterval"})," attributes. Here you define an interval as an integer in seconds, and Dashy will poll your apps or hosts every x seconds. Note that if this number is very low (below 5 seconds), you may notice the app running slightly slower."]}),"\n",(0,i.jsx)(t.p,{children:"The following example, will instruct Dashy to continuously check the status of your services and hosts every 20 seconds."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-text",children:"appConfig:\n statusCheck: true\n statusCheckInterval: 20\n pingCheckInterval: 5\n"})}),"\n",(0,i.jsx)(t.h2,{id:"using-a-different-endpoint",children:"Using a Different Endpoint"}),"\n",(0,i.jsx)(t.p,{children:"By default, the status checker will use the URL of each application being checked. In some situations, you may want to use a different endpoint for status checking. Similarly, some services provide a dedicated path for uptime monitoring."}),"\n",(0,i.jsxs)(t.p,{children:["You can set the ",(0,i.jsx)(t.code,{children:"statusCheckUrl"})," property on any given item in order to do this. The status checker will then ping that endpoint, instead of the apps main ",(0,i.jsx)(t.code,{children:"url"})," property."]}),"\n",(0,i.jsx)(t.h2,{id:"setting-custom-headers",children:"Setting Custom Headers"}),"\n",(0,i.jsxs)(t.p,{children:["If your service is responding with an error, despite being up and running, it is most likely because custom headers for authentication, authorization or encoding are required. You can define these headers under the ",(0,i.jsx)(t.code,{children:"statusCheckHeaders"})," property for any service. It should be defined as an object format, with the name of header as the key, and header content as the value.\nFor example, ",(0,i.jsx)(t.code,{children:"statusCheckHeaders: { 'X-Custom-Header': 'foobar' }"})]}),"\n",(0,i.jsx)(t.h2,{id:"disabling-security",children:"Disabling Security"}),"\n",(0,i.jsxs)(t.p,{children:["By default, (if you're using HTTPS) any requests to insecure or non-HTTPS content will be blocked. This will cause the status check to fail. If you trust the endpoint (e.g. you're self-hosting it), then you can disable this security measure for an individual item. This is done by setting ",(0,i.jsx)(t.code,{children:"statusCheckAllowInsecure: true"})]}),"\n",(0,i.jsx)(t.h2,{id:"allowing-alternative-status-codes",children:"Allowing Alternative Status Codes"}),"\n",(0,i.jsxs)(t.p,{children:["If you expect your service to return a status code that is not in the 2XX range, and still want the indicator to be green, then you can specify an expected status code under ",(0,i.jsx)(t.code,{children:"statusCheckAcceptCodes"})," for a given item. For example, ",(0,i.jsx)(t.code,{children:"statusCheckAcceptCodes: '403,418'"})]}),"\n",(0,i.jsx)(t.h2,{id:"troubleshooting-failing-status-checks",children:"Troubleshooting Failing Status Checks"}),"\n",(0,i.jsx)(t.p,{children:"If you're using status checks, and despite a given service being online, the check is displaying an error, there are a couple of things you can look at:"}),"\n",(0,i.jsxs)(t.p,{children:["If your service requires requests to include any authorization in the headers, then use the ",(0,i.jsx)(t.code,{children:"statusCheckHeaders"})," property, as described ",(0,i.jsx)(t.a,{href:"#setting-custom-headers",children:"above"}),"."]}),"\n",(0,i.jsxs)(t.p,{children:["If you are still having issues, it may be because your target application is blocking requests from Dashy's IP. This is a ",(0,i.jsx)(t.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"CORS error"}),", and can be fixed by setting the headers on your target app, to include:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-text",children:"Access-Control-Allow-Origin: https://location-of-dashy/\nVary: Origin\n"})}),"\n",(0,i.jsxs)(t.p,{children:["If the URL you are checking has an unsigned certificate, or is not using HTTPS, then you may need to disable the rejection of insecure requests. This can be done by setting ",(0,i.jsx)(t.code,{children:"statusCheckAllowInsecure"})," to true for a given item."]}),"\n",(0,i.jsxs)(t.p,{children:["If your service is online, but responds with a status code that is not in the 2xx range, then you can use ",(0,i.jsx)(t.code,{children:"statusCheckAcceptCodes"})," to set an accepted status code."]}),"\n",(0,i.jsxs)(t.p,{children:["If you get an error, like ",(0,i.jsx)(t.code,{children:"Service Unavailable: Server resulted in a fatal error"}),", even when it's definitely online, this is most likely caused by missing the protocol. Don't forget to include ",(0,i.jsx)(t.code,{children:"https://"})," (or whatever protocol) before the URL, and ensure that if needed, you've specified the port."]}),"\n",(0,i.jsxs)(t.p,{children:["Running Dashy in HOST network mode, instead of BRIDGE will allow status check access to other services in HOST mode. For more info, see ",(0,i.jsx)(t.a,{href:"https://github.com/Lissy93/dashy/discussions/445",children:"#445"}),"."]}),"\n",(0,i.jsx)(t.p,{children:"If you have firewall rules configured, then ensure that they don't prevent Dashy from making requests to the other services you are trying to access."}),"\n",(0,i.jsxs)(t.p,{children:["Currently, the status check needs a page to be rendered, so if this URL in your browser does not return anything, then status checks will not work. This may be modified in the future, but in the meantime, a fix would be to make your own status service, which just checks if your app responds with whatever code you'd like, and then return a 200 plus renders an arbitrary message. Then just point ",(0,i.jsx)(t.code,{children:"statusCheckUrl"})," to your custom page."]}),"\n",(0,i.jsxs)(t.p,{children:["For further troubleshooting, use an application like ",(0,i.jsx)(t.a,{href:"https://postman.com",children:"Postman"})," to diagnose the issue. Set the parameter to ",(0,i.jsx)(t.code,{children:"GET"}),", and then make a call to: ",(0,i.jsx)(t.code,{children:"https://[url-of-dashy]/status-check/?&url=[service-url]"}),". Where the service URL must have first been encoded (e.g. with ",(0,i.jsx)(t.code,{children:"encodeURIComponent()"})," or ",(0,i.jsx)(t.a,{href:"https://www.urlencoder.io/",children:"urlencoder.io"}),")"]}),"\n",(0,i.jsx)(t.p,{children:"If you're serving Dashy though a CDN, instead of using the Node server or Docker image, then the Node endpoint that makes requests will not be available to you, and all requests will fail. A workaround for this may be implemented in the future, but in the meantime, your only option is to use the Docker or Node deployment method."}),"\n",(0,i.jsx)(t.h2,{id:"color-blind-accessibility",children:"Color-Blind Accessibility"}),"\n",(0,i.jsxs)(t.p,{children:["The default indicators rely on color alone to illustrate status (green, yellow, red and grey). For greater visual distinction and colorblind support, the accessibility mode will use shapes instead of dots to show status. This can be enabled by setting ",(0,i.jsx)(t.code,{children:"appConfig.statusCheckAccessibility: true"})," or ",(0,i.jsx)(t.code,{children:"appConfig.pingCheckAccessibility: true"}),"."]}),"\n",(0,i.jsx)(t.p,{children:"By default, this will be: circle (online), square (offline), triangle (checking) and diamond (timed out)."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-yaml",children:"appConfig:\n statusCheck: true\n statusCheckAccessibility: true\n pingCheckAccessibility: true\n"})}),"\n",(0,i.jsxs)(t.p,{children:["Alternatively, you can set your own colors for status indicators, with these variables: ",(0,i.jsx)(t.code,{children:"--success"}),", ",(0,i.jsx)(t.code,{children:"--warning"}),", ",(0,i.jsx)(t.code,{children:"--danger"}),", see: ",(0,i.jsx)(t.a,{href:"/docs/theming#modifying-theme-colors",children:"modifying theme colors"}),"."]}),"\n",(0,i.jsx)(t.h2,{id:"how-it-works",children:"How it Works"}),"\n",(0,i.jsxs)(t.p,{children:["When the app is loaded, if ",(0,i.jsx)(t.code,{children:"appConfig.statusCheck: true"})," or ",(0,i.jsx)(t.code,{children:"appConfig.pingCheckEnabled: true"})," are set, or if any items have the ",(0,i.jsx)(t.code,{children:"statusCheck: true"})," or ",(0,i.jsx)(t.code,{children:"pingCheckEnabled: true"})," enabled, then Dashy will make a request, to ",(0,i.jsx)(t.code,{children:"https://[your-host-name]/status-check?&url=[address-or-servce]"})," or ",(0,i.jsx)(t.code,{children:"https://[your-host-name]/ping-check?&host=[hostname-or-ip]"})," (may also include GET params for headers and the secure flag for status check or ping count and timeout GET params for ping checks), which in turn will ping that running service or host, and respond with a status code. Response time is calculated from the difference between start and end time of the request for the status check of on the return of the ping command for the ping check."]}),"\n",(0,i.jsx)(t.p,{children:"When the response completes, an indicator will display next to each item. The color denotes the status: Yellow while waiting for the response to return, green if request was successful, red if it failed, and grey if it was unable to make the request all together."}),"\n",(0,i.jsxs)(t.p,{children:["All requests are made straight from your server, there is no intermediary. So providing you are hosting Dashy yourself, and are checking the status of other self-hosted services, there shouldn't be any privacy concerns. Requests are made asynchronously, so this won't have any significant impact on page load speeds. However recurring requests (using ",(0,i.jsx)(t.code,{children:"statusCheckInterval"})," or ",(0,i.jsx)(t.code,{children:"pingCheckInterval"}),") may run more slowly if the interval between requests is very short."]})]})}function d(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},28453(e,t,s){s.d(t,{R:()=>r,x:()=>a});var n=s(96540);const i={},o=n.createContext(i);function r(e){const t=n.useContext(o);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),n.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[8278],{98030(e,t,s){s.r(t),s.d(t,{assets:()=>c,contentTitle:()=>a,default:()=>d,frontMatter:()=>r,metadata:()=>n,toc:()=>l});const n=JSON.parse('{"id":"status-indicators","title":"Status Indicators","description":"Dashy has an optional feature that can display a small icon next to each of your running service or host, indicating it\'s current status (operation or online). This can be useful if you are using Dashy as your homelab\'s start page, as it gives you an overview of the health of each of your running services and hosts. The status and ping features will show response time, response code, online / offline check and if applicable, a relevant error message.","source":"@site/docs/status-indicators.md","sourceDirName":".","slug":"/status-indicators","permalink":"/docs/status-indicators","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/status-indicators.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Theming","permalink":"/docs/theming"},"next":{"title":"Authentication","permalink":"/docs/authentication"}}');var i=s(74848),o=s(28453);const r={},a="Status Indicators",c={},l=[{value:"Enabling Status Indicators",id:"enabling-status-indicators",level:2},{value:"Continuous Checking",id:"continuous-checking",level:2},{value:"Using a Different Endpoint",id:"using-a-different-endpoint",level:2},{value:"Setting Custom Headers",id:"setting-custom-headers",level:2},{value:"Disabling Security",id:"disabling-security",level:2},{value:"Allowing Alternative Status Codes",id:"allowing-alternative-status-codes",level:2},{value:"Troubleshooting Failing Status Checks",id:"troubleshooting-failing-status-checks",level:2},{value:"Color-Blind Accessibility",id:"color-blind-accessibility",level:2},{value:"How it Works",id:"how-it-works",level:2}];function h(e){const t={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",img:"img",p:"p",pre:"pre",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.header,{children:(0,i.jsx)(t.h1,{id:"status-indicators",children:"Status Indicators"})}),"\n",(0,i.jsx)(t.p,{children:"Dashy has an optional feature that can display a small icon next to each of your running service or host, indicating it's current status (operation or online). This can be useful if you are using Dashy as your homelab's start page, as it gives you an overview of the health of each of your running services and hosts. The status and ping features will show response time, response code, online / offline check and if applicable, a relevant error message."}),"\n ",(0,i.jsx)(t.img,{width:"800",src:"/docs/assets/status-check-demo.gif"}),"\n",(0,i.jsx)(t.h2,{id:"enabling-status-indicators",children:"Enabling Status Indicators"}),"\n",(0,i.jsxs)(t.p,{children:["By default, this feature is off. If you do not want this feature, just don't add the ",(0,i.jsx)(t.code,{children:"statusCheck"})," or ",(0,i.jsx)(t.code,{children:"pingCheckEnabled"})," to your conf.yml file, then no requests will be made."]}),"\n",(0,i.jsxs)(t.p,{children:["To enable status checks, you can either turn it on for all items, by setting ",(0,i.jsx)(t.code,{children:"appConfig.statusCheck: true"}),", like:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-yaml",children:"appConfig:\n statusCheck: true\n"})}),"\n",(0,i.jsxs)(t.p,{children:["To enable ping checks, you can either turn it on for all items, by setting ",(0,i.jsx)(t.code,{children:"appConfig.pingCheckEnabled: true"}),", like:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-yaml",children:"appConfig:\n pingCheckEnabled: true\n"})}),"\n",(0,i.jsxs)(t.p,{children:["Or you can enable/disable it on a per-item basis, with the ",(0,i.jsx)(t.code,{children:"item[n].statusCheck"})," and ",(0,i.jsx)(t.code,{children:"item[n].pingCheckEnabled"})," attributes"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-yaml",children:"sections:\n- name: Firewall\n items:\n - title: OPNsense\n description: Firewall Central Management\n icon: networking/opnsense.png\n url: https://192.168.1.1\n statusCheck: false\n - title: MalTrail\n description: Malicious traffic detection system\n icon: networking/maltrail.png\n url: http://192.168.1.1:8338\n statusCheck: true\n - title: Ntopng\n description: Network traffic probe and network use monitor\n icon: networking/ntop.png\n url: http://192.168.1.1:3001\n statusCheck: true\n pingCheckEnabled: true\n pingCheckHost: 192.168.1.1\n"})}),"\n",(0,i.jsx)(t.h2,{id:"continuous-checking",children:"Continuous Checking"}),"\n",(0,i.jsxs)(t.p,{children:["By default, with status indicators enabled Dashy will check an application or host status on page load, and will not keep indicators updated. This is usually the desirable behavior. However, if you do want the status indicators to continue to poll your running services or hosts, this can be enabled by setting the ",(0,i.jsx)(t.code,{children:"statusCheckInterval"})," or ",(0,i.jsx)(t.code,{children:"pingCheckInterval"})," attributes. Here you define an interval as an integer in seconds, and Dashy will poll your apps or hosts every x seconds. Note that if this number is very low (below 5 seconds), you may notice the app running slightly slower."]}),"\n",(0,i.jsx)(t.p,{children:"The following example, will instruct Dashy to continuously check the status of your services and hosts every 20 seconds."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-text",children:"appConfig:\n statusCheck: true\n statusCheckInterval: 20\n pingCheckInterval: 5\n"})}),"\n",(0,i.jsx)(t.h2,{id:"using-a-different-endpoint",children:"Using a Different Endpoint"}),"\n",(0,i.jsx)(t.p,{children:"By default, the status checker will use the URL of each application being checked. In some situations, you may want to use a different endpoint for status checking. Similarly, some services provide a dedicated path for uptime monitoring."}),"\n",(0,i.jsxs)(t.p,{children:["You can set the ",(0,i.jsx)(t.code,{children:"statusCheckUrl"})," property on any given item in order to do this. The status checker will then ping that endpoint, instead of the apps main ",(0,i.jsx)(t.code,{children:"url"})," property."]}),"\n",(0,i.jsx)(t.h2,{id:"setting-custom-headers",children:"Setting Custom Headers"}),"\n",(0,i.jsxs)(t.p,{children:["If your service is responding with an error, despite being up and running, it is most likely because custom headers for authentication, authorization or encoding are required. You can define these headers under the ",(0,i.jsx)(t.code,{children:"statusCheckHeaders"})," property for any service. It should be defined as an object format, with the name of header as the key, and header content as the value.\nFor example, ",(0,i.jsx)(t.code,{children:"statusCheckHeaders: { 'X-Custom-Header': 'foobar' }"})]}),"\n",(0,i.jsx)(t.h2,{id:"disabling-security",children:"Disabling Security"}),"\n",(0,i.jsxs)(t.p,{children:["By default, (if you're using HTTPS) any requests to insecure or non-HTTPS content will be blocked. This will cause the status check to fail. If you trust the endpoint (e.g. you're self-hosting it), then you can disable this security measure for an individual item. This is done by setting ",(0,i.jsx)(t.code,{children:"statusCheckAllowInsecure: true"})]}),"\n",(0,i.jsx)(t.h2,{id:"allowing-alternative-status-codes",children:"Allowing Alternative Status Codes"}),"\n",(0,i.jsxs)(t.p,{children:["If you expect your service to return a status code that is not in the 2XX range, and still want the indicator to be green, then you can specify an expected status code under ",(0,i.jsx)(t.code,{children:"statusCheckAcceptCodes"})," for a given item. For example, ",(0,i.jsx)(t.code,{children:"statusCheckAcceptCodes: '403,418'"})]}),"\n",(0,i.jsx)(t.h2,{id:"troubleshooting-failing-status-checks",children:"Troubleshooting Failing Status Checks"}),"\n",(0,i.jsx)(t.p,{children:"If you're using status checks, and despite a given service being online, the check is displaying an error, there are a couple of things you can look at:"}),"\n",(0,i.jsxs)(t.p,{children:["If your service requires requests to include any authorization in the headers, then use the ",(0,i.jsx)(t.code,{children:"statusCheckHeaders"})," property, as described ",(0,i.jsx)(t.a,{href:"#setting-custom-headers",children:"above"}),"."]}),"\n",(0,i.jsxs)(t.p,{children:["If you are still having issues, it may be because your target application is blocking requests from Dashy's IP. This is a ",(0,i.jsx)(t.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"CORS error"}),", and can be fixed by setting the headers on your target app, to include:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-text",children:"Access-Control-Allow-Origin: https://location-of-dashy/\nVary: Origin\n"})}),"\n",(0,i.jsxs)(t.p,{children:["If the URL you are checking has an unsigned certificate, or is not using HTTPS, then you may need to disable the rejection of insecure requests. This can be done by setting ",(0,i.jsx)(t.code,{children:"statusCheckAllowInsecure"})," to true for a given item."]}),"\n",(0,i.jsxs)(t.p,{children:["If your service is online, but responds with a status code that is not in the 2xx range, then you can use ",(0,i.jsx)(t.code,{children:"statusCheckAcceptCodes"})," to set an accepted status code."]}),"\n",(0,i.jsxs)(t.p,{children:["If you get an error, like ",(0,i.jsx)(t.code,{children:"Service Unavailable: Server resulted in a fatal error"}),", even when it's definitely online, this is most likely caused by missing the protocol. Don't forget to include ",(0,i.jsx)(t.code,{children:"https://"})," (or whatever protocol) before the URL, and ensure that if needed, you've specified the port."]}),"\n",(0,i.jsxs)(t.p,{children:["Running Dashy in HOST network mode, instead of BRIDGE will allow status check access to other services in HOST mode. For more info, see ",(0,i.jsx)(t.a,{href:"https://github.com/Lissy93/dashy/discussions/445",children:"#445"}),"."]}),"\n",(0,i.jsx)(t.p,{children:"If you have firewall rules configured, then ensure that they don't prevent Dashy from making requests to the other services you are trying to access."}),"\n",(0,i.jsxs)(t.p,{children:["Currently, the status check needs a page to be rendered, so if this URL in your browser does not return anything, then status checks will not work. This may be modified in the future, but in the meantime, a fix would be to make your own status service, which just checks if your app responds with whatever code you'd like, and then return a 200 plus renders an arbitrary message. Then just point ",(0,i.jsx)(t.code,{children:"statusCheckUrl"})," to your custom page."]}),"\n",(0,i.jsxs)(t.p,{children:["For further troubleshooting, use an application like ",(0,i.jsx)(t.a,{href:"https://postman.com",children:"Postman"})," to diagnose the issue. Set the parameter to ",(0,i.jsx)(t.code,{children:"GET"}),", and then make a call to: ",(0,i.jsx)(t.code,{children:"https://[url-of-dashy]/status-check/?&url=[service-url]"}),". Where the service URL must have first been encoded (e.g. with ",(0,i.jsx)(t.code,{children:"encodeURIComponent()"})," or ",(0,i.jsx)(t.a,{href:"https://www.urlencoder.io/",children:"urlencoder.io"}),")"]}),"\n",(0,i.jsx)(t.p,{children:"If you're serving Dashy though a CDN, instead of using the Node server or Docker image, then the Node endpoint that makes requests will not be available to you, and all requests will fail. A workaround for this may be implemented in the future, but in the meantime, your only option is to use the Docker or Node deployment method."}),"\n",(0,i.jsx)(t.h2,{id:"color-blind-accessibility",children:"Color-Blind Accessibility"}),"\n",(0,i.jsxs)(t.p,{children:["The default indicators rely on color alone to illustrate status (green, yellow, red and grey). For greater visual distinction and colorblind support, the accessibility mode will use shapes instead of dots to show status. This can be enabled by setting ",(0,i.jsx)(t.code,{children:"appConfig.statusCheckAccessibility: true"})," or ",(0,i.jsx)(t.code,{children:"appConfig.pingCheckAccessibility: true"}),"."]}),"\n",(0,i.jsx)(t.p,{children:"By default, this will be: circle (online), square (offline), triangle (checking) and diamond (timed out)."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-yaml",children:"appConfig:\n statusCheck: true\n statusCheckAccessibility: true\n pingCheckAccessibility: true\n"})}),"\n",(0,i.jsxs)(t.p,{children:["Alternatively, you can set your own colors for status indicators, with these variables: ",(0,i.jsx)(t.code,{children:"--success"}),", ",(0,i.jsx)(t.code,{children:"--warning"}),", ",(0,i.jsx)(t.code,{children:"--danger"}),", see: ",(0,i.jsx)(t.a,{href:"/docs/theming#modifying-theme-colors",children:"modifying theme colors"}),"."]}),"\n",(0,i.jsx)(t.h2,{id:"how-it-works",children:"How it Works"}),"\n",(0,i.jsxs)(t.p,{children:["When the app is loaded, if ",(0,i.jsx)(t.code,{children:"appConfig.statusCheck: true"})," or ",(0,i.jsx)(t.code,{children:"appConfig.pingCheckEnabled: true"})," are set, or if any items have the ",(0,i.jsx)(t.code,{children:"statusCheck: true"})," or ",(0,i.jsx)(t.code,{children:"pingCheckEnabled: true"})," enabled, then Dashy will make a request, to ",(0,i.jsx)(t.code,{children:"https://[your-host-name]/status-check?&url=[address-or-servce]"})," or ",(0,i.jsx)(t.code,{children:"https://[your-host-name]/ping-check?&host=[hostname-or-ip]"})," (may also include GET params for headers and the secure flag for status check or ping count and timeout GET params for ping checks), which in turn will ping that running service or host, and respond with a status code. Response time is calculated from the difference between start and end time of the request for the status check of on the return of the ping command for the ping check."]}),"\n",(0,i.jsx)(t.p,{children:"When the response completes, an indicator will display next to each item. The color denotes the status: Yellow while waiting for the response to return, green if request was successful, red if it failed, and grey if it was unable to make the request all together."}),"\n",(0,i.jsxs)(t.p,{children:["All requests are made straight from your server, there is no intermediary. So providing you are hosting Dashy yourself, and are checking the status of other self-hosted services, there shouldn't be any privacy concerns. Requests are made asynchronously, so this won't have any significant impact on page load speeds. However recurring requests (using ",(0,i.jsx)(t.code,{children:"statusCheckInterval"})," or ",(0,i.jsx)(t.code,{children:"pingCheckInterval"}),") may run more slowly if the interval between requests is very short."]})]})}function d(e={}){const{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},28453(e,t,s){s.d(t,{R:()=>r,x:()=>a});var n=s(96540);const i={},o=n.createContext(i);function r(e){const t=n.useContext(o);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),n.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/88ca5e38.6221b85a.js b/assets/js/88ca5e38.b0775334.js similarity index 99% rename from assets/js/88ca5e38.6221b85a.js rename to assets/js/88ca5e38.b0775334.js index a888dabc..cd972aa0 100644 --- a/assets/js/88ca5e38.6221b85a.js +++ b/assets/js/88ca5e38.b0775334.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[6608],{68375(e,s,n){n.r(s),n.d(s,{assets:()=>h,contentTitle:()=>a,default:()=>c,frontMatter:()=>o,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"authentication/built-in","title":"Built-In Auth","description":"Dashy ships with a simple username + password auth system baked in, as well as the option to enforce this server-side with standard HTTP auth.","source":"@site/docs/authentication/built-in.md","sourceDirName":"authentication","slug":"/authentication/built-in","permalink":"/docs/authentication/built-in","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/built-in.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Authentik OIDC","permalink":"/docs/authentication/authentik"},"next":{"title":"Cloudflare Tunnel","permalink":"/docs/authentication/cloudflare-tunnel"}}');var i=n(74848),t=n(28453);const o={},a="Built-In Auth",h={},d=[{value:"Contents",id:"contents",level:3},{value:"Quick Start",id:"quick-start",level:2},{value:"Defining Users",id:"defining-users",level:2},{value:"Generating a password hash",id:"generating-a-password-hash",level:3},{value:"Using an env var instead of an inline hash",id:"using-an-env-var-instead-of-an-inline-hash",level:3},{value:"Choosing a Protection Mode",id:"choosing-a-protection-mode",level:2},{value:"Client-side login",id:"client-side-login",level:3},{value:"Server-enforced via conf.yml users",id:"server-enforced-via-confyml-users",level:3},{value:"Static credentials (no Dashy login page)",id:"static-credentials-no-dashy-login-page",level:3},{value:"Sessions, Guest Access & Logout",id:"sessions-guest-access--logout",level:2},{value:"Guest access",id:"guest-access",level:3},{value:"User Roles & Visibility",id:"user-roles--visibility",level:2},{value:"Per-section and per-item visibility",id:"per-section-and-per-item-visibility",level:3},{value:"Auto-Authenticating the Frontend",id:"auto-authenticating-the-frontend",level:2},{value:"Troubleshooting common built-in auth issues",id:"troubleshooting-common-built-in-auth-issues",level:2},{value:"Restart and "Invalid user object" appears in the server logs",id:"restart-and-invalid-user-object-appears-in-the-server-logs",level:4},{value:"Login fails with the right password",id:"login-fails-with-the-right-password",level:4},{value:"Browser keeps prompting for basic auth on every page load",id:"browser-keeps-prompting-for-basic-auth-on-every-page-load",level:4},{value:"Set ENABLE_HTTP_AUTH=true and nothing changed",id:"set-enable_http_authtrue-and-nothing-changed",level:4},{value:"Got the warning "BASIC_AUTH env vars and appConfig.auth.users are both set"",id:"got-the-warning-basic_auth-env-vars-and-appconfigauthusers-are-both-set",level:4},{value:"Logged in as admin but Save Config is still disabled",id:"logged-in-as-admin-but-save-config-is-still-disabled",level:4},{value:"Guest can still see hidden items via /conf.yml",id:"guest-can-still-see-hidden-items-via-confyml",level:4},{value:"Want to keep passwords out of conf.yml entirely",id:"want-to-keep-passwords-out-of-confyml-entirely",level:4},{value:"Auth works locally but not behind a reverse proxy",id:"auth-works-locally-but-not-behind-a-reverse-proxy",level:4},{value:"Security Considerations",id:"security-considerations",level:2},{value:"How it Works",id:"how-it-works",level:2},{value:"The login flow (client-side mode)",id:"the-login-flow-client-side-mode",level:3},{value:"Server-enforced mode",id:"server-enforced-mode",level:3}];function l(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",br:"br",code:"code",details:"details",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",mermaid:"mermaid",ol:"ol",p:"p",pre:"pre",strong:"strong",summary:"summary",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(s.header,{children:(0,i.jsx)(s.h1,{id:"built-in-auth",children:"Built-In Auth"})}),"\n",(0,i.jsx)(s.p,{children:"Dashy ships with a simple username + password auth system baked in, as well as the option to enforce this server-side with standard HTTP auth."}),"\n",(0,i.jsxs)(s.p,{children:["This is the quickest way to setup a login screen, without the need to run any other auth services. No identity provider to run, no external dependencies, just a list of users in ",(0,i.jsx)(s.code,{children:"conf.yml"}),". Useful for keeping casual eyes of your dashboard within a LAN, but is probably not recommended if your instance is exposed to the public internet."]}),"\n",(0,i.jsxs)(s.p,{children:['This guide covers everything the built-in auth can do, from the lightest "show a login screen" mode to a server-enforced setup that 401s every unauthenticated request. If you need OIDC, SSO, or anything internet-facing, see the other guides in this folder. ',(0,i.jsx)(s.strong,{children:"Built-in auth is convenient, not hardened"}),"."]}),"\n",(0,i.jsx)(s.h3,{id:"contents",children:"Contents"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#quick-start",children:"Quick Start"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#defining-users",children:"Defining Users"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#choosing-a-protection-mode",children:"Choosing a Protection Mode"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#sessions-guest-access--logout",children:"Sessions, Guest Access & Logout"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#user-roles--visibility",children:"User Roles & Visibility"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#auto-authenticating-the-frontend",children:"Auto-Authenticating the Frontend"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#troubleshooting-common-built-in-auth-issues",children:"Troubleshooting"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#security-considerations",children:"Security Considerations"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#how-it-works",children:"How it Works"})}),"\n"]}),"\n",(0,i.jsx)(s.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,i.jsxs)(s.p,{children:["Add a single user to ",(0,i.jsx)(s.code,{children:"appConfig.auth.users"})," in ",(0,i.jsx)(s.code,{children:"conf.yml"}),", with a SHA-256 hash of your password:"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"appConfig:\n disableConfigurationForNonAdmin: true\n auth:\n users:\n - user: alicia\n hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5\n type: admin\n"})}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-env",children:"ENABLE_HTTP_AUTH=true\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Restart Dashy for changes to take effect. You'll be bounced to a login screen. Enter your username and the plaintext password that hashes to the value above (",(0,i.jsx)(s.code,{children:"12345"})," if you copy-pasted the example. Please change it)."]}),"\n",(0,i.jsx)(s.h2,{id:"defining-users",children:"Defining Users"}),"\n",(0,i.jsxs)(s.p,{children:[(0,i.jsx)(s.code,{children:"appConfig.auth.users"})," is a list of user records. Each entry has a username, a password (as a hash or via an env var), and an optional admin flag."]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"appConfig:\n auth:\n users:\n - user: alicia\n hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5\n type: admin\n - user: guest\n hash: EF797C8118F02DFB649607DD5D3F8C7623048C9C063D532CC95C5ED7A898A64F\n type: normal\n"})}),"\n",(0,i.jsx)(s.p,{children:"Field reference:"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"user"})," - Username. Lowercased on lookup, so casing doesn't matter at login"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"hash"})," - SHA-256 hash of the password, hex, 64 chars. Either upper or lower case works"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"type"})," - ",(0,i.jsx)(s.code,{children:"admin"})," or ",(0,i.jsx)(s.code,{children:"normal"}),". Defaults to ",(0,i.jsx)(s.code,{children:"normal"}),". Admins can save config changes; normal users cannot. See ",(0,i.jsx)(s.a,{href:"#user-roles--visibility",children:"User Roles & Visibility"})," for what else this gates"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"password"})," - Alternative to ",(0,i.jsx)(s.code,{children:"hash"}),". The name of a ",(0,i.jsx)(s.code,{children:"VITE_APP_*"})," environment variable holding the plaintext password. Dashy hashes the env var's value at runtime. This requires building the app from source (don't work with pre-built Docker image)"]}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["Each user needs ",(0,i.jsx)(s.code,{children:"user"})," plus exactly one of ",(0,i.jsx)(s.code,{children:"hash"})," or ",(0,i.jsx)(s.code,{children:"password"})," (not both!)"]}),"\n",(0,i.jsx)(s.h3,{id:"generating-a-password-hash",children:"Generating a password hash"}),"\n",(0,i.jsxs)(s.p,{children:["If you're using ",(0,i.jsx)(s.code,{children:"hash"}),", you need to create a SHA-256 hash ",(0,i.jsx)(s.a,{href:"https://en.wikipedia.org/wiki/Sha-256",children:"\u2197"})," of your password. Either run ",(0,i.jsx)(s.code,{children:'echo -n "my-super-secure-password" | sha256sum'}),", or use an online tool, such as ",(0,i.jsx)(s.a,{href:"https://emn178.github.io/online-tools/sha256.html",children:"this one"})," or ",(0,i.jsx)(s.a,{href:"https://gchq.github.io/CyberChef/",children:"CyberChef"})," (which can be self-hosted and run locally)."]}),"\n",(0,i.jsx)(s.h3,{id:"using-an-env-var-instead-of-an-inline-hash",children:"Using an env var instead of an inline hash"}),"\n",(0,i.jsxs)(s.p,{children:["If you'd rather not put hashed passwords to ",(0,i.jsx)(s.code,{children:"conf.yml"}),", point at an env var:"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"- user: alicia\n password: VITE_APP_ALICIA_PASSWORD\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Then run Dashy with ",(0,i.jsx)(s.code,{children:"VITE_APP_ALICIA_PASSWORD=supersecret"}),". Dashy hashes the value at login time and compares it to what the user types. The env var name must start with ",(0,i.jsx)(s.code,{children:"VITE_APP_"}),"; anything else is rejected."]}),"\n",(0,i.jsx)(s.admonition,{type:"warning",children:(0,i.jsxs)(s.p,{children:["Caveat: env-var passwords only resolve in the browser if the variable was set when the Dashy bundle was ",(0,i.jsx)(s.em,{children:"built"}),". The official Docker image is pre-built, so it can't pick up env-var users you set at runtime for the frontend. For env-var users to work, either build Dashy yourself or use ",(0,i.jsx)(s.code,{children:"hash"})," inline instead. The server-side enforcement does read env vars at runtime regardless."]})}),"\n",(0,i.jsx)(s.h2,{id:"choosing-a-protection-mode",children:"Choosing a Protection Mode"}),"\n",(0,i.jsxs)(s.p,{children:["Defining users is one decision. Deciding ",(0,i.jsx)(s.em,{children:"what those users protect"})," is another. There are three modes, depending on your use case:"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(s.table,{children:[(0,i.jsx)(s.thead,{children:(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.th,{children:"Mode"}),(0,i.jsx)(s.th,{children:"What you set"}),(0,i.jsx)(s.th,{children:"Login page?"}),(0,i.jsx)(s.th,{children:"Server enforces?"})]})}),(0,i.jsxs)(s.tbody,{children:[(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Client-side only"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"auth.users"})," in conf.yml"]}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u274c no"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Server-enforced via conf.yml users"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"auth.users"})," + ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})]}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u2705 yes"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Static credentials"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"BASIC_AUTH_USERNAME"})," + ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_PASSWORD"})," env vars"]}),(0,i.jsx)(s.td,{children:"\u274c no (browser prompt)"}),(0,i.jsx)(s.td,{children:"\u2705 yes"})]})]})]}),"\n",(0,i.jsx)(s.h3,{id:"client-side-login",children:"Client-side login"}),"\n",(0,i.jsxs)(s.p,{children:["With just ",(0,i.jsx)(s.code,{children:"auth.users"})," set in ",(0,i.jsx)(s.code,{children:"conf.yml"}),", and no env vars, Dashy shows a login screen, and after a successful login it sets a session cookie containing a SHA-256 token. The SPA refuses to render the dashboard without that cookie."]}),"\n",(0,i.jsx)(s.p,{children:"Suitable for: a friend or family member should land on the login screen rather than the dashboard, but you trust everyone on the network not to poke around in dev tools."}),"\n",(0,i.jsx)(s.h3,{id:"server-enforced-via-confyml-users",children:"Server-enforced via conf.yml users"}),"\n",(0,i.jsxs)(s.p,{children:["Same ",(0,i.jsx)(s.code,{children:"auth.users"})," list, but the server enforces it too. Set this env var when starting Dashy:"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-env",children:"ENABLE_HTTP_AUTH=true\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Now every API endpoint, every ",(0,i.jsx)(s.code,{children:".yml"})," file, and every server-side route requires a valid session token in the ",(0,i.jsx)(s.code,{children:"Authorization"})," header (delivered automatically by the SPA after login). Unauthenticated requests get a 401 with a basic-auth challenge."]}),"\n",(0,i.jsx)(s.p,{children:"The login page is still the user-facing entry point. The flow is:"}),"\n",(0,i.jsxs)(s.ol,{children:["\n",(0,i.jsx)(s.li,{children:"User loads the page, SPA bundle renders the login screen"}),"\n",(0,i.jsxs)(s.li,{children:["User submits credentials, frontend hashes the password and validates against ",(0,i.jsx)(s.code,{children:"users[]"})]}),"\n",(0,i.jsx)(s.li,{children:"On success, a session cookie is set"}),"\n",(0,i.jsxs)(s.li,{children:["Every subsequent API/asset request includes the cookie as a bearer token in the ",(0,i.jsx)(s.code,{children:"Authorization"})," header"]}),"\n",(0,i.jsxs)(s.li,{children:["The server ",(0,i.jsxs)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/app.js",children:["validates the same token against ",(0,i.jsx)(s.code,{children:"users[]"})]})," before letting the request through"]}),"\n"]}),"\n",(0,i.jsx)(s.p,{children:"This is the recommended setup if you're using built-in auth."}),"\n",(0,i.jsx)(s.h3,{id:"static-credentials-no-dashy-login-page",children:"Static credentials (no Dashy login page)"}),"\n",(0,i.jsxs)(s.p,{children:["If you don't want the Dashy login page at all and would rather have the browser handle auth via a native HTTP basic-auth dialog, skip ",(0,i.jsx)(s.code,{children:"auth.users"})," and set:"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-env",children:"BASIC_AUTH_USERNAME=alicia\nBASIC_AUTH_PASSWORD=supersecret\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Now the server challenges every request, the browser handles credential entry, and once the user enters their creds the browser caches them for the session. No ",(0,i.jsx)(s.code,{children:"users[]"})," block needed in ",(0,i.jsx)(s.code,{children:"conf.yml"}),"."]}),"\n",(0,i.jsxs)(s.p,{children:["Suitable for: single-user setups, machine-to-machine setups (e.g. monitoring or CI hitting ",(0,i.jsx)(s.code,{children:"/healthz"}),"), anywhere a basic-auth header is more convenient than a login page."]}),"\n",(0,i.jsx)(s.p,{children:"Not suitable for: multi-user setups where you want admin/normal role separation. Static credentials are one shared identity."}),"\n",(0,i.jsxs)(s.p,{children:["If your SPA needs to make API calls from JavaScript (most widgets do), you may also want to bake the credentials into the frontend bundle so the SPA can authenticate without bothering the user every page load. See ",(0,i.jsx)(s.a,{href:"#auto-authenticating-the-frontend",children:"Auto-Authenticating the Frontend"})," for that."]}),"\n",(0,i.jsx)(s.admonition,{type:"warning",children:(0,i.jsxs)(s.p,{children:[(0,i.jsxs)(s.strong,{children:["Do not combine ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_*"})," with conf.yml users."]})," If both are set, Dashy prints a warning at startup. The server will reject the SPA's bearer-token requests (because it's expecting the static basic-auth password, not the user's session token), and you'll see auth failures all over the place. Pick one approach. To get conf.yml users with server-side enforcement, use ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"}),", not the static env vars."]})}),"\n",(0,i.jsx)(s.h2,{id:"sessions-guest-access--logout",children:"Sessions, Guest Access & Logout"}),"\n",(0,i.jsx)(s.p,{children:"The session cookie's lifetime is picked at login time, not in config. The login form has a dropdown:"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Don't remember me"})," - session cookie, dies when the browser tab closes"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Remember me for"})," - choose either: 1 hour, 1 day, 1 week or forever"]}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["The cookie's ",(0,i.jsx)(s.code,{children:"expires"}),' attribute is set accordingly. There\'s no admin-level "force users to re-auth every N hours" knob in ',(0,i.jsx)(s.code,{children:"conf.yml"}),"; if you want shorter sessions, that's up to the user picking from the dropdown."]}),"\n",(0,i.jsxs)(s.p,{children:["To log out, open the ",(0,i.jsx)(s.strong,{children:"Config Menu"})," in the top-right and click ",(0,i.jsx)(s.strong,{children:"Logout"}),". The cookie is cleared and the user is bounced back to the login screen."]}),"\n",(0,i.jsx)(s.h3,{id:"guest-access",children:"Guest access"}),"\n",(0,i.jsx)(s.p,{children:"To let visitors browse the dashboard read-only without logging in, add:"}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"appConfig:\n auth:\n enableGuestAccess: true\n users:\n - user: alicia\n hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5\n type: admin\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Guests see whatever items aren't explicitly hidden from them (see next section). They can't enter edit mode, save config, or trigger admin-only actions. The login page gets a ",(0,i.jsx)(s.strong,{children:"Continue as Guest"})," button."]}),"\n",(0,i.jsxs)(s.p,{children:["Guest mode requires ",(0,i.jsx)(s.code,{children:"auth.users"})," to also be set. With no users defined, auth is off entirely and the dashboard is open to everyone."]}),"\n",(0,i.jsx)(s.h2,{id:"user-roles--visibility",children:"User Roles & Visibility"}),"\n",(0,i.jsxs)(s.p,{children:["Two ",(0,i.jsx)(s.code,{children:"type"})," values exist: ",(0,i.jsx)(s.code,{children:"admin"})," and ",(0,i.jsx)(s.code,{children:"normal"}),'. Plus there\'s the implicit "guest" identity when ',(0,i.jsx)(s.code,{children:"enableGuestAccess"})," is on. Different parts of the UI react differently to each:"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(s.table,{children:[(0,i.jsx)(s.thead,{children:(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.th,{children:"Capability"}),(0,i.jsx)(s.th,{children:"Admin"}),(0,i.jsx)(s.th,{children:"Normal user"}),(0,i.jsx)(s.th,{children:"Guest"})]})}),(0,i.jsxs)(s.tbody,{children:[(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Load the dashboard"}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u2705 yes (if guest access on)"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Edit config in the UI"}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u2705 yes (in memory only)"}),(0,i.jsx)(s.td,{children:"\u274c no"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Save config to disk"}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u274c no"}),(0,i.jsx)(s.td,{children:"\u274c no"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsxs)(s.td,{children:["See the ",(0,i.jsx)(s.strong,{children:"Logout"})," button"]}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsxs)(s.td,{children:["n/a (shows ",(0,i.jsx)(s.strong,{children:"Login"}),")"]})]})]})]}),"\n",(0,i.jsxs)(s.p,{children:["A few ",(0,i.jsx)(s.code,{children:"appConfig"})," flags control who can change config, and how:"]}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"disableConfigurationForNonAdmin: true"})," - hides the entire Config Menu from normal users and guests, leaving the dashboard view-only"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"disableConfiguration: true"})," - disables all config UI for everyone, including View Config and admins"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"preventWriteToDisk: true"})," - prevents any user from writing config changes to disk"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"preventLocalSave: true"})," - prevents config changes from being saved in the browser's local storage"]}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["See ",(0,i.jsx)(s.a,{href:"/docs/configuring#appconfig-optional",children:(0,i.jsx)(s.code,{children:"appConfig"})})," for the full list of options."]}),"\n",(0,i.jsx)(s.h3,{id:"per-section-and-per-item-visibility",children:"Per-section and per-item visibility"}),"\n",(0,i.jsxs)(s.p,{children:["Beyond global roles, each section and each item can opt in or out of visibility for different identities via the ",(0,i.jsx)(s.code,{children:"displayData"})," block. Useful for showing certain links only to certain users, or hiding sensitive sections from guests."]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"sections:\n - name: Admin tools\n displayData:\n hideForGuests: true\n hideForUsers: [bob, charlie]\n items:\n - title: Server logs\n url: https://logs.example.com\n displayData:\n showForUsers: [alicia]\n"})}),"\n",(0,i.jsx)(s.p,{children:"The full set:"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"hideForGuests"})," - Hide from anyone in guest mode"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"hideForUsers"})," - List of usernames who should not see this"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"showForUsers"})," - List of usernames who ",(0,i.jsx)(s.em,{children:"exclusively"})," see this (everyone else is hidden)"]}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["These work on sections and items, and on whole pages via a page's ",(0,i.jsx)(s.code,{children:"displayData"}),":"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"pages:\n - name: Home Lab\n path: home-lab.yml\n displayData:\n showForUsers: [alicia]\n - name: Intranet\n path: intranet.yml\n displayData:\n hideForGuests: true\n"})}),"\n",(0,i.jsxs)(s.blockquote,{children:["\n",(0,i.jsxs)(s.p,{children:["Visibility rules are UI-only. Hiding an item from a user doesn't strip the destination URL from the served ",(0,i.jsx)(s.code,{children:"conf.yml"})," if ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH"})," is off and they fetch it directly. Don't use ",(0,i.jsx)(s.code,{children:"displayData"})," as a substitute for actually-private resources."]}),"\n"]}),"\n",(0,i.jsx)(s.h2,{id:"auto-authenticating-the-frontend",children:"Auto-Authenticating the Frontend"}),"\n",(0,i.jsxs)(s.p,{children:["For ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_*"})," mode specifically, the SPA can be configured to send the basic-auth ",(0,i.jsx)(s.code,{children:"Authorization"})," header on every request automatically, so the user never sees the browser's native prompt. Set these at ",(0,i.jsx)(s.em,{children:"build time"}),":"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-env",children:"VITE_APP_BASIC_AUTH_USERNAME=alicia\nVITE_APP_BASIC_AUTH_PASSWORD=supersecret\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Then rebuild Dashy (",(0,i.jsx)(s.code,{children:"yarn build"}),"). The credentials are baked into the JS bundle. This is obviously for demo purposes only, or for use on a trusted private network where the alternative is no auth at all, but it does mean anyone with access to the bundle (i.e. anyone who can reach your Dashy instance unauthenticated, which shouldn't be anyone if you've configured this right) could extract the credentials."]}),"\n",(0,i.jsxs)(s.p,{children:["The official ",(0,i.jsx)(s.code,{children:"lissy93/dashy"})," Docker image is pre-built, so this requires self-building. With ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})," mode (conf.yml users), the SPA handles auth automatically via the session-cookie token, so these env vars aren't needed."]}),"\n",(0,i.jsx)(s.h2,{id:"troubleshooting-common-built-in-auth-issues",children:"Troubleshooting common built-in auth issues"}),"\n",(0,i.jsx)(s.h4,{id:"restart-and-invalid-user-object-appears-in-the-server-logs",children:'Restart and "Invalid user object" appears in the server logs'}),"\n",(0,i.jsxs)(s.p,{children:["Problem: Dashy logs ",(0,i.jsx)(s.code,{children:"Invalid user object. Must have user and either a hash or password param"}),".",(0,i.jsx)(s.br,{}),"\nSolution: YAML parsed your ",(0,i.jsx)(s.code,{children:"hash:"})," value as a number rather than a string, usually because every character in the hash happens to be a digit (",(0,i.jsx)(s.code,{children:"0000..."}),"). Quote it: ",(0,i.jsx)(s.code,{children:'hash: "0000..."'}),"."]}),"\n",(0,i.jsx)(s.h4,{id:"login-fails-with-the-right-password",children:"Login fails with the right password"}),"\n",(0,i.jsxs)(s.p,{children:['Problem: Username + password are correct but the login form says "Incorrect password".',(0,i.jsx)(s.br,{}),"\nSolution: Check the hash. The most common cause is using a hashing tool that adds a trailing newline; on Linux/macOS use ",(0,i.jsx)(s.code,{children:"echo -n"})," (with ",(0,i.jsx)(s.code,{children:"-n"}),") or ",(0,i.jsx)(s.code,{children:"printf '%s' 'password' | sha256sum"}),". You can confirm by computing the hash twice with and without ",(0,i.jsx)(s.code,{children:"-n"})," and seeing if either matches ",(0,i.jsx)(s.code,{children:"users[].hash"}),"."]}),"\n",(0,i.jsx)(s.h4,{id:"browser-keeps-prompting-for-basic-auth-on-every-page-load",children:"Browser keeps prompting for basic auth on every page load"}),"\n",(0,i.jsxs)(s.p,{children:["Problem: Using ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_*"})," mode, the browser prompts on every refresh.",(0,i.jsx)(s.br,{}),"\nSolution: This usually means the SPA is making API requests that don't include credentials, so the server 401s and the browser re-prompts. Either set ",(0,i.jsx)(s.code,{children:"VITE_APP_BASIC_AUTH_*"})," and rebuild (",(0,i.jsx)(s.a,{href:"#auto-authenticating-the-frontend",children:"Auto-Authenticating the Frontend"}),"), or switch to ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})," with ",(0,i.jsx)(s.code,{children:"conf.yml"})," users for a cleaner UX."]}),"\n",(0,i.jsxs)(s.h4,{id:"set-enable_http_authtrue-and-nothing-changed",children:["Set ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})," and nothing changed"]}),"\n",(0,i.jsxs)(s.p,{children:["Problem: ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})," is set but API endpoints are still unauthenticated.",(0,i.jsx)(s.br,{}),"\nSolution: ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH"})," only takes effect when ",(0,i.jsx)(s.code,{children:"auth.users"})," is also set in ",(0,i.jsx)(s.code,{children:"conf.yml"}),". With no users, the flag silently does nothing (no users means no one ",(0,i.jsx)(s.em,{children:"can"})," authenticate). Add at least one user to ",(0,i.jsx)(s.code,{children:"auth.users"})," and restart."]}),"\n",(0,i.jsx)(s.h4,{id:"got-the-warning-basic_auth-env-vars-and-appconfigauthusers-are-both-set",children:'Got the warning "BASIC_AUTH env vars and appConfig.auth.users are both set"'}),"\n",(0,i.jsxs)(s.p,{children:["Problem: Server logs a yellow warning at startup.",(0,i.jsx)(s.br,{}),"\nSolution: You've got both ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_*"})," env vars and ",(0,i.jsx)(s.code,{children:"auth.users"})," in ",(0,i.jsx)(s.code,{children:"conf.yml"}),". These don't play together. Pick one: remove the env vars and set ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})," to enforce ",(0,i.jsx)(s.code,{children:"conf.yml"})," users server-side, or remove ",(0,i.jsx)(s.code,{children:"auth.users"})," to use static creds only."]}),"\n",(0,i.jsx)(s.h4,{id:"logged-in-as-admin-but-save-config-is-still-disabled",children:"Logged in as admin but Save Config is still disabled"}),"\n",(0,i.jsxs)(s.p,{children:["Problem: The user logs in fine, but the save button is greyed out.",(0,i.jsx)(s.br,{}),"\nSolution: Confirm ",(0,i.jsx)(s.code,{children:"type: admin"})," (lowercase) is set on the user in ",(0,i.jsx)(s.code,{children:"conf.yml"}),". Default is ",(0,i.jsx)(s.code,{children:"normal"}),". Also check ",(0,i.jsx)(s.code,{children:"disableConfigurationForNonAdmin"})," is not unexpectedly applying to your account (it shouldn't if ",(0,i.jsx)(s.code,{children:"type: admin"}),", but worth checking)."]}),"\n",(0,i.jsxs)(s.h4,{id:"guest-can-still-see-hidden-items-via-confyml",children:["Guest can still see hidden items via ",(0,i.jsx)(s.code,{children:"/conf.yml"})]}),"\n",(0,i.jsxs)(s.p,{children:["Problem: Hidden a section with ",(0,i.jsx)(s.code,{children:"displayData.hideForGuests"})," but a guest can read it via ",(0,i.jsx)(s.code,{children:"curl https://dashy/conf.yml"}),".",(0,i.jsx)(s.br,{}),"\nSolution: ",(0,i.jsx)(s.code,{children:"displayData"})," is a UI control, not server-side filtering. To gate ",(0,i.jsx)(s.code,{children:"conf.yml"})," itself, you need server enforcement (",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"}),"), which will require auth before serving the raw YAML. With auth enforced, an unauthenticated request gets a stripped version of the config (or 401, depending on guest mode setting)."]}),"\n",(0,i.jsx)(s.h4,{id:"want-to-keep-passwords-out-of-confyml-entirely",children:"Want to keep passwords out of conf.yml entirely"}),"\n",(0,i.jsxs)(s.p,{children:["Problem: You don't want password hashes (even hashed) sitting in your config file.",(0,i.jsx)(s.br,{}),"\nSolution: Use the ",(0,i.jsx)(s.code,{children:"password: VITE_APP_FOO"})," form to reference an env var. Just note that env-var users only work in the frontend if the env var was present at build time; the official Docker image is pre-built. For runtime env vars to take effect on the frontend, you need to self-build."]}),"\n",(0,i.jsx)(s.h4,{id:"auth-works-locally-but-not-behind-a-reverse-proxy",children:"Auth works locally but not behind a reverse proxy"}),"\n",(0,i.jsxs)(s.p,{children:["Problem: Login works when hitting Dashy directly, but breaks behind nginx/Caddy/etc.",(0,i.jsx)(s.br,{}),"\nSolution: Reverse proxies sometimes strip the ",(0,i.jsx)(s.code,{children:"Authorization"})," header or mishandle ",(0,i.jsx)(s.code,{children:"Set-Cookie"}),". Make sure your proxy is configured to pass ",(0,i.jsx)(s.code,{children:"Authorization"})," through, and that ",(0,i.jsx)(s.code,{children:"Set-Cookie"})," from Dashy reaches the browser unmodified. For nginx, ",(0,i.jsx)(s.code,{children:"proxy_pass_request_headers on;"})," is the default, but custom ",(0,i.jsx)(s.code,{children:"proxy_set_header"})," blocks can override it."]}),"\n",(0,i.jsx)(s.hr,{}),"\n",(0,i.jsx)(s.h2,{id:"security-considerations",children:"Security Considerations"}),"\n",(0,i.jsx)(s.p,{children:"A few things to be honest about. Built-in auth was designed for convenience on private networks, not as a hardened identity system. Concretely:"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Hashing is SHA-256, not bcrypt or scrypt."})," SHA-256 is fast, which is great for verifying integrity and bad for storing passwords. If ",(0,i.jsx)(s.code,{children:"conf.yml"})," leaks, those hashes are crackable in seconds for any password short of high-entropy random. Don't reuse passwords across services"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Client-side only mode is bypassable."})," Without ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"}),", the login page is a UI overlay. Anyone can fetch ",(0,i.jsx)(s.code,{children:"/conf.yml"}),", ",(0,i.jsx)(s.code,{children:"/system-info"}),", ",(0,i.jsx)(s.code,{children:"/cors-proxy"}),', etc. directly. For anything past "keep my kids out of the dashboard", set ',(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"No password reset, no lockout, no rate limiting."})," A determined attacker can hammer the login endpoint. Front Dashy with a proxy that does rate limiting if you care"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"No 2FA, no SSO, no audit log."})," If you need any of those, use OIDC. ",(0,i.jsx)(s.a,{href:"/docs/keycloak",children:"Keycloak"}),", ",(0,i.jsx)(s.a,{href:"/docs/authentik",children:"Authentik"}),", ",(0,i.jsx)(s.a,{href:"/docs/authelia-oidc",children:"Authelia"}),", and others integrate cleanly"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Anything internet-facing should not rely on built-in auth alone."})," Put Dashy behind ",(0,i.jsx)(s.a,{href:"/docs/cloudflare-tunnel",children:"Cloudflare Tunnel + Access"}),", ",(0,i.jsx)(s.a,{href:"/docs/tailscale",children:"Tailscale"}),", or a real identity provider, and treat built-in auth as defence-in-depth, not the only gate"]}),"\n"]}),"\n",(0,i.jsx)(s.p,{children:"The right way to think about built-in auth: it's a soft barrier for trusted networks. For anything else, lean on a proper auth layer in front."}),"\n",(0,i.jsx)(s.h2,{id:"how-it-works",children:"How it Works"}),"\n",(0,i.jsx)(s.h3,{id:"the-login-flow-client-side-mode",children:"The login flow (client-side mode)"}),"\n",(0,i.jsx)(s.mermaid,{value:"sequenceDiagram\n autonumber\n actor User\n participant Browser\n participant Dashy\n\n User->>Browser: Open Dashy URL\n Browser->>Dashy: GET /\n Dashy--\x3e>Browser: SPA bundle (HTML/JS/CSS, always public)\n Browser->>Dashy: GET /conf.yml\n Dashy--\x3e>Browser: conf.yml (with auth.users defined)\n Browser->>Browser: SPA sees auth configured, route guard redirects to /login\n\n User->>Browser: Enter username + password\n Browser->>Browser: SHA-256(password), compare against configured user hash\n Browser->>Browser: On match, generate session token
(SHA-256 of username + hash)\n Browser->>Browser: Set AUTH_TOKEN cookie with expiry\n Browser->>Browser: Route guard re-checks, lets user into dashboard"}),"\n",(0,i.jsxs)(s.p,{children:["The token is ",(0,i.jsx)(s.code,{children:"SHA-256(uppercase(username) + uppercase(password_hash))"}),', stored as a cookie. On every page load, the SPA reads the cookie back, recomputes the token from each configured user, and looks for a match. If found, the user is "logged in" client-side. See ',(0,i.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/Auth.js",children:(0,i.jsx)(s.code,{children:"src/utils/auth/Auth.js"})}),"."]}),"\n",(0,i.jsx)(s.h3,{id:"server-enforced-mode",children:"Server-enforced mode"}),"\n",(0,i.jsx)(s.mermaid,{value:"flowchart LR\n Req([Request reaches Dashy]) --\x3e Mw{Has Authorization
Bearer header?}\n Mw -- No --\x3e R401a[/401 WWW-Authenticate
basic-auth challenge/]:::err\n Mw -- Yes --\x3e Tok{Token matches a
configured user?}\n Tok -- No --\x3e R401b[/401 Unauthorized
Incorrect token/]:::err\n Tok -- Yes --\x3e Pass[/Set req.auth = user
continue to route handler/]:::ok\n\n classDef ok fill:#bbf7d0,stroke:#16a34a,color:#14532d\n classDef err fill:#fecaca,stroke:#dc2626,color:#7f1d1d"}),"\n",(0,i.jsxs)(s.p,{children:["With ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"}),", ",(0,i.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/app.js",children:(0,i.jsx)(s.code,{children:"services/app.js"})})," installs an ",(0,i.jsx)(s.code,{children:"express-basic-auth"})," middleware in front of every API endpoint and every ",(0,i.jsx)(s.code,{children:".yml"})," file. The middleware's custom authorizer takes the bearer token, walks the ",(0,i.jsx)(s.code,{children:"users[]"})," list, computes the expected token for each user (",(0,i.jsx)(s.code,{children:"SHA-256(user.user.toUpperCase() + user.hash.toUpperCase())"}),"), and returns true if any match."]}),"\n",(0,i.jsxs)(s.p,{children:["The bearer token the SPA sends ",(0,i.jsx)(s.em,{children:"is"})," the same SHA-256-of-user-plus-hash that's stored client-side as a cookie. There's no signature, no JWT, no expiry on the server side; the server just verifies the cookie-derived token reproduces under one of the configured user entries."]}),"\n",(0,i.jsxs)(s.p,{children:["For ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_*"})," mode, the same middleware is installed but with ",(0,i.jsx)(s.code,{children:"{ users: { [username]: password } }"})," rather than a custom authorizer. The browser handles the credential prompt natively via ",(0,i.jsx)(s.code,{children:"WWW-Authenticate: Basic"}),"."]}),"\n",(0,i.jsxs)(s.details,{children:["\n",(0,i.jsxs)(s.summary,{children:["End-to-end flow with ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})]}),"\n",(0,i.jsx)(s.mermaid,{value:"sequenceDiagram\n autonumber\n actor User\n participant Browser\n participant Dashy\n\n Note over Browser,Dashy: SPA assets (HTML/JS/CSS) are always public\n\n User->>Browser: Open Dashy URL\n Browser->>Dashy: GET /\n Dashy--\x3e>Browser: SPA bundle\n\n Note over Browser,Dashy: First fetch of conf.yml is now gated\n Browser->>Dashy: GET /conf.yml\n Dashy--\x3e>Browser: 401 WWW-Authenticate: Basic\n Browser->>Browser: SPA renders login screen
(it can read auth.users from a fallback fetch
or fail gracefully)\n\n User->>Browser: Submit credentials\n Browser->>Browser: SHA-256(password), compare to configured user hash\n Browser->>Browser: Compute session token,
store as cookie\n Browser->>Dashy: GET /conf.yml
Authorization Bearer header set from cookie\n Dashy->>Dashy: customAuthorizer checks token matches any user\n Dashy--\x3e>Browser: 200 + conf.yml\n\n Note over Browser,Dashy: From now on, every API + .yml fetch carries the bearer\n Browser->>Dashy: GET /system-info
Authorization Bearer header set from cookie\n Dashy--\x3e>Browser: 200 + system info"}),"\n"]})]})}function c(e={}){const{wrapper:s}={...(0,t.R)(),...e.components};return s?(0,i.jsx)(s,{...e,children:(0,i.jsx)(l,{...e})}):l(e)}},28453(e,s,n){n.d(s,{R:()=>o,x:()=>a});var r=n(96540);const i={},t=r.createContext(i);function o(e){const s=r.useContext(t);return r.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),r.createElement(t.Provider,{value:s},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[6608],{68375(e,s,n){n.r(s),n.d(s,{assets:()=>h,contentTitle:()=>a,default:()=>c,frontMatter:()=>o,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"authentication/built-in","title":"Built-In Auth","description":"Dashy ships with a simple username + password auth system baked in, as well as the option to enforce this server-side with standard HTTP auth.","source":"@site/docs/authentication/built-in.md","sourceDirName":"authentication","slug":"/authentication/built-in","permalink":"/docs/authentication/built-in","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/built-in.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Authentik OIDC","permalink":"/docs/authentication/authentik"},"next":{"title":"Cloudflare Tunnel","permalink":"/docs/authentication/cloudflare-tunnel"}}');var i=n(74848),t=n(28453);const o={},a="Built-In Auth",h={},d=[{value:"Contents",id:"contents",level:3},{value:"Quick Start",id:"quick-start",level:2},{value:"Defining Users",id:"defining-users",level:2},{value:"Generating a password hash",id:"generating-a-password-hash",level:3},{value:"Using an env var instead of an inline hash",id:"using-an-env-var-instead-of-an-inline-hash",level:3},{value:"Choosing a Protection Mode",id:"choosing-a-protection-mode",level:2},{value:"Client-side login",id:"client-side-login",level:3},{value:"Server-enforced via conf.yml users",id:"server-enforced-via-confyml-users",level:3},{value:"Static credentials (no Dashy login page)",id:"static-credentials-no-dashy-login-page",level:3},{value:"Sessions, Guest Access & Logout",id:"sessions-guest-access--logout",level:2},{value:"Guest access",id:"guest-access",level:3},{value:"User Roles & Visibility",id:"user-roles--visibility",level:2},{value:"Per-section and per-item visibility",id:"per-section-and-per-item-visibility",level:3},{value:"Auto-Authenticating the Frontend",id:"auto-authenticating-the-frontend",level:2},{value:"Troubleshooting common built-in auth issues",id:"troubleshooting-common-built-in-auth-issues",level:2},{value:"Restart and "Invalid user object" appears in the server logs",id:"restart-and-invalid-user-object-appears-in-the-server-logs",level:4},{value:"Login fails with the right password",id:"login-fails-with-the-right-password",level:4},{value:"Browser keeps prompting for basic auth on every page load",id:"browser-keeps-prompting-for-basic-auth-on-every-page-load",level:4},{value:"Set ENABLE_HTTP_AUTH=true and nothing changed",id:"set-enable_http_authtrue-and-nothing-changed",level:4},{value:"Got the warning "BASIC_AUTH env vars and appConfig.auth.users are both set"",id:"got-the-warning-basic_auth-env-vars-and-appconfigauthusers-are-both-set",level:4},{value:"Logged in as admin but Save Config is still disabled",id:"logged-in-as-admin-but-save-config-is-still-disabled",level:4},{value:"Guest can still see hidden items via /conf.yml",id:"guest-can-still-see-hidden-items-via-confyml",level:4},{value:"Want to keep passwords out of conf.yml entirely",id:"want-to-keep-passwords-out-of-confyml-entirely",level:4},{value:"Auth works locally but not behind a reverse proxy",id:"auth-works-locally-but-not-behind-a-reverse-proxy",level:4},{value:"Security Considerations",id:"security-considerations",level:2},{value:"How it Works",id:"how-it-works",level:2},{value:"The login flow (client-side mode)",id:"the-login-flow-client-side-mode",level:3},{value:"Server-enforced mode",id:"server-enforced-mode",level:3}];function l(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",br:"br",code:"code",details:"details",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",mermaid:"mermaid",ol:"ol",p:"p",pre:"pre",strong:"strong",summary:"summary",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(s.header,{children:(0,i.jsx)(s.h1,{id:"built-in-auth",children:"Built-In Auth"})}),"\n",(0,i.jsx)(s.p,{children:"Dashy ships with a simple username + password auth system baked in, as well as the option to enforce this server-side with standard HTTP auth."}),"\n",(0,i.jsxs)(s.p,{children:["This is the quickest way to setup a login screen, without the need to run any other auth services. No identity provider to run, no external dependencies, just a list of users in ",(0,i.jsx)(s.code,{children:"conf.yml"}),". Useful for keeping casual eyes of your dashboard within a LAN, but is probably not recommended if your instance is exposed to the public internet."]}),"\n",(0,i.jsxs)(s.p,{children:['This guide covers everything the built-in auth can do, from the lightest "show a login screen" mode to a server-enforced setup that 401s every unauthenticated request. If you need OIDC, SSO, or anything internet-facing, see the other guides in this folder. ',(0,i.jsx)(s.strong,{children:"Built-in auth is convenient, not hardened"}),"."]}),"\n",(0,i.jsx)(s.h3,{id:"contents",children:"Contents"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#quick-start",children:"Quick Start"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#defining-users",children:"Defining Users"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#choosing-a-protection-mode",children:"Choosing a Protection Mode"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#sessions-guest-access--logout",children:"Sessions, Guest Access & Logout"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#user-roles--visibility",children:"User Roles & Visibility"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#auto-authenticating-the-frontend",children:"Auto-Authenticating the Frontend"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#troubleshooting-common-built-in-auth-issues",children:"Troubleshooting"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#security-considerations",children:"Security Considerations"})}),"\n",(0,i.jsx)(s.li,{children:(0,i.jsx)(s.a,{href:"#how-it-works",children:"How it Works"})}),"\n"]}),"\n",(0,i.jsx)(s.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,i.jsxs)(s.p,{children:["Add a single user to ",(0,i.jsx)(s.code,{children:"appConfig.auth.users"})," in ",(0,i.jsx)(s.code,{children:"conf.yml"}),", with a SHA-256 hash of your password:"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"appConfig:\n disableConfigurationForNonAdmin: true\n auth:\n users:\n - user: alicia\n hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5\n type: admin\n"})}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-env",children:"ENABLE_HTTP_AUTH=true\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Restart Dashy for changes to take effect. You'll be bounced to a login screen. Enter your username and the plaintext password that hashes to the value above (",(0,i.jsx)(s.code,{children:"12345"})," if you copy-pasted the example. Please change it)."]}),"\n",(0,i.jsx)(s.h2,{id:"defining-users",children:"Defining Users"}),"\n",(0,i.jsxs)(s.p,{children:[(0,i.jsx)(s.code,{children:"appConfig.auth.users"})," is a list of user records. Each entry has a username, a password (as a hash or via an env var), and an optional admin flag."]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"appConfig:\n auth:\n users:\n - user: alicia\n hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5\n type: admin\n - user: guest\n hash: EF797C8118F02DFB649607DD5D3F8C7623048C9C063D532CC95C5ED7A898A64F\n type: normal\n"})}),"\n",(0,i.jsx)(s.p,{children:"Field reference:"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"user"})," - Username. Lowercased on lookup, so casing doesn't matter at login"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"hash"})," - SHA-256 hash of the password, hex, 64 chars. Either upper or lower case works"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"type"})," - ",(0,i.jsx)(s.code,{children:"admin"})," or ",(0,i.jsx)(s.code,{children:"normal"}),". Defaults to ",(0,i.jsx)(s.code,{children:"normal"}),". Admins can save config changes; normal users cannot. See ",(0,i.jsx)(s.a,{href:"#user-roles--visibility",children:"User Roles & Visibility"})," for what else this gates"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"password"})," - Alternative to ",(0,i.jsx)(s.code,{children:"hash"}),". The name of a ",(0,i.jsx)(s.code,{children:"VITE_APP_*"})," environment variable holding the plaintext password. Dashy hashes the env var's value at runtime. This requires building the app from source (don't work with pre-built Docker image)"]}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["Each user needs ",(0,i.jsx)(s.code,{children:"user"})," plus exactly one of ",(0,i.jsx)(s.code,{children:"hash"})," or ",(0,i.jsx)(s.code,{children:"password"})," (not both!)"]}),"\n",(0,i.jsx)(s.h3,{id:"generating-a-password-hash",children:"Generating a password hash"}),"\n",(0,i.jsxs)(s.p,{children:["If you're using ",(0,i.jsx)(s.code,{children:"hash"}),", you need to create a SHA-256 hash ",(0,i.jsx)(s.a,{href:"https://en.wikipedia.org/wiki/Sha-256",children:"\u2197"})," of your password. Either run ",(0,i.jsx)(s.code,{children:'echo -n "my-super-secure-password" | sha256sum'}),", or use an online tool, such as ",(0,i.jsx)(s.a,{href:"https://emn178.github.io/online-tools/sha256.html",children:"this one"})," or ",(0,i.jsx)(s.a,{href:"https://gchq.github.io/CyberChef/",children:"CyberChef"})," (which can be self-hosted and run locally)."]}),"\n",(0,i.jsx)(s.h3,{id:"using-an-env-var-instead-of-an-inline-hash",children:"Using an env var instead of an inline hash"}),"\n",(0,i.jsxs)(s.p,{children:["If you'd rather not put hashed passwords to ",(0,i.jsx)(s.code,{children:"conf.yml"}),", point at an env var:"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"- user: alicia\n password: VITE_APP_ALICIA_PASSWORD\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Then run Dashy with ",(0,i.jsx)(s.code,{children:"VITE_APP_ALICIA_PASSWORD=supersecret"}),". Dashy hashes the value at login time and compares it to what the user types. The env var name must start with ",(0,i.jsx)(s.code,{children:"VITE_APP_"}),"; anything else is rejected."]}),"\n",(0,i.jsx)(s.admonition,{type:"warning",children:(0,i.jsxs)(s.p,{children:["Caveat: env-var passwords only resolve in the browser if the variable was set when the Dashy bundle was ",(0,i.jsx)(s.em,{children:"built"}),". The official Docker image is pre-built, so it can't pick up env-var users you set at runtime for the frontend. For env-var users to work, either build Dashy yourself or use ",(0,i.jsx)(s.code,{children:"hash"})," inline instead. The server-side enforcement does read env vars at runtime regardless."]})}),"\n",(0,i.jsx)(s.h2,{id:"choosing-a-protection-mode",children:"Choosing a Protection Mode"}),"\n",(0,i.jsxs)(s.p,{children:["Defining users is one decision. Deciding ",(0,i.jsx)(s.em,{children:"what those users protect"})," is another. There are three modes, depending on your use case:"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(s.table,{children:[(0,i.jsx)(s.thead,{children:(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.th,{children:"Mode"}),(0,i.jsx)(s.th,{children:"What you set"}),(0,i.jsx)(s.th,{children:"Login page?"}),(0,i.jsx)(s.th,{children:"Server enforces?"})]})}),(0,i.jsxs)(s.tbody,{children:[(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Client-side only"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"auth.users"})," in conf.yml"]}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u274c no"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Server-enforced via conf.yml users"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"auth.users"})," + ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})]}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u2705 yes"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Static credentials"}),(0,i.jsxs)(s.td,{children:[(0,i.jsx)(s.code,{children:"BASIC_AUTH_USERNAME"})," + ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_PASSWORD"})," env vars"]}),(0,i.jsx)(s.td,{children:"\u274c no (browser prompt)"}),(0,i.jsx)(s.td,{children:"\u2705 yes"})]})]})]}),"\n",(0,i.jsx)(s.h3,{id:"client-side-login",children:"Client-side login"}),"\n",(0,i.jsxs)(s.p,{children:["With just ",(0,i.jsx)(s.code,{children:"auth.users"})," set in ",(0,i.jsx)(s.code,{children:"conf.yml"}),", and no env vars, Dashy shows a login screen, and after a successful login it sets a session cookie containing a SHA-256 token. The SPA refuses to render the dashboard without that cookie."]}),"\n",(0,i.jsx)(s.p,{children:"Suitable for: a friend or family member should land on the login screen rather than the dashboard, but you trust everyone on the network not to poke around in dev tools."}),"\n",(0,i.jsx)(s.h3,{id:"server-enforced-via-confyml-users",children:"Server-enforced via conf.yml users"}),"\n",(0,i.jsxs)(s.p,{children:["Same ",(0,i.jsx)(s.code,{children:"auth.users"})," list, but the server enforces it too. Set this env var when starting Dashy:"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-env",children:"ENABLE_HTTP_AUTH=true\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Now every API endpoint, every ",(0,i.jsx)(s.code,{children:".yml"})," file, and every server-side route requires a valid session token in the ",(0,i.jsx)(s.code,{children:"Authorization"})," header (delivered automatically by the SPA after login). Unauthenticated requests get a 401 with a basic-auth challenge."]}),"\n",(0,i.jsx)(s.p,{children:"The login page is still the user-facing entry point. The flow is:"}),"\n",(0,i.jsxs)(s.ol,{children:["\n",(0,i.jsx)(s.li,{children:"User loads the page, SPA bundle renders the login screen"}),"\n",(0,i.jsxs)(s.li,{children:["User submits credentials, frontend hashes the password and validates against ",(0,i.jsx)(s.code,{children:"users[]"})]}),"\n",(0,i.jsx)(s.li,{children:"On success, a session cookie is set"}),"\n",(0,i.jsxs)(s.li,{children:["Every subsequent API/asset request includes the cookie as a bearer token in the ",(0,i.jsx)(s.code,{children:"Authorization"})," header"]}),"\n",(0,i.jsxs)(s.li,{children:["The server ",(0,i.jsxs)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/app.js",children:["validates the same token against ",(0,i.jsx)(s.code,{children:"users[]"})]})," before letting the request through"]}),"\n"]}),"\n",(0,i.jsx)(s.p,{children:"This is the recommended setup if you're using built-in auth."}),"\n",(0,i.jsx)(s.h3,{id:"static-credentials-no-dashy-login-page",children:"Static credentials (no Dashy login page)"}),"\n",(0,i.jsxs)(s.p,{children:["If you don't want the Dashy login page at all and would rather have the browser handle auth via a native HTTP basic-auth dialog, skip ",(0,i.jsx)(s.code,{children:"auth.users"})," and set:"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-env",children:"BASIC_AUTH_USERNAME=alicia\nBASIC_AUTH_PASSWORD=supersecret\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Now the server challenges every request, the browser handles credential entry, and once the user enters their creds the browser caches them for the session. No ",(0,i.jsx)(s.code,{children:"users[]"})," block needed in ",(0,i.jsx)(s.code,{children:"conf.yml"}),"."]}),"\n",(0,i.jsxs)(s.p,{children:["Suitable for: single-user setups, machine-to-machine setups (e.g. monitoring or CI hitting ",(0,i.jsx)(s.code,{children:"/healthz"}),"), anywhere a basic-auth header is more convenient than a login page."]}),"\n",(0,i.jsx)(s.p,{children:"Not suitable for: multi-user setups where you want admin/normal role separation. Static credentials are one shared identity."}),"\n",(0,i.jsxs)(s.p,{children:["If your SPA needs to make API calls from JavaScript (most widgets do), you may also want to bake the credentials into the frontend bundle so the SPA can authenticate without bothering the user every page load. See ",(0,i.jsx)(s.a,{href:"#auto-authenticating-the-frontend",children:"Auto-Authenticating the Frontend"})," for that."]}),"\n",(0,i.jsx)(s.admonition,{type:"warning",children:(0,i.jsxs)(s.p,{children:[(0,i.jsxs)(s.strong,{children:["Do not combine ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_*"})," with conf.yml users."]})," If both are set, Dashy prints a warning at startup. The server will reject the SPA's bearer-token requests (because it's expecting the static basic-auth password, not the user's session token), and you'll see auth failures all over the place. Pick one approach. To get conf.yml users with server-side enforcement, use ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"}),", not the static env vars."]})}),"\n",(0,i.jsx)(s.h2,{id:"sessions-guest-access--logout",children:"Sessions, Guest Access & Logout"}),"\n",(0,i.jsx)(s.p,{children:"The session cookie's lifetime is picked at login time, not in config. The login form has a dropdown:"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Don't remember me"})," - session cookie, dies when the browser tab closes"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Remember me for"})," - choose either: 1 hour, 1 day, 1 week or forever"]}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["The cookie's ",(0,i.jsx)(s.code,{children:"expires"}),' attribute is set accordingly. There\'s no admin-level "force users to re-auth every N hours" knob in ',(0,i.jsx)(s.code,{children:"conf.yml"}),"; if you want shorter sessions, that's up to the user picking from the dropdown."]}),"\n",(0,i.jsxs)(s.p,{children:["To log out, open the ",(0,i.jsx)(s.strong,{children:"Config Menu"})," in the top-right and click ",(0,i.jsx)(s.strong,{children:"Logout"}),". The cookie is cleared and the user is bounced back to the login screen."]}),"\n",(0,i.jsx)(s.h3,{id:"guest-access",children:"Guest access"}),"\n",(0,i.jsx)(s.p,{children:"To let visitors browse the dashboard read-only without logging in, add:"}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"appConfig:\n auth:\n enableGuestAccess: true\n users:\n - user: alicia\n hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5\n type: admin\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Guests see whatever items aren't explicitly hidden from them (see next section). They can't enter edit mode, save config, or trigger admin-only actions. The login page gets a ",(0,i.jsx)(s.strong,{children:"Continue as Guest"})," button."]}),"\n",(0,i.jsxs)(s.p,{children:["Guest mode requires ",(0,i.jsx)(s.code,{children:"auth.users"})," to also be set. With no users defined, auth is off entirely and the dashboard is open to everyone."]}),"\n",(0,i.jsx)(s.h2,{id:"user-roles--visibility",children:"User Roles & Visibility"}),"\n",(0,i.jsxs)(s.p,{children:["Two ",(0,i.jsx)(s.code,{children:"type"})," values exist: ",(0,i.jsx)(s.code,{children:"admin"})," and ",(0,i.jsx)(s.code,{children:"normal"}),'. Plus there\'s the implicit "guest" identity when ',(0,i.jsx)(s.code,{children:"enableGuestAccess"})," is on. Different parts of the UI react differently to each:"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(s.table,{children:[(0,i.jsx)(s.thead,{children:(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.th,{children:"Capability"}),(0,i.jsx)(s.th,{children:"Admin"}),(0,i.jsx)(s.th,{children:"Normal user"}),(0,i.jsx)(s.th,{children:"Guest"})]})}),(0,i.jsxs)(s.tbody,{children:[(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Load the dashboard"}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u2705 yes (if guest access on)"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Edit config in the UI"}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u2705 yes (in memory only)"}),(0,i.jsx)(s.td,{children:"\u274c no"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsx)(s.td,{children:"Save config to disk"}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u274c no"}),(0,i.jsx)(s.td,{children:"\u274c no"})]}),(0,i.jsxs)(s.tr,{children:[(0,i.jsxs)(s.td,{children:["See the ",(0,i.jsx)(s.strong,{children:"Logout"})," button"]}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsx)(s.td,{children:"\u2705 yes"}),(0,i.jsxs)(s.td,{children:["n/a (shows ",(0,i.jsx)(s.strong,{children:"Login"}),")"]})]})]})]}),"\n",(0,i.jsxs)(s.p,{children:["A few ",(0,i.jsx)(s.code,{children:"appConfig"})," flags control who can change config, and how:"]}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"disableConfigurationForNonAdmin: true"})," - hides the entire Config Menu from normal users and guests, leaving the dashboard view-only"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"disableConfiguration: true"})," - disables all config UI for everyone, including View Config and admins"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"preventWriteToDisk: true"})," - prevents any user from writing config changes to disk"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"preventLocalSave: true"})," - prevents config changes from being saved in the browser's local storage"]}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["See ",(0,i.jsx)(s.a,{href:"/docs/configuring#appconfig-optional",children:(0,i.jsx)(s.code,{children:"appConfig"})})," for the full list of options."]}),"\n",(0,i.jsx)(s.h3,{id:"per-section-and-per-item-visibility",children:"Per-section and per-item visibility"}),"\n",(0,i.jsxs)(s.p,{children:["Beyond global roles, each section and each item can opt in or out of visibility for different identities via the ",(0,i.jsx)(s.code,{children:"displayData"})," block. Useful for showing certain links only to certain users, or hiding sensitive sections from guests."]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"sections:\n - name: Admin tools\n displayData:\n hideForGuests: true\n hideForUsers: [bob, charlie]\n items:\n - title: Server logs\n url: https://logs.example.com\n displayData:\n showForUsers: [alicia]\n"})}),"\n",(0,i.jsx)(s.p,{children:"The full set:"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"hideForGuests"})," - Hide from anyone in guest mode"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"hideForUsers"})," - List of usernames who should not see this"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.code,{children:"showForUsers"})," - List of usernames who ",(0,i.jsx)(s.em,{children:"exclusively"})," see this (everyone else is hidden)"]}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["These work on sections and items, and on whole pages via a page's ",(0,i.jsx)(s.code,{children:"displayData"}),":"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-yaml",children:"pages:\n - name: Home Lab\n path: home-lab.yml\n displayData:\n showForUsers: [alicia]\n - name: Intranet\n path: intranet.yml\n displayData:\n hideForGuests: true\n"})}),"\n",(0,i.jsxs)(s.blockquote,{children:["\n",(0,i.jsxs)(s.p,{children:["Visibility rules are UI-only. Hiding an item from a user doesn't strip the destination URL from the served ",(0,i.jsx)(s.code,{children:"conf.yml"})," if ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH"})," is off and they fetch it directly. Don't use ",(0,i.jsx)(s.code,{children:"displayData"})," as a substitute for actually-private resources."]}),"\n"]}),"\n",(0,i.jsx)(s.h2,{id:"auto-authenticating-the-frontend",children:"Auto-Authenticating the Frontend"}),"\n",(0,i.jsxs)(s.p,{children:["For ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_*"})," mode specifically, the SPA can be configured to send the basic-auth ",(0,i.jsx)(s.code,{children:"Authorization"})," header on every request automatically, so the user never sees the browser's native prompt. Set these at ",(0,i.jsx)(s.em,{children:"build time"}),":"]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-env",children:"VITE_APP_BASIC_AUTH_USERNAME=alicia\nVITE_APP_BASIC_AUTH_PASSWORD=supersecret\n"})}),"\n",(0,i.jsxs)(s.p,{children:["Then rebuild Dashy (",(0,i.jsx)(s.code,{children:"yarn build"}),"). The credentials are baked into the JS bundle. This is obviously for demo purposes only, or for use on a trusted private network where the alternative is no auth at all, but it does mean anyone with access to the bundle (i.e. anyone who can reach your Dashy instance unauthenticated, which shouldn't be anyone if you've configured this right) could extract the credentials."]}),"\n",(0,i.jsxs)(s.p,{children:["The official ",(0,i.jsx)(s.code,{children:"lissy93/dashy"})," Docker image is pre-built, so this requires self-building. With ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})," mode (conf.yml users), the SPA handles auth automatically via the session-cookie token, so these env vars aren't needed."]}),"\n",(0,i.jsx)(s.h2,{id:"troubleshooting-common-built-in-auth-issues",children:"Troubleshooting common built-in auth issues"}),"\n",(0,i.jsx)(s.h4,{id:"restart-and-invalid-user-object-appears-in-the-server-logs",children:'Restart and "Invalid user object" appears in the server logs'}),"\n",(0,i.jsxs)(s.p,{children:["Problem: Dashy logs ",(0,i.jsx)(s.code,{children:"Invalid user object. Must have user and either a hash or password param"}),".",(0,i.jsx)(s.br,{}),"\nSolution: YAML parsed your ",(0,i.jsx)(s.code,{children:"hash:"})," value as a number rather than a string, usually because every character in the hash happens to be a digit (",(0,i.jsx)(s.code,{children:"0000..."}),"). Quote it: ",(0,i.jsx)(s.code,{children:'hash: "0000..."'}),"."]}),"\n",(0,i.jsx)(s.h4,{id:"login-fails-with-the-right-password",children:"Login fails with the right password"}),"\n",(0,i.jsxs)(s.p,{children:['Problem: Username + password are correct but the login form says "Incorrect password".',(0,i.jsx)(s.br,{}),"\nSolution: Check the hash. The most common cause is using a hashing tool that adds a trailing newline; on Linux/macOS use ",(0,i.jsx)(s.code,{children:"echo -n"})," (with ",(0,i.jsx)(s.code,{children:"-n"}),") or ",(0,i.jsx)(s.code,{children:"printf '%s' 'password' | sha256sum"}),". You can confirm by computing the hash twice with and without ",(0,i.jsx)(s.code,{children:"-n"})," and seeing if either matches ",(0,i.jsx)(s.code,{children:"users[].hash"}),"."]}),"\n",(0,i.jsx)(s.h4,{id:"browser-keeps-prompting-for-basic-auth-on-every-page-load",children:"Browser keeps prompting for basic auth on every page load"}),"\n",(0,i.jsxs)(s.p,{children:["Problem: Using ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_*"})," mode, the browser prompts on every refresh.",(0,i.jsx)(s.br,{}),"\nSolution: This usually means the SPA is making API requests that don't include credentials, so the server 401s and the browser re-prompts. Either set ",(0,i.jsx)(s.code,{children:"VITE_APP_BASIC_AUTH_*"})," and rebuild (",(0,i.jsx)(s.a,{href:"#auto-authenticating-the-frontend",children:"Auto-Authenticating the Frontend"}),"), or switch to ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})," with ",(0,i.jsx)(s.code,{children:"conf.yml"})," users for a cleaner UX."]}),"\n",(0,i.jsxs)(s.h4,{id:"set-enable_http_authtrue-and-nothing-changed",children:["Set ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})," and nothing changed"]}),"\n",(0,i.jsxs)(s.p,{children:["Problem: ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})," is set but API endpoints are still unauthenticated.",(0,i.jsx)(s.br,{}),"\nSolution: ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH"})," only takes effect when ",(0,i.jsx)(s.code,{children:"auth.users"})," is also set in ",(0,i.jsx)(s.code,{children:"conf.yml"}),". With no users, the flag silently does nothing (no users means no one ",(0,i.jsx)(s.em,{children:"can"})," authenticate). Add at least one user to ",(0,i.jsx)(s.code,{children:"auth.users"})," and restart."]}),"\n",(0,i.jsx)(s.h4,{id:"got-the-warning-basic_auth-env-vars-and-appconfigauthusers-are-both-set",children:'Got the warning "BASIC_AUTH env vars and appConfig.auth.users are both set"'}),"\n",(0,i.jsxs)(s.p,{children:["Problem: Server logs a yellow warning at startup.",(0,i.jsx)(s.br,{}),"\nSolution: You've got both ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_*"})," env vars and ",(0,i.jsx)(s.code,{children:"auth.users"})," in ",(0,i.jsx)(s.code,{children:"conf.yml"}),". These don't play together. Pick one: remove the env vars and set ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})," to enforce ",(0,i.jsx)(s.code,{children:"conf.yml"})," users server-side, or remove ",(0,i.jsx)(s.code,{children:"auth.users"})," to use static creds only."]}),"\n",(0,i.jsx)(s.h4,{id:"logged-in-as-admin-but-save-config-is-still-disabled",children:"Logged in as admin but Save Config is still disabled"}),"\n",(0,i.jsxs)(s.p,{children:["Problem: The user logs in fine, but the save button is greyed out.",(0,i.jsx)(s.br,{}),"\nSolution: Confirm ",(0,i.jsx)(s.code,{children:"type: admin"})," (lowercase) is set on the user in ",(0,i.jsx)(s.code,{children:"conf.yml"}),". Default is ",(0,i.jsx)(s.code,{children:"normal"}),". Also check ",(0,i.jsx)(s.code,{children:"disableConfigurationForNonAdmin"})," is not unexpectedly applying to your account (it shouldn't if ",(0,i.jsx)(s.code,{children:"type: admin"}),", but worth checking)."]}),"\n",(0,i.jsxs)(s.h4,{id:"guest-can-still-see-hidden-items-via-confyml",children:["Guest can still see hidden items via ",(0,i.jsx)(s.code,{children:"/conf.yml"})]}),"\n",(0,i.jsxs)(s.p,{children:["Problem: Hidden a section with ",(0,i.jsx)(s.code,{children:"displayData.hideForGuests"})," but a guest can read it via ",(0,i.jsx)(s.code,{children:"curl https://dashy/conf.yml"}),".",(0,i.jsx)(s.br,{}),"\nSolution: ",(0,i.jsx)(s.code,{children:"displayData"})," is a UI control, not server-side filtering. To gate ",(0,i.jsx)(s.code,{children:"conf.yml"})," itself, you need server enforcement (",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"}),"), which will require auth before serving the raw YAML. With auth enforced, an unauthenticated request gets a stripped version of the config (or 401, depending on guest mode setting)."]}),"\n",(0,i.jsx)(s.h4,{id:"want-to-keep-passwords-out-of-confyml-entirely",children:"Want to keep passwords out of conf.yml entirely"}),"\n",(0,i.jsxs)(s.p,{children:["Problem: You don't want password hashes (even hashed) sitting in your config file.",(0,i.jsx)(s.br,{}),"\nSolution: Use the ",(0,i.jsx)(s.code,{children:"password: VITE_APP_FOO"})," form to reference an env var. Just note that env-var users only work in the frontend if the env var was present at build time; the official Docker image is pre-built. For runtime env vars to take effect on the frontend, you need to self-build."]}),"\n",(0,i.jsx)(s.h4,{id:"auth-works-locally-but-not-behind-a-reverse-proxy",children:"Auth works locally but not behind a reverse proxy"}),"\n",(0,i.jsxs)(s.p,{children:["Problem: Login works when hitting Dashy directly, but breaks behind nginx/Caddy/etc.",(0,i.jsx)(s.br,{}),"\nSolution: Reverse proxies sometimes strip the ",(0,i.jsx)(s.code,{children:"Authorization"})," header or mishandle ",(0,i.jsx)(s.code,{children:"Set-Cookie"}),". Make sure your proxy is configured to pass ",(0,i.jsx)(s.code,{children:"Authorization"})," through, and that ",(0,i.jsx)(s.code,{children:"Set-Cookie"})," from Dashy reaches the browser unmodified. For nginx, ",(0,i.jsx)(s.code,{children:"proxy_pass_request_headers on;"})," is the default, but custom ",(0,i.jsx)(s.code,{children:"proxy_set_header"})," blocks can override it."]}),"\n",(0,i.jsx)(s.hr,{}),"\n",(0,i.jsx)(s.h2,{id:"security-considerations",children:"Security Considerations"}),"\n",(0,i.jsx)(s.p,{children:"A few things to be honest about. Built-in auth was designed for convenience on private networks, not as a hardened identity system. Concretely:"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Hashing is SHA-256, not bcrypt or scrypt."})," SHA-256 is fast, which is great for verifying integrity and bad for storing passwords. If ",(0,i.jsx)(s.code,{children:"conf.yml"})," leaks, those hashes are crackable in seconds for any password short of high-entropy random. Don't reuse passwords across services"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Client-side only mode is bypassable."})," Without ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"}),", the login page is a UI overlay. Anyone can fetch ",(0,i.jsx)(s.code,{children:"/conf.yml"}),", ",(0,i.jsx)(s.code,{children:"/system-info"}),", ",(0,i.jsx)(s.code,{children:"/cors-proxy"}),', etc. directly. For anything past "keep my kids out of the dashboard", set ',(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"No password reset, no lockout, no rate limiting."})," A determined attacker can hammer the login endpoint. Front Dashy with a proxy that does rate limiting if you care"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"No 2FA, no SSO, no audit log."})," If you need any of those, use OIDC. ",(0,i.jsx)(s.a,{href:"/docs/keycloak",children:"Keycloak"}),", ",(0,i.jsx)(s.a,{href:"/docs/authentik",children:"Authentik"}),", ",(0,i.jsx)(s.a,{href:"/docs/authelia-oidc",children:"Authelia"}),", and others integrate cleanly"]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Anything internet-facing should not rely on built-in auth alone."})," Put Dashy behind ",(0,i.jsx)(s.a,{href:"/docs/cloudflare-tunnel",children:"Cloudflare Tunnel + Access"}),", ",(0,i.jsx)(s.a,{href:"/docs/tailscale",children:"Tailscale"}),", or a real identity provider, and treat built-in auth as defence-in-depth, not the only gate"]}),"\n"]}),"\n",(0,i.jsx)(s.p,{children:"The right way to think about built-in auth: it's a soft barrier for trusted networks. For anything else, lean on a proper auth layer in front."}),"\n",(0,i.jsx)(s.h2,{id:"how-it-works",children:"How it Works"}),"\n",(0,i.jsx)(s.h3,{id:"the-login-flow-client-side-mode",children:"The login flow (client-side mode)"}),"\n",(0,i.jsx)(s.mermaid,{value:"sequenceDiagram\n autonumber\n actor User\n participant Browser\n participant Dashy\n\n User->>Browser: Open Dashy URL\n Browser->>Dashy: GET /\n Dashy--\x3e>Browser: SPA bundle (HTML/JS/CSS, always public)\n Browser->>Dashy: GET /conf.yml\n Dashy--\x3e>Browser: conf.yml (with auth.users defined)\n Browser->>Browser: SPA sees auth configured, route guard redirects to /login\n\n User->>Browser: Enter username + password\n Browser->>Browser: SHA-256(password), compare against configured user hash\n Browser->>Browser: On match, generate session token
(SHA-256 of username + hash)\n Browser->>Browser: Set AUTH_TOKEN cookie with expiry\n Browser->>Browser: Route guard re-checks, lets user into dashboard"}),"\n",(0,i.jsxs)(s.p,{children:["The token is ",(0,i.jsx)(s.code,{children:"SHA-256(uppercase(username) + uppercase(password_hash))"}),', stored as a cookie. On every page load, the SPA reads the cookie back, recomputes the token from each configured user, and looks for a match. If found, the user is "logged in" client-side. See ',(0,i.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/Auth.js",children:(0,i.jsx)(s.code,{children:"src/utils/auth/Auth.js"})}),"."]}),"\n",(0,i.jsx)(s.h3,{id:"server-enforced-mode",children:"Server-enforced mode"}),"\n",(0,i.jsx)(s.mermaid,{value:"flowchart LR\n Req([Request reaches Dashy]) --\x3e Mw{Has Authorization
Bearer header?}\n Mw -- No --\x3e R401a[/401 WWW-Authenticate
basic-auth challenge/]:::err\n Mw -- Yes --\x3e Tok{Token matches a
configured user?}\n Tok -- No --\x3e R401b[/401 Unauthorized
Incorrect token/]:::err\n Tok -- Yes --\x3e Pass[/Set req.auth = user
continue to route handler/]:::ok\n\n classDef ok fill:#bbf7d0,stroke:#16a34a,color:#14532d\n classDef err fill:#fecaca,stroke:#dc2626,color:#7f1d1d"}),"\n",(0,i.jsxs)(s.p,{children:["With ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"}),", ",(0,i.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/app.js",children:(0,i.jsx)(s.code,{children:"services/app.js"})})," installs an ",(0,i.jsx)(s.code,{children:"express-basic-auth"})," middleware in front of every API endpoint and every ",(0,i.jsx)(s.code,{children:".yml"})," file. The middleware's custom authorizer takes the bearer token, walks the ",(0,i.jsx)(s.code,{children:"users[]"})," list, computes the expected token for each user (",(0,i.jsx)(s.code,{children:"SHA-256(user.user.toUpperCase() + user.hash.toUpperCase())"}),"), and returns true if any match."]}),"\n",(0,i.jsxs)(s.p,{children:["The bearer token the SPA sends ",(0,i.jsx)(s.em,{children:"is"})," the same SHA-256-of-user-plus-hash that's stored client-side as a cookie. There's no signature, no JWT, no expiry on the server side; the server just verifies the cookie-derived token reproduces under one of the configured user entries."]}),"\n",(0,i.jsxs)(s.p,{children:["For ",(0,i.jsx)(s.code,{children:"BASIC_AUTH_*"})," mode, the same middleware is installed but with ",(0,i.jsx)(s.code,{children:"{ users: { [username]: password } }"})," rather than a custom authorizer. The browser handles the credential prompt natively via ",(0,i.jsx)(s.code,{children:"WWW-Authenticate: Basic"}),"."]}),"\n",(0,i.jsxs)(s.details,{children:["\n",(0,i.jsxs)(s.summary,{children:["End-to-end flow with ",(0,i.jsx)(s.code,{children:"ENABLE_HTTP_AUTH=true"})]}),"\n",(0,i.jsx)(s.mermaid,{value:"sequenceDiagram\n autonumber\n actor User\n participant Browser\n participant Dashy\n\n Note over Browser,Dashy: SPA assets (HTML/JS/CSS) are always public\n\n User->>Browser: Open Dashy URL\n Browser->>Dashy: GET /\n Dashy--\x3e>Browser: SPA bundle\n\n Note over Browser,Dashy: First fetch of conf.yml is now gated\n Browser->>Dashy: GET /conf.yml\n Dashy--\x3e>Browser: 401 WWW-Authenticate: Basic\n Browser->>Browser: SPA renders login screen
(it can read auth.users from a fallback fetch
or fail gracefully)\n\n User->>Browser: Submit credentials\n Browser->>Browser: SHA-256(password), compare to configured user hash\n Browser->>Browser: Compute session token,
store as cookie\n Browser->>Dashy: GET /conf.yml
Authorization Bearer header set from cookie\n Dashy->>Dashy: customAuthorizer checks token matches any user\n Dashy--\x3e>Browser: 200 + conf.yml\n\n Note over Browser,Dashy: From now on, every API + .yml fetch carries the bearer\n Browser->>Dashy: GET /system-info
Authorization Bearer header set from cookie\n Dashy--\x3e>Browser: 200 + system info"}),"\n"]})]})}function c(e={}){const{wrapper:s}={...(0,t.R)(),...e.components};return s?(0,i.jsx)(s,{...e,children:(0,i.jsx)(l,{...e})}):l(e)}},28453(e,s,n){n.d(s,{R:()=>o,x:()=>a});var r=n(96540);const i={},t=r.createContext(i);function o(e){const s=r.useContext(t);return r.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),r.createElement(t.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/90a62683.d340793c.js b/assets/js/90a62683.a64be9fa.js similarity index 99% rename from assets/js/90a62683.d340793c.js rename to assets/js/90a62683.a64be9fa.js index 4ba35de7..27439977 100644 --- a/assets/js/90a62683.d340793c.js +++ b/assets/js/90a62683.a64be9fa.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[2246],{86560(e,s,n){n.r(s),n.d(s,{assets:()=>a,contentTitle:()=>c,default:()=>h,frontMatter:()=>t,metadata:()=>i,toc:()=>d});const i=JSON.parse('{"id":"authentication/pocketid","title":"Pocket ID OIDC","description":"Dashy supports using Pocket ID as its OIDC provider.","source":"@site/docs/authentication/pocketid.md","sourceDirName":"authentication","slug":"/authentication/pocketid","permalink":"/docs/authentication/pocketid","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/pocketid.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Other Auth Methods","permalink":"/docs/authentication/other-auth-methods"},"next":{"title":"Tailscale","permalink":"/docs/authentication/tailscale"}}');var r=n(74848),o=n(28453);const t={},c="Pocket ID OIDC",a={},d=[{value:"Contents",id:"contents",level:3},{value:"1. Deploy Pocket ID",id:"1-deploy-pocket-id",level:2},{value:"2. Configure Pocket ID",id:"2-configure-pocket-id",level:2},{value:"First-time admin setup",id:"first-time-admin-setup",level:3},{value:"Create the OIDC client",id:"create-the-oidc-client",level:3},{value:"Create the admin group",id:"create-the-admin-group",level:3},{value:"Create test users",id:"create-test-users",level:3},{value:"Summary",id:"summary",level:3},{value:"3. Enabling Pocket ID in Dashy",id:"3-enabling-pocket-id-in-dashy",level:2},{value:"4. Groups and Visibility",id:"4-groups-and-visibility",level:2},{value:"5. Silent token renewal (optional)",id:"5-silent-token-renewal-optional",level:2},{value:"Troubleshooting common Pocket ID issues",id:"troubleshooting-common-pocket-id-issues",level:2},{value:"Pocket ID won't start, "ENCRYPTION_KEY must be at least 16 bytes long"",id:"pocket-id-wont-start-encryption_key-must-be-at-least-16-bytes-long",level:4},{value:""WebAuthn is not supported in this browser"",id:"webauthn-is-not-supported-in-this-browser",level:4},{value:""Cookie 'session' has been rejected because a non-HTTPS cookie can't be set as 'secure'"",id:"cookie-session-has-been-rejected-because-a-non-https-cookie-cant-be-set-as-secure",level:4},{value:"One-time access token returns "Token is invalid or expired"",id:"one-time-access-token-returns-token-is-invalid-or-expired",level:4},{value:"Logged in but config saves return 403",id:"logged-in-but-config-saves-return-403",level:4},{value:"Redirect URI mismatch",id:"redirect-uri-mismatch",level:4},{value:"Dashy server can't reach Pocket ID",id:"dashy-server-cant-reach-pocket-id",level:4},{value:"Issuer mismatch on token verification",id:"issuer-mismatch-on-token-verification",level:4},{value:"First admin can't sign in, no setup page appears",id:"first-admin-cant-sign-in-no-setup-page-appears",level:4},{value:"Token expired / clock skew",id:"token-expired--clock-skew",level:4},{value:"Config change to auth.oidc not picked up",id:"config-change-to-authoidc-not-picked-up",level:4},{value:"How it Works",id:"how-it-works",level:2},{value:"Client side",id:"client-side",level:3},{value:"Server side",id:"server-side",level:3},{value:"Visual Overview",id:"visual-overview",level:3}];function l(e){const s={a:"a",br:"br",code:"code",details:"details",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",mermaid:"mermaid",ol:"ol",p:"p",pre:"pre",strong:"strong",summary:"summary",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.header,{children:(0,r.jsx)(s.h1,{id:"pocket-id-oidc",children:"Pocket ID OIDC"})}),"\n",(0,r.jsxs)(s.p,{children:["Dashy supports using ",(0,r.jsx)(s.a,{href:"https://pocket-id.org/",children:"Pocket ID"})," as its OIDC provider."]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://pocket-id.org/",children:"Pocket ID"})," is a small ",(0,r.jsx)(s.a,{href:"https://github.com/pocket-id/pocket-id",children:"open source"})," identity provider built around passkeys. It's a single Go binary backed by SQLite (or Postgres if you want), no password storage, no email server needed for basic use. Popular in homelab setups where you want SSO across a handful of services without running Authentik or Keycloak."]}),"\n",(0,r.jsx)(s.h3,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#1-deploy-pocket-id",children:"1. Deploy Pocket ID"})}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.a,{href:"#2-configure-pocket-id",children:"2. Configure Pocket ID"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#first-time-admin-setup",children:"First-time admin setup"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#create-the-oidc-client",children:"Create the OIDC client"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#create-the-admin-group",children:"Create the admin group"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#create-test-users",children:"Create test users"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#3-enabling-pocket-id-in-dashy",children:"3. Enabling Pocket ID in Dashy"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#4-groups-and-visibility",children:"4. Groups and Visibility"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#5-silent-token-renewal-optional",children:"5. Silent token renewal (optional)"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#troubleshooting-common-pocket-id-issues",children:"Troubleshooting"})}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.a,{href:"#how-it-works",children:"How it Works"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#client-side",children:"Client side"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#server-side",children:"Server side"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#visual-overview",children:"Visual Overview"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"1-deploy-pocket-id",children:"1. Deploy Pocket ID"}),"\n",(0,r.jsx)(s.p,{children:"A minimal compose:"}),"\n",(0,r.jsxs)(s.details,{children:["\n ",(0,r.jsxs)(s.summary,{children:["Example ",(0,r.jsx)(s.code,{children:"docker-compose.yml"})]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:'name: dashy-pocketid\n\nservices:\n pocket-id:\n image: ghcr.io/pocket-id/pocket-id:v2.7.0\n restart: unless-stopped\n ports:\n - "1411:1411"\n environment:\n APP_URL: http://localhost:1411\n TRUST_PROXY: "false"\n DB_PROVIDER: sqlite\n DB_CONNECTION_STRING: /data/pocket-id.db\n ENCRYPTION_KEY: "local-dev-key-32-bytes-padding!!"\n volumes:\n - ./data:/data\n healthcheck:\n test: ["CMD", "wget", "-qO-", "http://127.0.0.1:1411/healthz"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 30s\n'})}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"A few things worth understanding before you start:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"APP_URL"})," is the URL the browser uses to reach Pocket ID. It must match exactly, since Pocket ID puts that value in the OIDC issuer, in cookie domains, and in the WebAuthn relying party"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"ENCRYPTION_KEY"})," is required and must be at least 16 bytes. Generate a real one with ",(0,r.jsx)(s.code,{children:"openssl rand -hex 32"})," for production; the example above is for local testing only"]}),"\n",(0,r.jsxs)(s.li,{children:["Pocket ID is passkey-only by default. Passkeys (WebAuthn) require a secure context, which means either HTTPS, or a literal ",(0,r.jsx)(s.code,{children:"localhost"})," host. ",(0,r.jsx)(s.code,{children:"pocketid.lvh.me"})," and similar lookalikes don't qualify, only the exact string ",(0,r.jsx)(s.code,{children:"localhost"}),". For production use HTTPS with a real domain; for local testing stick to ",(0,r.jsx)(s.code,{children:"http://localhost"})]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"For production:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Put Pocket ID behind a reverse proxy with HTTPS on a real domain"}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.code,{children:"APP_URL: https://auth.example.com"})]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.code,{children:'TRUST_PROXY: "true"'})," so it honours forwarded headers"]}),"\n",(0,r.jsxs)(s.li,{children:["Use a 32-byte random ",(0,r.jsx)(s.code,{children:"ENCRYPTION_KEY"})]}),"\n",(0,r.jsxs)(s.li,{children:["Consider swapping ",(0,r.jsx)(s.code,{children:"DB_PROVIDER"})," to ",(0,r.jsx)(s.code,{children:"postgres"})," for redundancy"]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"Bring Pocket ID up:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:"docker compose up -d pocket-id\n"})}),"\n",(0,r.jsxs)(s.p,{children:["Wait for it to be healthy (",(0,r.jsx)(s.code,{children:"curl -sf http://localhost:1411/healthz"}),"), then continue."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"2-configure-pocket-id",children:"2. Configure Pocket ID"}),"\n",(0,r.jsx)(s.h3,{id:"first-time-admin-setup",children:"First-time admin setup"}),"\n",(0,r.jsx)(s.p,{children:"There's no env-var bootstrap. You create the first admin through the UI, or via SQLite if you want to script it."}),"\n",(0,r.jsx)(s.p,{children:"Through the UI:"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Open ",(0,r.jsx)(s.code,{children:"http://localhost:1411"})," in a browser"]}),"\n",(0,r.jsx)(s.li,{children:"The setup wizard prompts for a first admin (username, email, name)"}),"\n",(0,r.jsx)(s.li,{children:"After creating the user, register a passkey when prompted"}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"If you can't easily register a passkey (no compatible device, or running headless), insert the admin user via SQLite and then use the CLI to get a one-time login URL:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:'docker compose exec pocket-id sh -c \'\n apk add --no-cache sqlite >/dev/null 2>&1\n sqlite3 /data/pocket-id.db "\n INSERT INTO users (id, created_at, updated_at, username, email, first_name, last_name, display_name, is_admin, email_verified)\n VALUES (\\"admin-uuid-001\\", datetime(\\"now\\"), datetime(\\"now\\"), \\"pocketid-admin\\", \\"admin@example.com\\", \\"Pocket ID\\", \\"Admin\\", \\"Admin\\", 1, 1);\n "\n\'\n\ndocker compose exec pocket-id /app/pocket-id one-time-access-token pocketid-admin\n'})}),"\n",(0,r.jsxs)(s.p,{children:["The CLI prints a URL like ",(0,r.jsx)(s.code,{children:"http://localhost:1411/lc/"}),". Open it in a browser to log in. The token is single-use and expires in 1 hour. Once signed in, register a passkey under ",(0,r.jsx)(s.strong,{children:"Settings > Passkeys"})," so you don't need to keep generating OTA tokens."]}),"\n",(0,r.jsx)(s.h3,{id:"create-the-oidc-client",children:"Create the OIDC client"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Open ",(0,r.jsx)(s.strong,{children:"Admin > OIDC Clients"})]}),"\n",(0,r.jsxs)(s.li,{children:["Click ",(0,r.jsx)(s.strong,{children:"Add OIDC Client"})]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.strong,{children:"Name"})," to ",(0,r.jsx)(s.code,{children:"Dashy"})]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.strong,{children:"Callback URLs"})," to both:\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.code,{children:"http://localhost:4000"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.code,{children:"http://localhost:4000/"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.strong,{children:"Logout Callback URLs"})," to ",(0,r.jsx)(s.code,{children:"http://localhost:4000"})]}),"\n",(0,r.jsxs)(s.li,{children:["Turn ",(0,r.jsx)(s.strong,{children:"Public Client"})," on"]}),"\n",(0,r.jsxs)(s.li,{children:["Turn ",(0,r.jsx)(s.strong,{children:"PKCE"})," on"]}),"\n",(0,r.jsxs)(s.li,{children:["Click ",(0,r.jsx)(s.strong,{children:"Save"})]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["Pocket ID generates a client ID (a UUID, e.g. ",(0,r.jsx)(s.code,{children:"268a3701-e9ec-41f6-bad5-87c78ce87c94"}),"). Copy it; you'll need it for Dashy's config."]}),"\n",(0,r.jsx)(s.h3,{id:"create-the-admin-group",children:"Create the admin group"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Open ",(0,r.jsx)(s.strong,{children:"Admin > User Groups"})]}),"\n",(0,r.jsxs)(s.li,{children:["Click ",(0,r.jsx)(s.strong,{children:"Add Group"})]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.strong,{children:"Name"})," to ",(0,r.jsx)(s.code,{children:"Dashy admins"})," (or anything you like)"]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.strong,{children:"Friendly name"})," to ",(0,r.jsx)(s.code,{children:"admins"})," (this is what Pocket ID emits in the OIDC ",(0,r.jsx)(s.code,{children:"groups"})," claim, and what Dashy matches against)"]}),"\n",(0,r.jsxs)(s.li,{children:["Click ",(0,r.jsx)(s.strong,{children:"Save"})]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"create-test-users",children:"Create test users"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Open ",(0,r.jsx)(s.strong,{children:"Admin > Users"})]}),"\n",(0,r.jsxs)(s.li,{children:["Click ",(0,r.jsx)(s.strong,{children:"Add User"})]}),"\n",(0,r.jsxs)(s.li,{children:["Fill in ",(0,r.jsx)(s.strong,{children:"Username"}),", ",(0,r.jsx)(s.strong,{children:"Email"}),", ",(0,r.jsx)(s.strong,{children:"First Name"}),", ",(0,r.jsx)(s.strong,{children:"Last Name"})]}),"\n",(0,r.jsxs)(s.li,{children:["Save, then optionally open the user and assign them to the ",(0,r.jsx)(s.code,{children:"admins"})," group via the ",(0,r.jsx)(s.strong,{children:"Groups"})," tab"]}),"\n",(0,r.jsx)(s.li,{children:"Either share the OTA URL (via the CLI command above) with the user, or have them set up a passkey on first sign-in"}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"summary",children:"Summary"}),"\n",(0,r.jsx)(s.p,{children:"Pocket ID should now have an OIDC client, an admin group, and at least one admin user in that group."}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"3-enabling-pocket-id-in-dashy",children:"3. Enabling Pocket ID in Dashy"}),"\n",(0,r.jsxs)(s.p,{children:["In ",(0,r.jsx)(s.code,{children:"/user-data/conf.yml"}),":"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:"appConfig:\n ...\n disableConfigurationForNonAdmin: true\n auth:\n enableOidc: true\n oidc:\n clientId: 268a3701-e9ec-41f6-bad5-87c78ce87c94\n endpoint: http://localhost:1411\n adminGroup: admins\n scope: openid profile email groups\n"})}),"\n",(0,r.jsx)(s.p,{children:"Where:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"disableConfigurationForNonAdmin"})," - Prevent read/write config access to non-admin users"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"auth.enableOidc"})," - Set the auth mode to OIDC"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"clientId"})," - The client ID from Pocket ID. It's a UUID, so no YAML quoting needed"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"endpoint"})," - Pocket ID's ",(0,r.jsx)(s.code,{children:"APP_URL"}),". Must match exactly. Dashy appends ",(0,r.jsx)(s.code,{children:"/.well-known/openid-configuration"})," itself"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"adminGroup"})," - The group's ",(0,r.jsx)(s.strong,{children:"friendly name"})," (not the display name) that grants admin in Dashy"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"scope"})," - ",(0,r.jsx)(s.code,{children:"groups"})," is needed for the ",(0,r.jsx)(s.code,{children:"adminGroup"})," check. Pocket ID emits the claim by default when the scope is requested"]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"Restart Dashy after editing."}),"\n",(0,r.jsxs)(s.p,{children:["If Pocket ID runs behind a reverse proxy, make sure ",(0,r.jsx)(s.code,{children:"endpoint"})," and ",(0,r.jsx)(s.code,{children:"APP_URL"})," agree, and that Dashy can reach ",(0,r.jsx)(s.code,{children:"endpoint"})," from its container. With both services in Docker, you can either put them on the same network and use the service name, or use the host's exposed port."]}),"\n",(0,r.jsx)(s.p,{children:"Everything should now be fully configured and working \ud83c\udf89\nWhen you load Dashy, you'll be redirected to Pocket ID's sign-in page. Authenticate with your passkey (or paste a one-time code), and you'll land back on Dashy with full access. All of Dashy's client, server and asset endpoints will be locked behind authentication."}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"4-groups-and-visibility",children:"4. Groups and Visibility"}),"\n",(0,r.jsxs)(s.p,{children:["Once the ",(0,r.jsx)(s.code,{children:"groups"})," claim is in the id_token, you can use it to hide or show pages, sections and items. The property name is ",(0,r.jsx)(s.code,{children:"hideForKeycloakUsers"})," / ",(0,r.jsx)(s.code,{children:"showForKeycloakUsers"})," (the name is historical; it works for any OIDC provider, including Pocket ID)."]}),"\n",(0,r.jsxs)(s.p,{children:["To make an Admin section visible only to members of ",(0,r.jsx)(s.code,{children:"admins"}),":"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:"displayData:\n showForKeycloakUsers:\n groups:\n - admins\n"})}),"\n",(0,r.jsxs)(s.p,{children:["Both ",(0,r.jsx)(s.code,{children:"showForKeycloakUsers"})," and ",(0,r.jsx)(s.code,{children:"hideForKeycloakUsers"})," accept lists of ",(0,r.jsx)(s.code,{children:"groups"})," and ",(0,r.jsx)(s.code,{children:"roles"}),". If a user matches an entry they're allowed or excluded as defined."]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:"sections:\n - name: Internal Tools\n displayData:\n showForKeycloakUsers:\n groups: ['admins']\n hideForKeycloakUsers:\n groups: ['guests']\n items:\n - title: Hidden from interns\n displayData:\n hideForKeycloakUsers:\n groups: ['interns']\n"})}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"5-silent-token-renewal-optional",children:"5. Silent token renewal (optional)"}),"\n",(0,r.jsxs)(s.p,{children:["By default, when your token expires Dashy sends you back through Pocket ID's login to get a new one. Set ",(0,r.jsx)(s.code,{children:"enableSilentRenew: true"})," to have Dashy refresh the session quietly in the background instead, using a refresh token:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:" oidc:\n clientId: 268a3701-e9ec-41f6-bad5-87c78ce87c94\n endpoint: http://localhost:1411\n adminGroup: admins\n scope: openid profile email groups\n enableSilentRenew: true\n"})}),"\n",(0,r.jsxs)(s.p,{children:["Dashy adds the ",(0,r.jsx)(s.code,{children:"offline_access"})," scope to its request automatically, and Pocket ID issues a refresh token for it, so nothing needs changing on the Pocket ID side. It's off by default, and if a refresh ever fails Dashy falls back to the normal sign-in. See ",(0,r.jsx)(s.a,{href:"/docs/oidc#silent-token-renewal",children:"silent token renewal"})," for the full notes and caveats."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"troubleshooting-common-pocket-id-issues",children:"Troubleshooting common Pocket ID issues"}),"\n",(0,r.jsx)(s.h4,{id:"pocket-id-wont-start-encryption_key-must-be-at-least-16-bytes-long",children:'Pocket ID won\'t start, "ENCRYPTION_KEY must be at least 16 bytes long"'}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Container restarts on boot, logs show the encryption key error.",(0,r.jsx)(s.br,{}),"\nSolution: Set ",(0,r.jsx)(s.code,{children:"ENCRYPTION_KEY"})," to a string of 16 characters or more. For production generate a real random one (",(0,r.jsx)(s.code,{children:"openssl rand -hex 32"})," gives you 64 hex chars)."]}),"\n",(0,r.jsx)(s.h4,{id:"webauthn-is-not-supported-in-this-browser",children:'"WebAuthn is not supported in this browser"'}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Browser console shows the WebAuthn error and passkey registration or login fails.",(0,r.jsx)(s.br,{}),"\nSolution: WebAuthn needs a secure context. Browsers grant this for HTTPS, or for the literal hostname ",(0,r.jsx)(s.code,{children:"localhost"}),". ",(0,r.jsx)(s.code,{children:"pocketid.lvh.me"}),", ",(0,r.jsx)(s.code,{children:"127.0.0.1"}),", or anything else over HTTP won't qualify. For local testing, set ",(0,r.jsx)(s.code,{children:"APP_URL: http://localhost:1411"})," and access Pocket ID via ",(0,r.jsx)(s.code,{children:"http://localhost:1411"}),". For production, terminate HTTPS in front of Pocket ID."]}),"\n",(0,r.jsx)(s.h4,{id:"cookie-session-has-been-rejected-because-a-non-https-cookie-cant-be-set-as-secure",children:"\"Cookie 'session' has been rejected because a non-HTTPS cookie can't be set as 'secure'\""}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Browser refuses to store Pocket ID's session cookie, so login appears to succeed but the next request comes back as not signed in.",(0,r.jsx)(s.br,{}),"\nSolution: Same root cause as the WebAuthn one. Pocket ID always sets cookies with the ",(0,r.jsx)(s.code,{children:"Secure"})," flag, which browsers will only accept on HTTPS or on ",(0,r.jsx)(s.code,{children:"localhost"}),". Switch to ",(0,r.jsx)(s.code,{children:"http://localhost"})," or use HTTPS."]}),"\n",(0,r.jsx)(s.h4,{id:"one-time-access-token-returns-token-is-invalid-or-expired",children:'One-time access token returns "Token is invalid or expired"'}),"\n",(0,r.jsxs)(s.p,{children:["Problem: The CLI gave you a URL, but visiting it returns the invalid/expired error.",(0,r.jsx)(s.br,{}),"\nSolution: OTA tokens are single-use and expire after 1 hour. If you've already used the URL or it's stale, generate a fresh one: ",(0,r.jsx)(s.code,{children:"docker compose exec pocket-id /app/pocket-id one-time-access-token "}),'. The earlier "Cookie rejected as Secure" issue can also cause this to look like a token problem because the session cookie from the OTA exchange never persisted.']}),"\n",(0,r.jsx)(s.h4,{id:"logged-in-but-config-saves-return-403",children:"Logged in but config saves return 403"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: User authenticates fine, but saving the dashboard returns 403.",(0,r.jsx)(s.br,{}),"\nSolution: The ",(0,r.jsx)(s.code,{children:"groups"})," claim isn't matching. Two things to check. First, that ",(0,r.jsx)(s.code,{children:"adminGroup"})," in ",(0,r.jsx)(s.code,{children:"conf.yml"})," equals the group's ",(0,r.jsx)(s.strong,{children:"friendly name"}),", not the display name. Second, that the user is actually in the group via ",(0,r.jsx)(s.strong,{children:"Admin > Users > [user] > Groups"}),". You can decode the id_token at ",(0,r.jsx)(s.a,{href:"https://jwt.io",children:"jwt.io"})," (take it from localStorage, key ",(0,r.jsx)(s.code,{children:"ID_TOKEN"}),") to see what's in the claim."]}),"\n",(0,r.jsx)(s.h4,{id:"redirect-uri-mismatch",children:"Redirect URI mismatch"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Pocket ID returns an ",(0,r.jsx)(s.code,{children:"invalid_request"})," for the redirect URI on the authorize step.",(0,r.jsx)(s.br,{}),"\nSolution: Pocket ID matches callback URLs exactly. Register both the bare URL and the trailing-slash variant (",(0,r.jsx)(s.code,{children:"http://localhost:4000"})," and ",(0,r.jsx)(s.code,{children:"http://localhost:4000/"}),") on the OIDC client, and make sure the scheme matches what Dashy is actually served on."]}),"\n",(0,r.jsx)(s.h4,{id:"dashy-server-cant-reach-pocket-id",children:"Dashy server can't reach Pocket ID"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Auth'd API calls return 401 and Dashy logs show fetch errors for ",(0,r.jsx)(s.code,{children:".well-known/openid-configuration"}),".",(0,r.jsx)(s.br,{}),"\nSolution: ",(0,r.jsx)(s.code,{children:"endpoint"})," must be reachable from inside the Dashy container. If both run in Docker, share a network and use service names, or use ",(0,r.jsx)(s.code,{children:"network_mode: service:pocket-id"})," (then ",(0,r.jsx)(s.code,{children:"localhost"})," inside Dashy resolves to Pocket ID). Test with ",(0,r.jsx)(s.code,{children:'docker exec wget -qO- "$ENDPOINT/.well-known/openid-configuration"'}),"."]}),"\n",(0,r.jsx)(s.h4,{id:"issuer-mismatch-on-token-verification",children:"Issuer mismatch on token verification"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Dashy server logs show ",(0,r.jsx)(s.code,{children:'unexpected "iss" claim value'}),". The token's ",(0,r.jsx)(s.code,{children:"iss"})," claim doesn't match Dashy's ",(0,r.jsx)(s.code,{children:"endpoint"}),".",(0,r.jsx)(s.br,{}),"\nSolution: Pocket ID puts ",(0,r.jsx)(s.code,{children:"APP_URL"})," straight into the issuer. Make sure ",(0,r.jsx)(s.code,{children:"APP_URL"}),", ",(0,r.jsx)(s.code,{children:"endpoint"})," in Dashy's ",(0,r.jsx)(s.code,{children:"conf.yml"}),", and the URL the browser uses are all the same string (same scheme, host, and port)."]}),"\n",(0,r.jsx)(s.h4,{id:"first-admin-cant-sign-in-no-setup-page-appears",children:"First admin can't sign in, no setup page appears"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Visiting Pocket ID just shows a login form, no setup wizard.",(0,r.jsx)(s.br,{}),"\nSolution: A previous Pocket ID instance may have created users in the database. Either log in as one of those users, or wipe ",(0,r.jsx)(s.code,{children:"data/"})," and start fresh: ",(0,r.jsx)(s.code,{children:"docker compose down && rm -rf data && docker compose up -d"}),". (The ",(0,r.jsx)(s.code,{children:"data"})," dir is owned by the container user, so you may need sudo or use a throwaway container to clean up.)"]}),"\n",(0,r.jsx)(s.h4,{id:"token-expired--clock-skew",children:"Token expired / clock skew"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: 401s with ",(0,r.jsx)(s.code,{children:'"exp" claim timestamp check failed'}),", even just after login.",(0,r.jsx)(s.br,{}),"\nSolution: Dashy allows 30 seconds of drift. Sync clocks (NTP) on both hosts. Container clocks follow their host, so it's almost always the host that's drifted."]}),"\n",(0,r.jsx)(s.h4,{id:"config-change-to-authoidc-not-picked-up",children:"Config change to auth.oidc not picked up"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Updated ",(0,r.jsx)(s.code,{children:"clientId"}),", ",(0,r.jsx)(s.code,{children:"endpoint"}),", ",(0,r.jsx)(s.code,{children:"adminGroup"})," or ",(0,r.jsx)(s.code,{children:"scope"})," in ",(0,r.jsx)(s.code,{children:"conf.yml"}),", but Dashy still uses the old values.",(0,r.jsx)(s.br,{}),"\nSolution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under ",(0,r.jsx)(s.code,{children:"auth.oidc"}),"."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"how-it-works",children:"How it Works"}),"\n",(0,r.jsx)(s.p,{children:"If you're a developer or contributor looking to understand or make changes to Dashy's OIDC implementation, the following outlines how it's wired together."}),"\n",(0,r.jsx)(s.p,{children:"The same OIDC pipeline backs Pocket ID, Authelia, Authentik, Keycloak, Zitadel, and any other generic OIDC provider. The only Pocket ID specific quirk is that it's passkey-first; everything on the Dashy side is shared."}),"\n",(0,r.jsx)(s.h3,{id:"client-side",children:"Client side"}),"\n",(0,r.jsxs)(s.p,{children:["Boot starts in ",(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/main.js",children:(0,r.jsx)(s.code,{children:"src/main.js"})}),". After the initial ",(0,r.jsx)(s.code,{children:"/conf.yml"})," fetch parses the auth block, ",(0,r.jsx)(s.code,{children:"isOidcEnabled()"})," decides whether to lazily import ",(0,r.jsx)(s.a,{href:"https://github.com/authts/oidc-client-ts",children:(0,r.jsx)(s.code,{children:"oidc-client-ts"})})," and call ",(0,r.jsx)(s.code,{children:"initOidcAuth()"}),"."]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/OidcAuth.js",children:(0,r.jsx)(s.code,{children:"src/utils/auth/OidcAuth.js"})})," wraps ",(0,r.jsx)(s.code,{children:"oidc-client-ts"}),". On load it inspects the URL: if it sees a ",(0,r.jsx)(s.code,{children:"?code="})," callback it runs ",(0,r.jsx)(s.code,{children:"userManager.signinCallback()"})," to exchange the code (and PKCE verifier) for tokens, persists the user info, and hard-redirects to ",(0,r.jsx)(s.code,{children:"/"}),". Otherwise it calls ",(0,r.jsx)(s.code,{children:"userManager.getUser()"}),"; if there's no usable session it falls through to ",(0,r.jsx)(s.code,{children:"userManager.signinRedirect()"})," to send the browser to Pocket ID. A short-lived ",(0,r.jsx)(s.code,{children:"sessionStorage"})," guard prevents the redirect loop that would otherwise occur if the IdP returns without a usable user."]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"persistUserInfo()"})," writes the raw ",(0,r.jsx)(s.code,{children:"id_token"}),", the user's ",(0,r.jsx)(s.code,{children:"groups"})," and ",(0,r.jsx)(s.code,{children:"roles"}),", a derived ",(0,r.jsx)(s.code,{children:"isAdmin"})," flag, and a username (falling back through ",(0,r.jsx)(s.code,{children:"preferred_username"}),", ",(0,r.jsx)(s.code,{children:"email"}),", and ",(0,r.jsx)(s.code,{children:"sub"}),") to localStorage. The keys (",(0,r.jsx)(s.code,{children:"ID_TOKEN"}),", ",(0,r.jsx)(s.code,{children:"KEYCLOAK_INFO"}),", ",(0,r.jsx)(s.code,{children:"USERNAME"}),", ",(0,r.jsx)(s.code,{children:"ISADMIN"}),") live in ",(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/config/defaults.js",children:(0,r.jsx)(s.code,{children:"src/utils/config/defaults.js"})}),"; the ",(0,r.jsx)(s.code,{children:"KEYCLOAK_INFO"})," name is historical and reused for all OIDC providers, including Pocket ID."]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/getApiAuthHeader.js",children:(0,r.jsx)(s.code,{children:"src/utils/auth/getApiAuthHeader.js"})})," builds the Authorization header for every internal API call. It does a client-side ",(0,r.jsx)(s.code,{children:"exp"})," check and returns ",(0,r.jsx)(s.code,{children:"null"})," for missing or expired tokens, so the next request triggers a fresh login rather than a 401."]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/IsVisibleToUser.js",children:(0,r.jsx)(s.code,{children:"src/utils/IsVisibleToUser.js"})})," reads ",(0,r.jsx)(s.code,{children:"KEYCLOAK_INFO"})," when evaluating ",(0,r.jsx)(s.code,{children:"showForKeycloakUsers"})," and ",(0,r.jsx)(s.code,{children:"hideForKeycloakUsers"})," rules."]}),"\n",(0,r.jsx)(s.h3,{id:"server-side",children:"Server side"}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/auth-oidc.js",children:(0,r.jsx)(s.code,{children:"services/auth-oidc.js"})})," contains the entire server-side auth surface, in five small pieces:"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"loadOidcSettings()"})," reads ",(0,r.jsx)(s.code,{children:"auth.oidc"})," (or ",(0,r.jsx)(s.code,{children:"auth.keycloak"}),") at boot and returns a normalised ",(0,r.jsx)(s.code,{children:"{ issuer, clientId, adminGroup, adminRole }"}),". For generic OIDC providers the ",(0,r.jsx)(s.code,{children:"issuer"})," is whatever you set as ",(0,r.jsx)(s.code,{children:"endpoint"})," in ",(0,r.jsx)(s.code,{children:"conf.yml"}),", verbatim"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"createOidcMiddleware()"})," returns a Connect middleware. Permissive on no-token requests so the SPA can bootstrap; otherwise it verifies the Bearer token against the issuer's JWKS using ",(0,r.jsx)(s.a,{href:"https://github.com/panva/jose",children:(0,r.jsx)(s.code,{children:"jose"})}),". Checks cover signature, issuer (against the canonical value from the discovery doc), audience (must equal ",(0,r.jsx)(s.code,{children:"clientId"}),"), and expiry, with a 30-second clock-skew tolerance. Sets ",(0,r.jsx)(s.code,{children:"req.auth = { user, isAdmin, claims }"})," on success, ",(0,r.jsx)(s.code,{children:"401"})," on failure"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"getIssuerContext()"})," lazily fetches ",(0,r.jsx)(s.code,{children:".well-known/openid-configuration"})," on first use and wraps ",(0,r.jsx)(s.code,{children:"jwks_uri"})," in ",(0,r.jsx)(s.code,{children:"createRemoteJWKSet"}),", which handles JWKS caching and on-demand key rotation. The result is memoised per-issuer for the life of the process"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"deriveIsAdmin()"})," checks the token's ",(0,r.jsx)(s.code,{children:"groups"})," claim against ",(0,r.jsx)(s.code,{children:"adminGroup"}),", and the top-level ",(0,r.jsx)(s.code,{children:"roles"})," claim against ",(0,r.jsx)(s.code,{children:"adminRole"})," (for Keycloak it also folds in the nested ",(0,r.jsx)(s.code,{children:"realm_access.roles"})," / ",(0,r.jsx)(s.code,{children:"resource_access..roles"})," arrays). Pocket ID emits ",(0,r.jsx)(s.code,{children:"groups"})," natively, so the group path is what's used in practice"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"maybeBootstrapConfig()"})," is the stripped-response helper. When auth is configured, guest access is off, and an unauthenticated request hits the root ",(0,r.jsx)(s.code,{children:"/conf.yml"}),", it returns a minimal copy with only ",(0,r.jsx)(s.code,{children:"appConfig.auth"}),", ",(0,r.jsx)(s.code,{children:"appConfig.enableServiceWorker"}),", and a ",(0,r.jsx)(s.code,{children:"pageInfo.title"})," of ",(0,r.jsx)(s.code,{children:"Login | "}),". Sections, items, hostnames and any other secrets never leave the server"]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/app.js",children:(0,r.jsx)(s.code,{children:"services/app.js"})})," wires it all together. The middleware mounts as ",(0,r.jsx)(s.code,{children:"protectConfig"})," in front of every YAML route and config-mutating route. The ",(0,r.jsx)(s.code,{children:"/*.yml"})," handler sets ",(0,r.jsx)(s.code,{children:"Cache-Control: private, no-store"})," and ",(0,r.jsx)(s.code,{children:"Vary: Authorization"})," whenever auth is configured (so intermediate caches can never mix auth states), then calls ",(0,r.jsx)(s.code,{children:"maybeBootstrapConfig"}),"; a stripped result is sent as-is, otherwise ",(0,r.jsx)(s.code,{children:"res.sendFile"})," serves the full file. ",(0,r.jsx)(s.code,{children:"POST /config-manager/save"})," is additionally guarded by ",(0,r.jsx)(s.code,{children:"requireAdmin"}),", which returns ",(0,r.jsx)(s.code,{children:"401"})," if ",(0,r.jsx)(s.code,{children:"req.auth"})," is unset and ",(0,r.jsx)(s.code,{children:"403"})," if ",(0,r.jsx)(s.code,{children:"req.auth.isAdmin"})," is false."]}),"\n",(0,r.jsx)(s.h3,{id:"visual-overview",children:"Visual Overview"}),"\n",(0,r.jsxs)(s.details,{children:["\n",(0,r.jsx)(s.summary,{children:"End-to-end authentication flow"}),"\n",(0,r.jsx)(s.mermaid,{value:"sequenceDiagram\n autonumber\n actor User\n participant Browser as Browser (Dashy SPA)\n participant Dashy as Dashy Server\n participant P as Pocket ID\n\n Note over Browser,Dashy: 1. Bootstrap (no token yet)\n User->>Browser: Open Dashy\n Browser->>Dashy: GET /conf.yml\n Dashy--\x3e>Browser: 200 stripped conf
(auth block + minimal pageInfo)\n Browser->>Browser: Parse auth.oidc\n\n Note over Browser,P: 2. OIDC login (Authorization Code + PKCE)\n Browser->>P: 302 /authorize
client_id, code_challenge, redirect_uri\n User->>P: Authenticate (passkey or one-time code)\n P--\x3e>Browser: 302 back with ?code=AUTH_CODE\n Browser->>P: POST /api/oidc/token
(code + code_verifier)\n P--\x3e>Browser: id_token + access_token
(groups claim from user's groups)\n Browser->>Browser: Store tokens in localStorage
hard reload to /\n\n Note over Browser,Dashy: 3. Authenticated read\n Browser->>Dashy: GET /conf.yml
Authorization: Bearer id_token\n Dashy->>P: Fetch discovery + JWKS
(lazy on first call, then cached)\n P--\x3e>Dashy: openid-configuration + JWKS\n Dashy->>Dashy: Verify signature / issuer / audience / expiry\n Dashy--\x3e>Browser: 200 full conf.yml\n\n Note over Browser,Dashy: 4. Write request (admin only)\n Browser->>Dashy: POST /config-manager/save
Authorization: Bearer id_token\n Dashy->>Dashy: Verify token, derive isAdmin from
adminGroup claim\n alt isAdmin\n Dashy--\x3e>Browser: 200 saved\n else not admin\n Dashy--\x3e>Browser: 403 Forbidden\n end"}),"\n"]}),"\n",(0,r.jsxs)(s.details,{children:["\n",(0,r.jsx)(s.summary,{children:"Server-side request handling"}),"\n",(0,r.jsx)(s.mermaid,{value:'flowchart TD\n Req([Incoming request to Dashy server])\n Req --\x3e Bearer{Authorization:
Bearer present?}\n\n Bearer -- No --\x3e NoAuth[req.auth unset
pass through]:::neutral\n Bearer -- Yes --\x3e Verify[/Verify JWT against cached JWKS:
signature \xb7 issuer \xb7 audience \xb7 expiry/]\n Verify --\x3e Valid{Valid?}\n Valid -- No --\x3e R401Bad[/401 Unauthorized/]:::err\n Valid -- Yes --\x3e SetAuth[req.auth = user + isAdmin
derived from claims]:::ok\n\n NoAuth --\x3e Route{Endpoint}\n SetAuth --\x3e Route\n\n Route -- "GET /conf.yml" --\x3e ConfGate{req.auth?}\n ConfGate -- No --\x3e Strip[/200 stripped conf
auth + minimal pageInfo/]:::ok\n ConfGate -- Yes --\x3e Full[/200 full conf.yml/]:::ok\n\n Route -- "POST /config-manager/save" --\x3e SaveGate{req.auth
and isAdmin?}\n SaveGate -- No req.auth --\x3e R401Save[/401 Unauthorized/]:::err\n SaveGate -- Not admin --\x3e R403[/403 Forbidden/]:::err\n SaveGate -- Admin --\x3e Saved[/200 saved/]:::ok\n\n classDef ok fill:#bbf7d0,stroke:#16a34a,color:#14532d\n classDef err fill:#fecaca,stroke:#dc2626,color:#7f1d1d\n classDef neutral fill:#dbeafe,stroke:#2563eb,color:#1e3a8a'}),"\n"]})]})}function h(e={}){const{wrapper:s}={...(0,o.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},28453(e,s,n){n.d(s,{R:()=>t,x:()=>c});var i=n(96540);const r={},o=i.createContext(r);function t(e){const s=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function c(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(o.Provider,{value:s},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[2246],{86560(e,s,n){n.r(s),n.d(s,{assets:()=>a,contentTitle:()=>c,default:()=>h,frontMatter:()=>t,metadata:()=>i,toc:()=>d});const i=JSON.parse('{"id":"authentication/pocketid","title":"Pocket ID OIDC","description":"Dashy supports using Pocket ID as its OIDC provider.","source":"@site/docs/authentication/pocketid.md","sourceDirName":"authentication","slug":"/authentication/pocketid","permalink":"/docs/authentication/pocketid","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/authentication/pocketid.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Other Auth Methods","permalink":"/docs/authentication/other-auth-methods"},"next":{"title":"Tailscale","permalink":"/docs/authentication/tailscale"}}');var r=n(74848),o=n(28453);const t={},c="Pocket ID OIDC",a={},d=[{value:"Contents",id:"contents",level:3},{value:"1. Deploy Pocket ID",id:"1-deploy-pocket-id",level:2},{value:"2. Configure Pocket ID",id:"2-configure-pocket-id",level:2},{value:"First-time admin setup",id:"first-time-admin-setup",level:3},{value:"Create the OIDC client",id:"create-the-oidc-client",level:3},{value:"Create the admin group",id:"create-the-admin-group",level:3},{value:"Create test users",id:"create-test-users",level:3},{value:"Summary",id:"summary",level:3},{value:"3. Enabling Pocket ID in Dashy",id:"3-enabling-pocket-id-in-dashy",level:2},{value:"4. Groups and Visibility",id:"4-groups-and-visibility",level:2},{value:"5. Silent token renewal (optional)",id:"5-silent-token-renewal-optional",level:2},{value:"Troubleshooting common Pocket ID issues",id:"troubleshooting-common-pocket-id-issues",level:2},{value:"Pocket ID won't start, "ENCRYPTION_KEY must be at least 16 bytes long"",id:"pocket-id-wont-start-encryption_key-must-be-at-least-16-bytes-long",level:4},{value:""WebAuthn is not supported in this browser"",id:"webauthn-is-not-supported-in-this-browser",level:4},{value:""Cookie 'session' has been rejected because a non-HTTPS cookie can't be set as 'secure'"",id:"cookie-session-has-been-rejected-because-a-non-https-cookie-cant-be-set-as-secure",level:4},{value:"One-time access token returns "Token is invalid or expired"",id:"one-time-access-token-returns-token-is-invalid-or-expired",level:4},{value:"Logged in but config saves return 403",id:"logged-in-but-config-saves-return-403",level:4},{value:"Redirect URI mismatch",id:"redirect-uri-mismatch",level:4},{value:"Dashy server can't reach Pocket ID",id:"dashy-server-cant-reach-pocket-id",level:4},{value:"Issuer mismatch on token verification",id:"issuer-mismatch-on-token-verification",level:4},{value:"First admin can't sign in, no setup page appears",id:"first-admin-cant-sign-in-no-setup-page-appears",level:4},{value:"Token expired / clock skew",id:"token-expired--clock-skew",level:4},{value:"Config change to auth.oidc not picked up",id:"config-change-to-authoidc-not-picked-up",level:4},{value:"How it Works",id:"how-it-works",level:2},{value:"Client side",id:"client-side",level:3},{value:"Server side",id:"server-side",level:3},{value:"Visual Overview",id:"visual-overview",level:3}];function l(e){const s={a:"a",br:"br",code:"code",details:"details",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",mermaid:"mermaid",ol:"ol",p:"p",pre:"pre",strong:"strong",summary:"summary",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.header,{children:(0,r.jsx)(s.h1,{id:"pocket-id-oidc",children:"Pocket ID OIDC"})}),"\n",(0,r.jsxs)(s.p,{children:["Dashy supports using ",(0,r.jsx)(s.a,{href:"https://pocket-id.org/",children:"Pocket ID"})," as its OIDC provider."]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://pocket-id.org/",children:"Pocket ID"})," is a small ",(0,r.jsx)(s.a,{href:"https://github.com/pocket-id/pocket-id",children:"open source"})," identity provider built around passkeys. It's a single Go binary backed by SQLite (or Postgres if you want), no password storage, no email server needed for basic use. Popular in homelab setups where you want SSO across a handful of services without running Authentik or Keycloak."]}),"\n",(0,r.jsx)(s.h3,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#1-deploy-pocket-id",children:"1. Deploy Pocket ID"})}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.a,{href:"#2-configure-pocket-id",children:"2. Configure Pocket ID"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#first-time-admin-setup",children:"First-time admin setup"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#create-the-oidc-client",children:"Create the OIDC client"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#create-the-admin-group",children:"Create the admin group"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#create-test-users",children:"Create test users"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#3-enabling-pocket-id-in-dashy",children:"3. Enabling Pocket ID in Dashy"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#4-groups-and-visibility",children:"4. Groups and Visibility"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#5-silent-token-renewal-optional",children:"5. Silent token renewal (optional)"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#troubleshooting-common-pocket-id-issues",children:"Troubleshooting"})}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.a,{href:"#how-it-works",children:"How it Works"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#client-side",children:"Client side"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#server-side",children:"Server side"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#visual-overview",children:"Visual Overview"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"1-deploy-pocket-id",children:"1. Deploy Pocket ID"}),"\n",(0,r.jsx)(s.p,{children:"A minimal compose:"}),"\n",(0,r.jsxs)(s.details,{children:["\n ",(0,r.jsxs)(s.summary,{children:["Example ",(0,r.jsx)(s.code,{children:"docker-compose.yml"})]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:'name: dashy-pocketid\n\nservices:\n pocket-id:\n image: ghcr.io/pocket-id/pocket-id:v2.7.0\n restart: unless-stopped\n ports:\n - "1411:1411"\n environment:\n APP_URL: http://localhost:1411\n TRUST_PROXY: "false"\n DB_PROVIDER: sqlite\n DB_CONNECTION_STRING: /data/pocket-id.db\n ENCRYPTION_KEY: "local-dev-key-32-bytes-padding!!"\n volumes:\n - ./data:/data\n healthcheck:\n test: ["CMD", "wget", "-qO-", "http://127.0.0.1:1411/healthz"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 30s\n'})}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"A few things worth understanding before you start:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"APP_URL"})," is the URL the browser uses to reach Pocket ID. It must match exactly, since Pocket ID puts that value in the OIDC issuer, in cookie domains, and in the WebAuthn relying party"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"ENCRYPTION_KEY"})," is required and must be at least 16 bytes. Generate a real one with ",(0,r.jsx)(s.code,{children:"openssl rand -hex 32"})," for production; the example above is for local testing only"]}),"\n",(0,r.jsxs)(s.li,{children:["Pocket ID is passkey-only by default. Passkeys (WebAuthn) require a secure context, which means either HTTPS, or a literal ",(0,r.jsx)(s.code,{children:"localhost"})," host. ",(0,r.jsx)(s.code,{children:"pocketid.lvh.me"})," and similar lookalikes don't qualify, only the exact string ",(0,r.jsx)(s.code,{children:"localhost"}),". For production use HTTPS with a real domain; for local testing stick to ",(0,r.jsx)(s.code,{children:"http://localhost"})]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"For production:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Put Pocket ID behind a reverse proxy with HTTPS on a real domain"}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.code,{children:"APP_URL: https://auth.example.com"})]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.code,{children:'TRUST_PROXY: "true"'})," so it honours forwarded headers"]}),"\n",(0,r.jsxs)(s.li,{children:["Use a 32-byte random ",(0,r.jsx)(s.code,{children:"ENCRYPTION_KEY"})]}),"\n",(0,r.jsxs)(s.li,{children:["Consider swapping ",(0,r.jsx)(s.code,{children:"DB_PROVIDER"})," to ",(0,r.jsx)(s.code,{children:"postgres"})," for redundancy"]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"Bring Pocket ID up:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:"docker compose up -d pocket-id\n"})}),"\n",(0,r.jsxs)(s.p,{children:["Wait for it to be healthy (",(0,r.jsx)(s.code,{children:"curl -sf http://localhost:1411/healthz"}),"), then continue."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"2-configure-pocket-id",children:"2. Configure Pocket ID"}),"\n",(0,r.jsx)(s.h3,{id:"first-time-admin-setup",children:"First-time admin setup"}),"\n",(0,r.jsx)(s.p,{children:"There's no env-var bootstrap. You create the first admin through the UI, or via SQLite if you want to script it."}),"\n",(0,r.jsx)(s.p,{children:"Through the UI:"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Open ",(0,r.jsx)(s.code,{children:"http://localhost:1411"})," in a browser"]}),"\n",(0,r.jsx)(s.li,{children:"The setup wizard prompts for a first admin (username, email, name)"}),"\n",(0,r.jsx)(s.li,{children:"After creating the user, register a passkey when prompted"}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"If you can't easily register a passkey (no compatible device, or running headless), insert the admin user via SQLite and then use the CLI to get a one-time login URL:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:'docker compose exec pocket-id sh -c \'\n apk add --no-cache sqlite >/dev/null 2>&1\n sqlite3 /data/pocket-id.db "\n INSERT INTO users (id, created_at, updated_at, username, email, first_name, last_name, display_name, is_admin, email_verified)\n VALUES (\\"admin-uuid-001\\", datetime(\\"now\\"), datetime(\\"now\\"), \\"pocketid-admin\\", \\"admin@example.com\\", \\"Pocket ID\\", \\"Admin\\", \\"Admin\\", 1, 1);\n "\n\'\n\ndocker compose exec pocket-id /app/pocket-id one-time-access-token pocketid-admin\n'})}),"\n",(0,r.jsxs)(s.p,{children:["The CLI prints a URL like ",(0,r.jsx)(s.code,{children:"http://localhost:1411/lc/"}),". Open it in a browser to log in. The token is single-use and expires in 1 hour. Once signed in, register a passkey under ",(0,r.jsx)(s.strong,{children:"Settings > Passkeys"})," so you don't need to keep generating OTA tokens."]}),"\n",(0,r.jsx)(s.h3,{id:"create-the-oidc-client",children:"Create the OIDC client"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Open ",(0,r.jsx)(s.strong,{children:"Admin > OIDC Clients"})]}),"\n",(0,r.jsxs)(s.li,{children:["Click ",(0,r.jsx)(s.strong,{children:"Add OIDC Client"})]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.strong,{children:"Name"})," to ",(0,r.jsx)(s.code,{children:"Dashy"})]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.strong,{children:"Callback URLs"})," to both:\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.code,{children:"http://localhost:4000"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.code,{children:"http://localhost:4000/"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.strong,{children:"Logout Callback URLs"})," to ",(0,r.jsx)(s.code,{children:"http://localhost:4000"})]}),"\n",(0,r.jsxs)(s.li,{children:["Turn ",(0,r.jsx)(s.strong,{children:"Public Client"})," on"]}),"\n",(0,r.jsxs)(s.li,{children:["Turn ",(0,r.jsx)(s.strong,{children:"PKCE"})," on"]}),"\n",(0,r.jsxs)(s.li,{children:["Click ",(0,r.jsx)(s.strong,{children:"Save"})]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["Pocket ID generates a client ID (a UUID, e.g. ",(0,r.jsx)(s.code,{children:"268a3701-e9ec-41f6-bad5-87c78ce87c94"}),"). Copy it; you'll need it for Dashy's config."]}),"\n",(0,r.jsx)(s.h3,{id:"create-the-admin-group",children:"Create the admin group"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Open ",(0,r.jsx)(s.strong,{children:"Admin > User Groups"})]}),"\n",(0,r.jsxs)(s.li,{children:["Click ",(0,r.jsx)(s.strong,{children:"Add Group"})]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.strong,{children:"Name"})," to ",(0,r.jsx)(s.code,{children:"Dashy admins"})," (or anything you like)"]}),"\n",(0,r.jsxs)(s.li,{children:["Set ",(0,r.jsx)(s.strong,{children:"Friendly name"})," to ",(0,r.jsx)(s.code,{children:"admins"})," (this is what Pocket ID emits in the OIDC ",(0,r.jsx)(s.code,{children:"groups"})," claim, and what Dashy matches against)"]}),"\n",(0,r.jsxs)(s.li,{children:["Click ",(0,r.jsx)(s.strong,{children:"Save"})]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"create-test-users",children:"Create test users"}),"\n",(0,r.jsxs)(s.ol,{children:["\n",(0,r.jsxs)(s.li,{children:["Open ",(0,r.jsx)(s.strong,{children:"Admin > Users"})]}),"\n",(0,r.jsxs)(s.li,{children:["Click ",(0,r.jsx)(s.strong,{children:"Add User"})]}),"\n",(0,r.jsxs)(s.li,{children:["Fill in ",(0,r.jsx)(s.strong,{children:"Username"}),", ",(0,r.jsx)(s.strong,{children:"Email"}),", ",(0,r.jsx)(s.strong,{children:"First Name"}),", ",(0,r.jsx)(s.strong,{children:"Last Name"})]}),"\n",(0,r.jsxs)(s.li,{children:["Save, then optionally open the user and assign them to the ",(0,r.jsx)(s.code,{children:"admins"})," group via the ",(0,r.jsx)(s.strong,{children:"Groups"})," tab"]}),"\n",(0,r.jsx)(s.li,{children:"Either share the OTA URL (via the CLI command above) with the user, or have them set up a passkey on first sign-in"}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"summary",children:"Summary"}),"\n",(0,r.jsx)(s.p,{children:"Pocket ID should now have an OIDC client, an admin group, and at least one admin user in that group."}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"3-enabling-pocket-id-in-dashy",children:"3. Enabling Pocket ID in Dashy"}),"\n",(0,r.jsxs)(s.p,{children:["In ",(0,r.jsx)(s.code,{children:"/user-data/conf.yml"}),":"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:"appConfig:\n ...\n disableConfigurationForNonAdmin: true\n auth:\n enableOidc: true\n oidc:\n clientId: 268a3701-e9ec-41f6-bad5-87c78ce87c94\n endpoint: http://localhost:1411\n adminGroup: admins\n scope: openid profile email groups\n"})}),"\n",(0,r.jsx)(s.p,{children:"Where:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"disableConfigurationForNonAdmin"})," - Prevent read/write config access to non-admin users"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"auth.enableOidc"})," - Set the auth mode to OIDC"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"clientId"})," - The client ID from Pocket ID. It's a UUID, so no YAML quoting needed"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"endpoint"})," - Pocket ID's ",(0,r.jsx)(s.code,{children:"APP_URL"}),". Must match exactly. Dashy appends ",(0,r.jsx)(s.code,{children:"/.well-known/openid-configuration"})," itself"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"adminGroup"})," - The group's ",(0,r.jsx)(s.strong,{children:"friendly name"})," (not the display name) that grants admin in Dashy"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"scope"})," - ",(0,r.jsx)(s.code,{children:"groups"})," is needed for the ",(0,r.jsx)(s.code,{children:"adminGroup"})," check. Pocket ID emits the claim by default when the scope is requested"]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"Restart Dashy after editing."}),"\n",(0,r.jsxs)(s.p,{children:["If Pocket ID runs behind a reverse proxy, make sure ",(0,r.jsx)(s.code,{children:"endpoint"})," and ",(0,r.jsx)(s.code,{children:"APP_URL"})," agree, and that Dashy can reach ",(0,r.jsx)(s.code,{children:"endpoint"})," from its container. With both services in Docker, you can either put them on the same network and use the service name, or use the host's exposed port."]}),"\n",(0,r.jsx)(s.p,{children:"Everything should now be fully configured and working \ud83c\udf89\nWhen you load Dashy, you'll be redirected to Pocket ID's sign-in page. Authenticate with your passkey (or paste a one-time code), and you'll land back on Dashy with full access. All of Dashy's client, server and asset endpoints will be locked behind authentication."}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"4-groups-and-visibility",children:"4. Groups and Visibility"}),"\n",(0,r.jsxs)(s.p,{children:["Once the ",(0,r.jsx)(s.code,{children:"groups"})," claim is in the id_token, you can use it to hide or show pages, sections and items. The property name is ",(0,r.jsx)(s.code,{children:"hideForKeycloakUsers"})," / ",(0,r.jsx)(s.code,{children:"showForKeycloakUsers"})," (the name is historical; it works for any OIDC provider, including Pocket ID)."]}),"\n",(0,r.jsxs)(s.p,{children:["To make an Admin section visible only to members of ",(0,r.jsx)(s.code,{children:"admins"}),":"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:"displayData:\n showForKeycloakUsers:\n groups:\n - admins\n"})}),"\n",(0,r.jsxs)(s.p,{children:["Both ",(0,r.jsx)(s.code,{children:"showForKeycloakUsers"})," and ",(0,r.jsx)(s.code,{children:"hideForKeycloakUsers"})," accept lists of ",(0,r.jsx)(s.code,{children:"groups"})," and ",(0,r.jsx)(s.code,{children:"roles"}),". If a user matches an entry they're allowed or excluded as defined."]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:"sections:\n - name: Internal Tools\n displayData:\n showForKeycloakUsers:\n groups: ['admins']\n hideForKeycloakUsers:\n groups: ['guests']\n items:\n - title: Hidden from interns\n displayData:\n hideForKeycloakUsers:\n groups: ['interns']\n"})}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"5-silent-token-renewal-optional",children:"5. Silent token renewal (optional)"}),"\n",(0,r.jsxs)(s.p,{children:["By default, when your token expires Dashy sends you back through Pocket ID's login to get a new one. Set ",(0,r.jsx)(s.code,{children:"enableSilentRenew: true"})," to have Dashy refresh the session quietly in the background instead, using a refresh token:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-yaml",children:" oidc:\n clientId: 268a3701-e9ec-41f6-bad5-87c78ce87c94\n endpoint: http://localhost:1411\n adminGroup: admins\n scope: openid profile email groups\n enableSilentRenew: true\n"})}),"\n",(0,r.jsxs)(s.p,{children:["Dashy adds the ",(0,r.jsx)(s.code,{children:"offline_access"})," scope to its request automatically, and Pocket ID issues a refresh token for it, so nothing needs changing on the Pocket ID side. It's off by default, and if a refresh ever fails Dashy falls back to the normal sign-in. See ",(0,r.jsx)(s.a,{href:"/docs/oidc#silent-token-renewal",children:"silent token renewal"})," for the full notes and caveats."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"troubleshooting-common-pocket-id-issues",children:"Troubleshooting common Pocket ID issues"}),"\n",(0,r.jsx)(s.h4,{id:"pocket-id-wont-start-encryption_key-must-be-at-least-16-bytes-long",children:'Pocket ID won\'t start, "ENCRYPTION_KEY must be at least 16 bytes long"'}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Container restarts on boot, logs show the encryption key error.",(0,r.jsx)(s.br,{}),"\nSolution: Set ",(0,r.jsx)(s.code,{children:"ENCRYPTION_KEY"})," to a string of 16 characters or more. For production generate a real random one (",(0,r.jsx)(s.code,{children:"openssl rand -hex 32"})," gives you 64 hex chars)."]}),"\n",(0,r.jsx)(s.h4,{id:"webauthn-is-not-supported-in-this-browser",children:'"WebAuthn is not supported in this browser"'}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Browser console shows the WebAuthn error and passkey registration or login fails.",(0,r.jsx)(s.br,{}),"\nSolution: WebAuthn needs a secure context. Browsers grant this for HTTPS, or for the literal hostname ",(0,r.jsx)(s.code,{children:"localhost"}),". ",(0,r.jsx)(s.code,{children:"pocketid.lvh.me"}),", ",(0,r.jsx)(s.code,{children:"127.0.0.1"}),", or anything else over HTTP won't qualify. For local testing, set ",(0,r.jsx)(s.code,{children:"APP_URL: http://localhost:1411"})," and access Pocket ID via ",(0,r.jsx)(s.code,{children:"http://localhost:1411"}),". For production, terminate HTTPS in front of Pocket ID."]}),"\n",(0,r.jsx)(s.h4,{id:"cookie-session-has-been-rejected-because-a-non-https-cookie-cant-be-set-as-secure",children:"\"Cookie 'session' has been rejected because a non-HTTPS cookie can't be set as 'secure'\""}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Browser refuses to store Pocket ID's session cookie, so login appears to succeed but the next request comes back as not signed in.",(0,r.jsx)(s.br,{}),"\nSolution: Same root cause as the WebAuthn one. Pocket ID always sets cookies with the ",(0,r.jsx)(s.code,{children:"Secure"})," flag, which browsers will only accept on HTTPS or on ",(0,r.jsx)(s.code,{children:"localhost"}),". Switch to ",(0,r.jsx)(s.code,{children:"http://localhost"})," or use HTTPS."]}),"\n",(0,r.jsx)(s.h4,{id:"one-time-access-token-returns-token-is-invalid-or-expired",children:'One-time access token returns "Token is invalid or expired"'}),"\n",(0,r.jsxs)(s.p,{children:["Problem: The CLI gave you a URL, but visiting it returns the invalid/expired error.",(0,r.jsx)(s.br,{}),"\nSolution: OTA tokens are single-use and expire after 1 hour. If you've already used the URL or it's stale, generate a fresh one: ",(0,r.jsx)(s.code,{children:"docker compose exec pocket-id /app/pocket-id one-time-access-token "}),'. The earlier "Cookie rejected as Secure" issue can also cause this to look like a token problem because the session cookie from the OTA exchange never persisted.']}),"\n",(0,r.jsx)(s.h4,{id:"logged-in-but-config-saves-return-403",children:"Logged in but config saves return 403"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: User authenticates fine, but saving the dashboard returns 403.",(0,r.jsx)(s.br,{}),"\nSolution: The ",(0,r.jsx)(s.code,{children:"groups"})," claim isn't matching. Two things to check. First, that ",(0,r.jsx)(s.code,{children:"adminGroup"})," in ",(0,r.jsx)(s.code,{children:"conf.yml"})," equals the group's ",(0,r.jsx)(s.strong,{children:"friendly name"}),", not the display name. Second, that the user is actually in the group via ",(0,r.jsx)(s.strong,{children:"Admin > Users > [user] > Groups"}),". You can decode the id_token at ",(0,r.jsx)(s.a,{href:"https://jwt.io",children:"jwt.io"})," (take it from localStorage, key ",(0,r.jsx)(s.code,{children:"ID_TOKEN"}),") to see what's in the claim."]}),"\n",(0,r.jsx)(s.h4,{id:"redirect-uri-mismatch",children:"Redirect URI mismatch"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Pocket ID returns an ",(0,r.jsx)(s.code,{children:"invalid_request"})," for the redirect URI on the authorize step.",(0,r.jsx)(s.br,{}),"\nSolution: Pocket ID matches callback URLs exactly. Register both the bare URL and the trailing-slash variant (",(0,r.jsx)(s.code,{children:"http://localhost:4000"})," and ",(0,r.jsx)(s.code,{children:"http://localhost:4000/"}),") on the OIDC client, and make sure the scheme matches what Dashy is actually served on."]}),"\n",(0,r.jsx)(s.h4,{id:"dashy-server-cant-reach-pocket-id",children:"Dashy server can't reach Pocket ID"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Auth'd API calls return 401 and Dashy logs show fetch errors for ",(0,r.jsx)(s.code,{children:".well-known/openid-configuration"}),".",(0,r.jsx)(s.br,{}),"\nSolution: ",(0,r.jsx)(s.code,{children:"endpoint"})," must be reachable from inside the Dashy container. If both run in Docker, share a network and use service names, or use ",(0,r.jsx)(s.code,{children:"network_mode: service:pocket-id"})," (then ",(0,r.jsx)(s.code,{children:"localhost"})," inside Dashy resolves to Pocket ID). Test with ",(0,r.jsx)(s.code,{children:'docker exec wget -qO- "$ENDPOINT/.well-known/openid-configuration"'}),"."]}),"\n",(0,r.jsx)(s.h4,{id:"issuer-mismatch-on-token-verification",children:"Issuer mismatch on token verification"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Dashy server logs show ",(0,r.jsx)(s.code,{children:'unexpected "iss" claim value'}),". The token's ",(0,r.jsx)(s.code,{children:"iss"})," claim doesn't match Dashy's ",(0,r.jsx)(s.code,{children:"endpoint"}),".",(0,r.jsx)(s.br,{}),"\nSolution: Pocket ID puts ",(0,r.jsx)(s.code,{children:"APP_URL"})," straight into the issuer. Make sure ",(0,r.jsx)(s.code,{children:"APP_URL"}),", ",(0,r.jsx)(s.code,{children:"endpoint"})," in Dashy's ",(0,r.jsx)(s.code,{children:"conf.yml"}),", and the URL the browser uses are all the same string (same scheme, host, and port)."]}),"\n",(0,r.jsx)(s.h4,{id:"first-admin-cant-sign-in-no-setup-page-appears",children:"First admin can't sign in, no setup page appears"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Visiting Pocket ID just shows a login form, no setup wizard.",(0,r.jsx)(s.br,{}),"\nSolution: A previous Pocket ID instance may have created users in the database. Either log in as one of those users, or wipe ",(0,r.jsx)(s.code,{children:"data/"})," and start fresh: ",(0,r.jsx)(s.code,{children:"docker compose down && rm -rf data && docker compose up -d"}),". (The ",(0,r.jsx)(s.code,{children:"data"})," dir is owned by the container user, so you may need sudo or use a throwaway container to clean up.)"]}),"\n",(0,r.jsx)(s.h4,{id:"token-expired--clock-skew",children:"Token expired / clock skew"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: 401s with ",(0,r.jsx)(s.code,{children:'"exp" claim timestamp check failed'}),", even just after login.",(0,r.jsx)(s.br,{}),"\nSolution: Dashy allows 30 seconds of drift. Sync clocks (NTP) on both hosts. Container clocks follow their host, so it's almost always the host that's drifted."]}),"\n",(0,r.jsx)(s.h4,{id:"config-change-to-authoidc-not-picked-up",children:"Config change to auth.oidc not picked up"}),"\n",(0,r.jsxs)(s.p,{children:["Problem: Updated ",(0,r.jsx)(s.code,{children:"clientId"}),", ",(0,r.jsx)(s.code,{children:"endpoint"}),", ",(0,r.jsx)(s.code,{children:"adminGroup"})," or ",(0,r.jsx)(s.code,{children:"scope"})," in ",(0,r.jsx)(s.code,{children:"conf.yml"}),", but Dashy still uses the old values.",(0,r.jsx)(s.br,{}),"\nSolution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under ",(0,r.jsx)(s.code,{children:"auth.oidc"}),"."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"how-it-works",children:"How it Works"}),"\n",(0,r.jsx)(s.p,{children:"If you're a developer or contributor looking to understand or make changes to Dashy's OIDC implementation, the following outlines how it's wired together."}),"\n",(0,r.jsx)(s.p,{children:"The same OIDC pipeline backs Pocket ID, Authelia, Authentik, Keycloak, Zitadel, and any other generic OIDC provider. The only Pocket ID specific quirk is that it's passkey-first; everything on the Dashy side is shared."}),"\n",(0,r.jsx)(s.h3,{id:"client-side",children:"Client side"}),"\n",(0,r.jsxs)(s.p,{children:["Boot starts in ",(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/main.js",children:(0,r.jsx)(s.code,{children:"src/main.js"})}),". After the initial ",(0,r.jsx)(s.code,{children:"/conf.yml"})," fetch parses the auth block, ",(0,r.jsx)(s.code,{children:"isOidcEnabled()"})," decides whether to lazily import ",(0,r.jsx)(s.a,{href:"https://github.com/authts/oidc-client-ts",children:(0,r.jsx)(s.code,{children:"oidc-client-ts"})})," and call ",(0,r.jsx)(s.code,{children:"initOidcAuth()"}),"."]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/OidcAuth.js",children:(0,r.jsx)(s.code,{children:"src/utils/auth/OidcAuth.js"})})," wraps ",(0,r.jsx)(s.code,{children:"oidc-client-ts"}),". On load it inspects the URL: if it sees a ",(0,r.jsx)(s.code,{children:"?code="})," callback it runs ",(0,r.jsx)(s.code,{children:"userManager.signinCallback()"})," to exchange the code (and PKCE verifier) for tokens, persists the user info, and hard-redirects to ",(0,r.jsx)(s.code,{children:"/"}),". Otherwise it calls ",(0,r.jsx)(s.code,{children:"userManager.getUser()"}),"; if there's no usable session it falls through to ",(0,r.jsx)(s.code,{children:"userManager.signinRedirect()"})," to send the browser to Pocket ID. A short-lived ",(0,r.jsx)(s.code,{children:"sessionStorage"})," guard prevents the redirect loop that would otherwise occur if the IdP returns without a usable user."]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"persistUserInfo()"})," writes the raw ",(0,r.jsx)(s.code,{children:"id_token"}),", the user's ",(0,r.jsx)(s.code,{children:"groups"})," and ",(0,r.jsx)(s.code,{children:"roles"}),", a derived ",(0,r.jsx)(s.code,{children:"isAdmin"})," flag, and a username (falling back through ",(0,r.jsx)(s.code,{children:"preferred_username"}),", ",(0,r.jsx)(s.code,{children:"email"}),", and ",(0,r.jsx)(s.code,{children:"sub"}),") to localStorage. The keys (",(0,r.jsx)(s.code,{children:"ID_TOKEN"}),", ",(0,r.jsx)(s.code,{children:"KEYCLOAK_INFO"}),", ",(0,r.jsx)(s.code,{children:"USERNAME"}),", ",(0,r.jsx)(s.code,{children:"ISADMIN"}),") live in ",(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/config/defaults.js",children:(0,r.jsx)(s.code,{children:"src/utils/config/defaults.js"})}),"; the ",(0,r.jsx)(s.code,{children:"KEYCLOAK_INFO"})," name is historical and reused for all OIDC providers, including Pocket ID."]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/auth/getApiAuthHeader.js",children:(0,r.jsx)(s.code,{children:"src/utils/auth/getApiAuthHeader.js"})})," builds the Authorization header for every internal API call. It does a client-side ",(0,r.jsx)(s.code,{children:"exp"})," check and returns ",(0,r.jsx)(s.code,{children:"null"})," for missing or expired tokens, so the next request triggers a fresh login rather than a 401."]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/src/utils/IsVisibleToUser.js",children:(0,r.jsx)(s.code,{children:"src/utils/IsVisibleToUser.js"})})," reads ",(0,r.jsx)(s.code,{children:"KEYCLOAK_INFO"})," when evaluating ",(0,r.jsx)(s.code,{children:"showForKeycloakUsers"})," and ",(0,r.jsx)(s.code,{children:"hideForKeycloakUsers"})," rules."]}),"\n",(0,r.jsx)(s.h3,{id:"server-side",children:"Server side"}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/auth-oidc.js",children:(0,r.jsx)(s.code,{children:"services/auth-oidc.js"})})," contains the entire server-side auth surface, in five small pieces:"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"loadOidcSettings()"})," reads ",(0,r.jsx)(s.code,{children:"auth.oidc"})," (or ",(0,r.jsx)(s.code,{children:"auth.keycloak"}),") at boot and returns a normalised ",(0,r.jsx)(s.code,{children:"{ issuer, clientId, adminGroup, adminRole }"}),". For generic OIDC providers the ",(0,r.jsx)(s.code,{children:"issuer"})," is whatever you set as ",(0,r.jsx)(s.code,{children:"endpoint"})," in ",(0,r.jsx)(s.code,{children:"conf.yml"}),", verbatim"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"createOidcMiddleware()"})," returns a Connect middleware. Permissive on no-token requests so the SPA can bootstrap; otherwise it verifies the Bearer token against the issuer's JWKS using ",(0,r.jsx)(s.a,{href:"https://github.com/panva/jose",children:(0,r.jsx)(s.code,{children:"jose"})}),". Checks cover signature, issuer (against the canonical value from the discovery doc), audience (must equal ",(0,r.jsx)(s.code,{children:"clientId"}),"), and expiry, with a 30-second clock-skew tolerance. Sets ",(0,r.jsx)(s.code,{children:"req.auth = { user, isAdmin, claims }"})," on success, ",(0,r.jsx)(s.code,{children:"401"})," on failure"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"getIssuerContext()"})," lazily fetches ",(0,r.jsx)(s.code,{children:".well-known/openid-configuration"})," on first use and wraps ",(0,r.jsx)(s.code,{children:"jwks_uri"})," in ",(0,r.jsx)(s.code,{children:"createRemoteJWKSet"}),", which handles JWKS caching and on-demand key rotation. The result is memoised per-issuer for the life of the process"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"deriveIsAdmin()"})," checks the token's ",(0,r.jsx)(s.code,{children:"groups"})," claim against ",(0,r.jsx)(s.code,{children:"adminGroup"}),", and the top-level ",(0,r.jsx)(s.code,{children:"roles"})," claim against ",(0,r.jsx)(s.code,{children:"adminRole"})," (for Keycloak it also folds in the nested ",(0,r.jsx)(s.code,{children:"realm_access.roles"})," / ",(0,r.jsx)(s.code,{children:"resource_access..roles"})," arrays). Pocket ID emits ",(0,r.jsx)(s.code,{children:"groups"})," natively, so the group path is what's used in practice"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"maybeBootstrapConfig()"})," is the stripped-response helper. When auth is configured, guest access is off, and an unauthenticated request hits the root ",(0,r.jsx)(s.code,{children:"/conf.yml"}),", it returns a minimal copy with only ",(0,r.jsx)(s.code,{children:"appConfig.auth"}),", ",(0,r.jsx)(s.code,{children:"appConfig.enableServiceWorker"}),", and a ",(0,r.jsx)(s.code,{children:"pageInfo.title"})," of ",(0,r.jsx)(s.code,{children:"Login | "}),". Sections, items, hostnames and any other secrets never leave the server"]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://github.com/lissy93/dashy/blob/4.1.5/services/app.js",children:(0,r.jsx)(s.code,{children:"services/app.js"})})," wires it all together. The middleware mounts as ",(0,r.jsx)(s.code,{children:"protectConfig"})," in front of every YAML route and config-mutating route. The ",(0,r.jsx)(s.code,{children:"/*.yml"})," handler sets ",(0,r.jsx)(s.code,{children:"Cache-Control: private, no-store"})," and ",(0,r.jsx)(s.code,{children:"Vary: Authorization"})," whenever auth is configured (so intermediate caches can never mix auth states), then calls ",(0,r.jsx)(s.code,{children:"maybeBootstrapConfig"}),"; a stripped result is sent as-is, otherwise ",(0,r.jsx)(s.code,{children:"res.sendFile"})," serves the full file. ",(0,r.jsx)(s.code,{children:"POST /config-manager/save"})," is additionally guarded by ",(0,r.jsx)(s.code,{children:"requireAdmin"}),", which returns ",(0,r.jsx)(s.code,{children:"401"})," if ",(0,r.jsx)(s.code,{children:"req.auth"})," is unset and ",(0,r.jsx)(s.code,{children:"403"})," if ",(0,r.jsx)(s.code,{children:"req.auth.isAdmin"})," is false."]}),"\n",(0,r.jsx)(s.h3,{id:"visual-overview",children:"Visual Overview"}),"\n",(0,r.jsxs)(s.details,{children:["\n",(0,r.jsx)(s.summary,{children:"End-to-end authentication flow"}),"\n",(0,r.jsx)(s.mermaid,{value:"sequenceDiagram\n autonumber\n actor User\n participant Browser as Browser (Dashy SPA)\n participant Dashy as Dashy Server\n participant P as Pocket ID\n\n Note over Browser,Dashy: 1. Bootstrap (no token yet)\n User->>Browser: Open Dashy\n Browser->>Dashy: GET /conf.yml\n Dashy--\x3e>Browser: 200 stripped conf
(auth block + minimal pageInfo)\n Browser->>Browser: Parse auth.oidc\n\n Note over Browser,P: 2. OIDC login (Authorization Code + PKCE)\n Browser->>P: 302 /authorize
client_id, code_challenge, redirect_uri\n User->>P: Authenticate (passkey or one-time code)\n P--\x3e>Browser: 302 back with ?code=AUTH_CODE\n Browser->>P: POST /api/oidc/token
(code + code_verifier)\n P--\x3e>Browser: id_token + access_token
(groups claim from user's groups)\n Browser->>Browser: Store tokens in localStorage
hard reload to /\n\n Note over Browser,Dashy: 3. Authenticated read\n Browser->>Dashy: GET /conf.yml
Authorization: Bearer id_token\n Dashy->>P: Fetch discovery + JWKS
(lazy on first call, then cached)\n P--\x3e>Dashy: openid-configuration + JWKS\n Dashy->>Dashy: Verify signature / issuer / audience / expiry\n Dashy--\x3e>Browser: 200 full conf.yml\n\n Note over Browser,Dashy: 4. Write request (admin only)\n Browser->>Dashy: POST /config-manager/save
Authorization: Bearer id_token\n Dashy->>Dashy: Verify token, derive isAdmin from
adminGroup claim\n alt isAdmin\n Dashy--\x3e>Browser: 200 saved\n else not admin\n Dashy--\x3e>Browser: 403 Forbidden\n end"}),"\n"]}),"\n",(0,r.jsxs)(s.details,{children:["\n",(0,r.jsx)(s.summary,{children:"Server-side request handling"}),"\n",(0,r.jsx)(s.mermaid,{value:'flowchart TD\n Req([Incoming request to Dashy server])\n Req --\x3e Bearer{Authorization:
Bearer present?}\n\n Bearer -- No --\x3e NoAuth[req.auth unset
pass through]:::neutral\n Bearer -- Yes --\x3e Verify[/Verify JWT against cached JWKS:
signature \xb7 issuer \xb7 audience \xb7 expiry/]\n Verify --\x3e Valid{Valid?}\n Valid -- No --\x3e R401Bad[/401 Unauthorized/]:::err\n Valid -- Yes --\x3e SetAuth[req.auth = user + isAdmin
derived from claims]:::ok\n\n NoAuth --\x3e Route{Endpoint}\n SetAuth --\x3e Route\n\n Route -- "GET /conf.yml" --\x3e ConfGate{req.auth?}\n ConfGate -- No --\x3e Strip[/200 stripped conf
auth + minimal pageInfo/]:::ok\n ConfGate -- Yes --\x3e Full[/200 full conf.yml/]:::ok\n\n Route -- "POST /config-manager/save" --\x3e SaveGate{req.auth
and isAdmin?}\n SaveGate -- No req.auth --\x3e R401Save[/401 Unauthorized/]:::err\n SaveGate -- Not admin --\x3e R403[/403 Forbidden/]:::err\n SaveGate -- Admin --\x3e Saved[/200 saved/]:::ok\n\n classDef ok fill:#bbf7d0,stroke:#16a34a,color:#14532d\n classDef err fill:#fecaca,stroke:#dc2626,color:#7f1d1d\n classDef neutral fill:#dbeafe,stroke:#2563eb,color:#1e3a8a'}),"\n"]})]})}function h(e={}){const{wrapper:s}={...(0,o.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},28453(e,s,n){n.d(s,{R:()=>t,x:()=>c});var i=n(96540);const r={},o=i.createContext(r);function t(e){const s=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function c(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(o.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/9b4185c1.f7acbc46.js b/assets/js/9b4185c1.a8865e57.js similarity index 99% rename from assets/js/9b4185c1.f7acbc46.js rename to assets/js/9b4185c1.a8865e57.js index 87141e65..4ed84acf 100644 --- a/assets/js/9b4185c1.f7acbc46.js +++ b/assets/js/9b4185c1.a8865e57.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[5330],{69356(e,s,n){n.r(s),n.d(s,{assets:()=>c,contentTitle:()=>d,default:()=>h,frontMatter:()=>o,metadata:()=>i,toc:()=>a});const i=JSON.parse('{"id":"privacy","title":"Privacy & Security","description":"Dashy was built with privacy in mind.","source":"@site/docs/privacy.md","sourceDirName":".","slug":"/privacy","permalink":"/docs/privacy","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/privacy.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Development Guides","permalink":"/docs/development-guides"},"next":{"title":"Credits","permalink":"/docs/credits"}}');var r=n(74848),t=n(28453);const o={},d="Privacy & Security",c={},a=[{value:"Contents",id:"contents",level:2},{value:"Browser Storage",id:"browser-storage",level:2},{value:"Cookies",id:"cookies",level:3},{value:"Session Storage",id:"session-storage",level:3},{value:"Local Storage",id:"local-storage",level:3},{value:"Deleting Stored Data",id:"deleting-stored-data",level:3},{value:"External Requests",id:"external-requests",level:2},{value:"Icons",id:"icons",level:3},{value:"Font Awesome",id:"font-awesome",level:4},{value:"Material Design Icons",id:"material-design-icons",level:4},{value:"Favicon Fetching",id:"favicon-fetching",level:4},{value:"Generative Icons",id:"generative-icons",level:4},{value:"Self-Hosted Icons",id:"self-hosted-icons",level:4},{value:"Other Icons",id:"other-icons",level:4},{value:"Web Assets",id:"web-assets",level:4},{value:"Themes",id:"themes",level:3},{value:"Status Checking",id:"status-checking",level:3},{value:"Update Checks",id:"update-checks",level:3},{value:"Cloud Backup",id:"cloud-backup",level:3},{value:"Web Search",id:"web-search",level:3},{value:"Initialization Page",id:"initialization-page",level:3},{value:"Anonymous Error Reporting",id:"anonymous-error-reporting",level:3},{value:"Widgets",id:"widgets",level:3},{value:"Dependencies",id:"dependencies",level:2},{value:"Securing your Environment",id:"securing-your-environment",level:2},{value:"Security Features",id:"security-features",level:2},{value:"Subresource Integrity",id:"subresource-integrity",level:3},{value:"SSL",id:"ssl",level:3},{value:"Authentication",id:"authentication",level:3},{value:"Configuration Lockdown",id:"configuration-lockdown",level:3},{value:"Disabling Features",id:"disabling-features",level:3},{value:"Threat Model",id:"threat-model",level:2},{value:"Intended Deployment",id:"intended-deployment",level:3},{value:"Trust Boundaries",id:"trust-boundaries",level:3},{value:"Assets",id:"assets",level:3},{value:"When Dashy is NOT the Right Choice",id:"when-dashy-is-not-the-right-choice",level:3},{value:"Known Limitations",id:"known-limitations",level:2},{value:"Update & Patch Policy",id:"update--patch-policy",level:2},{value:"Reporting a Security Issue",id:"reporting-a-security-issue",level:2}];function l(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",br:"br",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",kbd:"kbd",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.header,{children:(0,r.jsx)(s.h1,{id:"privacy--security",children:"Privacy & Security"})}),"\n",(0,r.jsx)(s.p,{children:"Dashy was built with privacy in mind.\nSelf-hosting your own apps and services is a great way to protect yourself from the mass data collection employed by big tech companies, and Dashy was designed to make self-hosting easier."}),"\n",(0,r.jsx)(s.p,{children:"Dashy operates on the premise, that:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"No external data requests should ever be made, unless explicitly enabled by the user"}),"\n",(0,r.jsx)(s.li,{children:"All code is 100% open source, clearly documented and intended to be easily auditable"}),"\n",(0,r.jsx)(s.li,{children:"Privacy-respecting by default. No premium features, analytics, tracking or ads"}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"This document outlines all network requests, data storage requirements, security configurations and data handling processes."}),"\n",(0,r.jsx)(s.admonition,{type:"tip",children:(0,r.jsxs)(s.p,{children:["Btw (shameless plug), if you care about your privacy, you might also like ",(0,r.jsx)(s.a,{href:"https://github.com/Lissy93/awesome-privacy/",children:"awesome-privacy"}),"!",(0,r.jsx)(s.br,{})]})}),"\n",(0,r.jsx)(s.h2,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["Privacy\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#browser-storage",children:"Browser Storage"})}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.a,{href:"#external-requests",children:"External Requests"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#icons",children:"Icons"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#themes",children:"Themes"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#status-checking",children:"Status Checking"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#update-checks",children:"Update Checks"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#cloud-backup",children:"Cloud Backup"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#web-search",children:"Web Search"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#initialization-page",children:"Initialization Page"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#anonymous-error-reporting",children:"Anonymous Error Reporting"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#widgets",children:"Widgets"})}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(s.li,{children:["Security\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#dependencies",children:"Dependencies"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#securing-your-environment",children:"Securing your Environment"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#security-features",children:"Security Features"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#threat-model",children:"Threat Model"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#known-limitations",children:"Known Limitations"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#update--patch-policy",children:"Update & Patch Policy"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#reporting-a-security-issue",children:"Reporting a Security Issue"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"browser-storage",children:"Browser Storage"}),"\n",(0,r.jsx)(s.p,{children:"In order for user preferences to be persisted some data is stored locally in your browsers storage.\nNo personal info is kept here, none of this data can be accessed by other domains, no data is ever sent to any server without your prior consent, and all data is removed when no longer needed unless you delete it sooner."}),"\n",(0,r.jsx)(s.p,{children:"The following section outlines all data that is stored in the browsers, as cookies, session storage or local storage."}),"\n",(0,r.jsx)(s.h3,{id:"cookies",children:"Cookies"}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies",children:"Cookies"})," will expire after their pre-defined lifetime.\nDashy uses cookies for authentication, when enabled."]}),"\n"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"AUTH_TOKEN"})," - A unique token, generated from a hash of users credentials, to verify they are authenticated. Only used when auth is enabled."]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"session-storage",children:"Session Storage"}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage",children:"Session storage"})," is deleted when the current session ends (tab / window is closed).\nDashy uses session storage service worker status and error list."]}),"\n"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"SW_STATUS"})," - The current status of any service workers"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"ERROR_LOG"})," - List of recent errors"]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"local-storage",children:"Local Storage"}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage",children:"Local storage"})," is persisted between sessions, and only deleted when manually removed.\nDashy can use local storage to keep track of your preferences."]}),"\n"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"LANGUAGE"})," - The locale to show app text in"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"HIDE_INFO_NOTIFICATION"})," - Set to true once user dismissed welcome message, so that it's not shown again"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"LAYOUT_ORIENTATION"})," - Preferred section layout, either auto, horizontal, vertical or masonry"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"COLLAPSE_STATE"})," - Remembers which sections are collapsed"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"ICON_SIZE"})," - Size of items, either small, medium or large"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"THEME"})," - Users applied theme"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"CUSTOM_COLORS"})," - Any color modifications made to a given theme"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"BACKUP_ID"})," - If a backup has been made, the ID is stored here"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"BACKUP_HASH"})," - A unique hash of the previous backups meta data"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"HIDE_SETTINGS"})," - Lets user hide or show the settings menu"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"USERNAME"})," - If user logged in, store username. Only used to show welcome message, not used for auth"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"CONF_SECTIONS"})," - Array of sections, only used when user applies changes locally"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"PAGE_INFO"})," - Config page info, only used when user applies changes locally"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"APP_CONFIG"})," - App config, only used when user applies changes locally"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"MOST_USED"})," - If smart sort is used to order items by most used, store open count"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"LAST_USED"})," - If smart sort is used to order items by last used, store timestamps"]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"deleting-stored-data",children:"Deleting Stored Data"}),"\n",(0,r.jsxs)(s.p,{children:["You can manually view and delete session storage, local storage and cookies at anytime. First ",(0,r.jsx)(s.a,{href:"/docs/troubleshooting#how-to-open-browser-console",children:"open"})," your browsers developer tools (usually ",(0,r.jsx)(s.kbd,{children:"F12"}),"), then under the Application tab select the storage category. Here you will see a list of stored data, and you can select any item and delete it."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"external-requests",children:"External Requests"}),"\n",(0,r.jsx)(s.p,{children:"By default, Dashy will not make any external requests, unless you configure it to. Some features (which are off by default) do require internet access, and this section outlines those features, the services used, and (where applicable) links to their privacy policies."}),"\n",(0,r.jsx)(s.h3,{id:"icons",children:"Icons"}),"\n",(0,r.jsx)(s.h4,{id:"font-awesome",children:"Font Awesome"}),"\n",(0,r.jsxs)(s.p,{children:["If either any of your sections, items or themes are using icons from font-awesome, then it will be automatically enabled. But you can also manually enable or disable it by setting ",(0,r.jsx)(s.code,{children:"appConfig.enableFontAwesome"})," to ",(0,r.jsx)(s.code,{children:"true"})," / ",(0,r.jsx)(s.code,{children:"false"}),". Requests are made directly to Font-Awesome CDN, for more info, see the ",(0,r.jsx)(s.a,{href:"https://fontawesome.com/privacy",children:"Font Awesome Privacy Policy"}),"."]}),"\n",(0,r.jsx)(s.h4,{id:"material-design-icons",children:"Material Design Icons"}),"\n",(0,r.jsxs)(s.p,{children:["If either any of your sections, items or themes are mdi icons, then it will be automatically enabled. But you can also manually enable or disable it by setting ",(0,r.jsx)(s.code,{children:"appConfig.enableMaterialDesignIcons"})," to ",(0,r.jsx)(s.code,{children:"true"})," / ",(0,r.jsx)(s.code,{children:"false"}),". Requests are made directly to Material-Design-Icons CDN, for more info, see the ",(0,r.jsx)(s.a,{href:"https://materialdesignicons.com/",children:"Material Design Icons Website"}),"."]}),"\n",(0,r.jsx)(s.h4,{id:"favicon-fetching",children:"Favicon Fetching"}),"\n",(0,r.jsxs)(s.p,{children:["If an item's icon is set to ",(0,r.jsx)(s.code,{children:"favicon"}),", then it will be auto-fetched from the corresponding URL. Since not all websites have their icon located at ",(0,r.jsx)(s.code,{children:"/favicon.ico"}),", and if they do, it's often very low resolution (like ",(0,r.jsx)(s.code,{children:"16 x 16 px"}),"). Therefore, the default behavior is for Dashy to check if the URL is public, and if so will use an API to fetch the favicon. For self-hosted services, the favicon will be fetched from the default path, and no external requests will be made."]}),"\n",(0,r.jsxs)(s.p,{children:["The default favicon API is ",(0,r.jsx)(s.a,{href:"https://favicon.allesedv.com/",children:"allesedv.com"}),", but this can be changed by setting ",(0,r.jsx)(s.code,{children:"appConfig.faviconApi"})," to an alternate source (",(0,r.jsx)(s.code,{children:"iconhorse"}),", ",(0,r.jsx)(s.code,{children:"faviconkit"}),", ",(0,r.jsx)(s.code,{children:"besticon"}),", ",(0,r.jsx)(s.code,{children:"duckduckgo"}),", ",(0,r.jsx)(s.code,{children:"google"})," and ",(0,r.jsx)(s.code,{children:"allesedv"})," are supported). If you do not want to use any API, then you can set this property to ",(0,r.jsx)(s.code,{children:"local"}),", and the favicon will be fetched from the default path. For hosted services, this will still incur an external request."]}),"\n",(0,r.jsx)(s.h4,{id:"generative-icons",children:"Generative Icons"}),"\n",(0,r.jsxs)(s.p,{children:["If an item has the icon set to ",(0,r.jsx)(s.code,{children:"generative"}),", then an external request it made to ",(0,r.jsx)(s.a,{href:"https://dicebear.com/",children:"Dice Bear"})," to fetch the uniquely generated icon. The URL of a given service is used as the key for generating the icon, but it is first hashed and encoded for basic privacy. For more info, please reference the ",(0,r.jsx)(s.a,{href:"https://avatars.dicebear.com/legal/privacy-policy",children:"Dicebear Privacy Policy"})]}),"\n",(0,r.jsxs)(s.p,{children:["As a fallback, if Dicebear fails, then ",(0,r.jsx)(s.a,{href:"https://evatar.io/",children:"Evatar"})," is used."]}),"\n",(0,r.jsx)(s.h4,{id:"self-hosted-icons",children:"Self-Hosted Icons"}),"\n",(0,r.jsxs)(s.p,{children:["If an item's icon uses the ",(0,r.jsx)(s.code,{children:"sh-"})," prefix, icons are fetched from the ",(0,r.jsx)(s.a,{href:"https://selfh.st/icons/",children:"selfh.st icons"})," CDN at ",(0,r.jsx)(s.code,{children:"https://cdn.jsdelivr.net/gh/selfhst/icons"}),". This only applies when you explicitly use the ",(0,r.jsx)(s.code,{children:"sh-"})," prefix for an icon."]}),"\n",(0,r.jsx)(s.h4,{id:"other-icons",children:"Other Icons"}),"\n",(0,r.jsxs)(s.p,{children:["Section icons, item icons and app icons are able to accept a URL to a raw image, if the image is hosted online then an external request will be made. To avoid the need to make external requests for icon assets, you can either use a self-hosted CDN, or store your images within ",(0,r.jsx)(s.code,{children:"./public/item-icons"})," (which can be mounted as a volume if you're using Docker)."]}),"\n",(0,r.jsx)(s.h4,{id:"web-assets",children:"Web Assets"}),"\n",(0,r.jsx)(s.p,{children:"By default, all assets required by Dashy come bundled within the source, and so no external requests are made. If you add an additional font, which is imported from a CDN, then that will incur an external request. The same applies for other web assets, like external images, scripts or styles."}),"\n",(0,r.jsx)(s.h3,{id:"themes",children:"Themes"}),"\n",(0,r.jsx)(s.p,{children:"Certain themes may use external assets (such as fonts or images). These are only loaded if you actively select the theme. Currently, this applies to: Adventure, Vaporwave, Glass, Glass-2 and Night Sky themes, which load background images from external sources."}),"\n",(0,r.jsx)(s.h3,{id:"status-checking",children:"Status Checking"}),"\n",(0,r.jsx)(s.p,{children:"The status checking feature allows you to ping your apps/services and hosts to check if they are currently online and operational."}),"\n",(0,r.jsx)(s.p,{children:"Dashy will ping your services directly, and does not rely on any third party. If you are checking the uptime status of a public/hosted application, then please refer to that services privacy policy. For all self-hosted services, requests happen locally within your network, and are not external."}),"\n",(0,r.jsx)(s.h3,{id:"update-checks",children:"Update Checks"}),"\n",(0,r.jsxs)(s.p,{children:["When the application loads, it checks for updates. The results of which are displayed in the config menu of the UI. This was implemented because using a very outdated version of Dashy may have unfixed issues. Your version is fetched from the source (local request), but the latest version is fetched from GitHub, which is an external request. This can be disabled by setting ",(0,r.jsx)(s.code,{children:"appConfig.disableUpdateChecks: true"})]}),"\n",(0,r.jsx)(s.h3,{id:"cloud-backup",children:"Cloud Backup"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy has an optional End-to-End encrypted ",(0,r.jsx)(s.a,{href:"/docs/backup-restore",children:"cloud backup feature"}),". No data is ever transmitted unless you actively enable this feature through the UI."]}),"\n",(0,r.jsxs)(s.p,{children:["All data is encrypted before being sent to the backend. This is done in ",(0,r.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/CloudBackup.js",children:(0,r.jsx)(s.code,{children:"CloudBackup.js"})}),", using ",(0,r.jsx)(s.a,{href:"https://github.com/brix/crypto-js",children:"crypto.js"}),"'s AES method, using the users chosen password as the key. The data is then sent to a ",(0,r.jsx)(s.a,{href:"https://developers.cloudflare.com/workers/learning/how-workers-works",children:"Cloudflare worker"})," (a platform for running serverless functions), and stored in a ",(0,r.jsx)(s.a,{href:"https://developers.cloudflare.com/workers/learning/how-kv-works",children:"KV"})," data store."]}),"\n",(0,r.jsx)(s.p,{children:"Your selected password never leaves your device, and is hashed before being compared. It is only possible to restore a configuration if you have both the backup ID and decryption password. Because the data is encrypted on the client-side (before being sent to the cloud), it is not possible for a man-in-the-middle, government entity, website owner, or even Cloudflare to be able read any of your data."}),"\n",(0,r.jsx)(s.h3,{id:"web-search",children:"Web Search"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy has a primitive ",(0,r.jsx)(s.a,{href:"/docs/searching#web-search",children:"web search feature"}),". No external requests are made, instead you are redirected to your chosen search engine (defaults to DuckDuckGo), using your chosen opening method."]}),"\n",(0,r.jsxs)(s.p,{children:["This feature can be disabled under appConfig, with ",(0,r.jsx)(s.code,{children:"webSearch: { disableWebSearch: true }"})]}),"\n",(0,r.jsx)(s.h3,{id:"initialization-page",children:"Initialization Page"}),"\n",(0,r.jsxs)(s.p,{children:["When Dashy is first building (before the compiled app is ready), a static initialization page is displayed. This page loads a Google Font (",(0,r.jsx)(s.a,{href:"https://fonts.google.com/specimen/Fredoka+One",children:"Fredoka One"}),") from ",(0,r.jsx)(s.code,{children:"https://fonts.googleapis.com"}),". This only occurs during the build process, not during normal app usage. For more info, see ",(0,r.jsx)(s.a,{href:"https://policies.google.com/privacy",children:"Google's Privacy Policy"}),"."]}),"\n",(0,r.jsx)(s.h3,{id:"anonymous-error-reporting",children:"Anonymous Error Reporting"}),"\n",(0,r.jsxs)(s.p,{children:["Error reporting is disabled by default, and no data will ever be sent without your explicit consent. In fact, the error tracking code isn't even imported unless you have actively enabled it. ",(0,r.jsx)(s.a,{href:"https://github.com/getsentry/sentry",children:"Sentry"})," is used for this, it's an open source error tracking and performance monitoring tool, used to identify any issues which occur in the production app (if you enable it)."]}),"\n",(0,r.jsxs)(s.p,{children:["The crash report includes the file or line of code that triggered the error, and a 2-layer deep stack trace. Reoccurring errors will also include the following user information: OS type (Mac, Windows, Linux, Android or iOS) and browser type (Firefox, Chrome, IE, Safari). Data scrubbing is enabled. IP address will not be stored. If any potentially identifiable data ever finds its way into a crash report, it will be automatically and permanently erased. All statistics collected are anonymized and stored securely, and are automatically deleted after 14 days. For more about privacy and security, see the ",(0,r.jsx)(s.a,{href:"https://sentry.io/security/",children:"Sentry Docs"}),"."]}),"\n",(0,r.jsxs)(s.p,{children:["Enabling anonymous error reporting helps me to discover bugs I was unaware of, and then fix them, in order to make Dashy more reliable long term. Error reporting is activated by setting ",(0,r.jsx)(s.code,{children:"appConfig.enableErrorReporting: true"}),"."]}),"\n",(0,r.jsxs)(s.p,{children:["If you need to monitor bugs yourself, then you can ",(0,r.jsx)(s.a,{href:"https://develop.sentry.dev/self-hosted/",children:"self-host your own Sentry Server"}),", and use it by setting ",(0,r.jsx)(s.code,{children:"appConfig.sentryDsn"})," to your Sentry instances ",(0,r.jsx)(s.a,{href:"https://docs.sentry.io/product/sentry-basics/dsn-explainer/",children:"Data Source Name"}),", then just enable error reporting in Dashy."]}),"\n",(0,r.jsx)(s.h3,{id:"widgets",children:"Widgets"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy supports ",(0,r.jsx)(s.a,{href:"/docs/widgets",children:"Widgets"})," for displaying dynamic content. Below is a list of all widgets that make external data requests, along with the endpoint they call and a link to the Privacy Policy of that service."]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Widget"}),(0,r.jsx)(s.th,{children:"Endpoint"}),(0,r.jsx)(s.th,{children:"Privacy Policy"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.a,{href:"/docs/widgets#weather",children:"Weather"})," / ",(0,r.jsx)(s.a,{href:"/docs/widgets#weather-forecast",children:"Weather Forecast"})]}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.openweathermap.org"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://openweather.co.uk/privacy-policy",children:"OWM Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#rss-feed",children:"RSS Feed"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.rss2json.com/v1/api.json"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://rss2json.com/privacy-policy",children:"Rss2Json Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#public-ip",children:"IP Address"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://free.freeipapi.com/api/json"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://freeipapi.com/",children:"FreeIPAPI"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://ipinfo.io/json"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://ipinfo.io/privacy-policy",children:"IPInfo Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.ipquery.io/"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://ipquery.io/",children:"IPQuery"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"http://ip-api.com/json"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://ip-api.com/docs/legal",children:"IP-API Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.ipgeolocation.io/ipgeo"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://ipgeolocation.io/privacy.html",children:"IPGeoLocation Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#ip-blacklist",children:"IP Blacklist"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.blacklistchecker.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://blacklistchecker.com/privacy",children:"Blacklist Checker Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#domain-monitor",children:"Domain Monitor"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.whoapi.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://whoapi.com/privacy-policy/",children:"WhoAPI Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.a,{href:"/docs/widgets#crypto-watch-list",children:"Crypto Watch List"})," / ",(0,r.jsx)(s.a,{href:"/docs/widgets#crypto-token-price-history",children:"Token Price History"})]}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.coingecko.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.coingecko.com/en/privacy",children:"CoinGecko Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#wallet-balance",children:"Wallet Balance"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.blockcypher.com/"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.blockcypher.com/privacy.html",children:"BlockCypher Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#code-stats",children:"Code::Stats"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://codestats.net"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://codestats.net/tos#privacy",children:"Code::Stats Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#addyio",children:"addy.io"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://app.addy.io"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://addy.io/privacy/",children:"addy.io Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#vulnerability-feed",children:"Vulnerability Feed"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://services.nvd.nist.gov/rest/json/cves/2.0"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.nist.gov/privacy-policy",children:"NIST Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#exchange-rates",children:"Exchange Rate"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://v6.exchangerate-api.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.exchangerate-api.com/terms",children:"ExchangeRateAPI Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#public-holidays",children:"Public Holidays"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://kayaposoft.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://github.com/jurajmajer/enrico",children:"jurajmajer/enrico"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#covid-19-status",children:"Covid-19 Status"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://disease.sh/v3/covid-19"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://github.com/disease-sh/api",children:"disease-sh/api"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#sports-scores",children:"Sports Scores"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://thesportsdb.com"})}),(0,r.jsx)(s.td,{children:"No Policy Available"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#news-headlines",children:"News Headlines"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.currentsapi.services"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://currentsapi.services/privacy",children:"CurrentsAPI Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#mullvad-status",children:"Mullvad Status"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://am.i.mullvad.net"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://mullvad.net/en/help/privacy-policy/",children:"Mullvad Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#tfl-status",children:"TFL Status"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.tfl.gov.uk"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://tfl.gov.uk/corporate/privacy-and-cookies/",children:"TFL Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#stock-price-history",children:"Stock Price History"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://www.alphavantage.co"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.alphavantage.co/privacy/",children:"AlphaVantage Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#eth-gas-prices",children:"ETH Gas Prices"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://ethgas.watch"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://github.com/wslyvh/ethgaswatch",children:"wslyvh/ethgaswatch"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#joke",children:"Joke"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://v2.jokeapi.dev"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://sv443.net/privacypolicy/en",children:"SV443's Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#chuck-norris-jokes",children:"Chuck Norris Jokes"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.chucknorris.io"})}),(0,r.jsx)(s.td,{children:"No Policy Available"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#xkcd-comic",children:"XKCD Comic"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://xkcd.vercel.app"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://xkcd.com",children:"XKCD"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#flight-data",children:"Flight Data"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://aerodatabox.p.rapidapi.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.aerodatabox.com/#h.p_cxtiyzwf_wqd",children:"AeroDataBox Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#astronomy-picture-of-the-day",children:"Astronomy Picture of the Day"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://apod.as93.net"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.nasa.gov/about/highlights/HP_Privacy.html",children:"NASA's Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#github-trending",children:"GitHub Trending"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://trend.doforce.xyz"})}),(0,r.jsx)(s.td,{children:"No Policy Available"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#github-profile-stats",children:"GitHub Profile Stats"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://github-readme-stats.vercel.app"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://docs.github.com/en/github/site-policy/github-privacy-statement",children:"GitHub's Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#healthchecks-status",children:"Healthchecks Status"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://healthchecks.io"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://healthchecks.io/privacy/",children:"Health-Checks Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#hacker-news-trending",children:"Hacker News Trending"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://hacker-news.firebaseio.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.ycombinator.com/legal#privacy",children:"Y Combinator Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#minecraft-server-status",children:"Minecraft Server Status"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.mcsrvstat.us"})}),(0,r.jsx)(s.td,{children:"No Policy Available"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#mvg",children:"MVG"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://www.mvg.de/api/fib/v2/"})}),(0,r.jsx)(s.td,{children:"No Policy Available"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#rescue-time",children:"RescueTime"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://www.rescuetime.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.rescuetime.com/privacy",children:"RescueTime Privacy Policy"})})]})]})]}),"\n",(0,r.jsx)(s.p,{children:"Note: There are also many widgets that connect to self-hosted services (such as Pi-hole, AdGuard, Glances, Nextcloud, Proxmox, Uptime Kuma, etc.). These only make requests to your own configured server addresses and do not contact any third-party services."}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"dependencies",children:"Dependencies"}),"\n",(0,r.jsxs)(s.p,{children:["As with most web projects, Dashy relies on several ",(0,r.jsx)(s.a,{href:"/docs/credits#dependencies",children:"dependencies"}),"."]}),"\n",(0,r.jsxs)(s.p,{children:["Dependencies can introduce security vulnerabilities, but since all these packages are open source any issues are usually very quickly spotted. Dashy is using Snyk for dependency security monitoring, and you can see ",(0,r.jsx)(s.a,{href:"https://snyk.io/test/github/lissy93/dashy",children:"the latest report here"}),". If any issue is detected by Snyk, a note about it will appear at the top of the Readme, and will usually be fixed within 48 hours."]}),"\n",(0,r.jsxs)(s.p,{children:["Note that packages listed under ",(0,r.jsx)(s.code,{children:"devDependencies"})," section are only used for building the project, and are not included in the production environment."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"securing-your-environment",children:"Securing your Environment"}),"\n",(0,r.jsx)(s.p,{children:"There is very little complexity involved with Dashy, and therefore the attack surface is reasonably small, but it is still important to follow best practices for all your self-hosted apps:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Use SSL/HTTPS"})," for securing traffic in transit, see ",(0,r.jsx)(s.a,{href:"/docs/management#ssl-certificates",children:"Management Docs: SSL Certificates"})]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Configure authentication"})," to prevent unauthorized access, see ",(0,r.jsx)(s.a,{href:"/docs/authentication",children:"Authentication Docs"}),". For internet-facing instances, use ",(0,r.jsx)(s.a,{href:"/docs/authentication#keycloak",children:"Keycloak"}),", ",(0,r.jsx)(s.a,{href:"/docs/authentication#oidc",children:"OIDC"}),", or an ",(0,r.jsx)(s.a,{href:"/docs/authentication#alternative-authentication-methods",children:"alternative server-side method"})]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Place behind a reverse proxy"})," if exposing to the internet, see ",(0,r.jsx)(s.a,{href:"/docs/management#network-exposure",children:"Management Docs: Network Exposure"})]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Harden your containers"})," if running in Docker, see ",(0,r.jsx)(s.a,{href:"/docs/management#container-security",children:"Management Docs: Container Security"})]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Keep Dashy and your system up-to-date"})," to ensure known vulnerabilities are patched"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Configure firewall rules"})," to restrict access to only necessary ports and networks"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Use a VPN"})," for private access without exposing Dashy to the public internet"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsxs)(s.strong,{children:["Follow ",(0,r.jsx)(s.a,{href:"https://docs.docker.com/engine/security/",children:"Docker security best practices"})]})," including running as non-root, limiting capabilities, and using read-only volumes"]}),"\n"]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"security-features",children:"Security Features"}),"\n",(0,r.jsx)(s.h3,{id:"subresource-integrity",children:"Subresource Integrity"}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity",children:"Subresource Integrity"})," or SRI is a security feature that enables browsers to verify that resources they fetch are delivered without unexpected manipulation. It works by allowing you to provide a cryptographic hash that a fetched resource must match. This prevents the app from loading any resources that have been manipulated, by verifying the files hashes. It safeguards against the risk of an attacker injecting arbitrary malicious content into any files served up via a CDN."]}),"\n",(0,r.jsxs)(s.p,{children:["Dashy supports SRI, and it is recommended to enable this if you are hosting your dashboard via a public CDN. To enable SRI, set the ",(0,r.jsx)(s.code,{children:"INTEGRITY"})," environmental variable to ",(0,r.jsx)(s.code,{children:"true"}),"."]}),"\n",(0,r.jsx)(s.h3,{id:"ssl",children:"SSL"}),"\n",(0,r.jsxs)(s.p,{children:["Native SSL support is enabled, for setup instructions, see the ",(0,r.jsx)(s.a,{href:"/docs/management#ssl-certificates",children:"Management Docs"})]}),"\n",(0,r.jsx)(s.h3,{id:"authentication",children:"Authentication"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy supports built-in auth, server-based SSO using Keycloak or any OIDC provider, and header-based authentication for reverse proxy setups. Full details of which, along with alternate authentication methods can be found in the ",(0,r.jsx)(s.a,{href:"/docs/authentication",children:"Authentication Docs"}),". If your dashboard is exposed to the internet and/ or contains any sensitive info it is strongly recommended to configure access control with Keycloak, OIDC, or another server-side method."]}),"\n",(0,r.jsx)(s.h3,{id:"configuration-lockdown",children:"Configuration Lockdown"}),"\n",(0,r.jsx)(s.p,{children:"Dashy provides several options to restrict what users can modify:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"appConfig.preventWriteToDisk"})," - Prevents config changes from being saved to the server"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"appConfig.preventLocalSave"})," - Prevents config changes from being saved to browser storage"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"appConfig.disableConfiguration"})," - Hides the config UI from all users"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"appConfig.disableConfigurationForNonAdmin"})," - Hides the config UI for non-admin users"]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["These can be combined with the ",(0,r.jsx)(s.code,{children:"admin"})," and ",(0,r.jsx)(s.code,{children:"normal"})," user roles to give fine-grained control. Admin users can save config changes, while normal users have read-only access. For more details, see the ",(0,r.jsx)(s.a,{href:"/docs/authentication#permissions",children:"Authentication Docs: Permissions"}),"."]}),"\n",(0,r.jsx)(s.h3,{id:"disabling-features",children:"Disabling Features"}),"\n",(0,r.jsx)(s.p,{children:"You may wish to disable features that you don't want to use, if they involve storing data in the browser or making network requests."}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["To disable smart-sort (uses local storage), set ",(0,r.jsx)(s.code,{children:"appConfig.disableSmartSort: true"})]}),"\n",(0,r.jsxs)(s.li,{children:["To disable update checks (makes external request to GH), set ",(0,r.jsx)(s.code,{children:"appConfig.disableUpdateChecks: true"})]}),"\n",(0,r.jsxs)(s.li,{children:["To disable web search (redirect to external / internal content), set ",(0,r.jsx)(s.code,{children:"appConfig.webSearch.disableWebSearch: true"})]}),"\n",(0,r.jsxs)(s.li,{children:["To keep status checks disabled (external / internal requests), set ",(0,r.jsx)(s.code,{children:"appConfig.statusCheck: false"})]}),"\n",(0,r.jsxs)(s.li,{children:["To keep ping checks disabled (external / internal requests), set ",(0,r.jsx)(s.code,{children:"appConfig.pingCheckEnabled: false"})]}),"\n",(0,r.jsxs)(s.li,{children:["To keep font-awesome icons disabled (external requests), set ",(0,r.jsx)(s.code,{children:"appConfig.enableFontAwesome: false"})]}),"\n",(0,r.jsxs)(s.li,{children:["To keep error reporting disabled (external requests and data collection), set ",(0,r.jsx)(s.code,{children:"appConfig.enableErrorReporting: false"})]}),"\n",(0,r.jsxs)(s.li,{children:["To keep the service worker disabled (stores cache of app in browser data), set ",(0,r.jsx)(s.code,{children:"appConfig.enableServiceWorker: false"})]}),"\n"]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"threat-model",children:"Threat Model"}),"\n",(0,r.jsx)(s.p,{children:"Dashy is a statically-hosted dashboard application, designed to be self-hosted on a private network. This threat model outlines the intended deployment context, trust boundaries, known risks and accepted trade-offs, to help users assess whether Dashy is appropriate for their environment."}),"\n",(0,r.jsx)(s.h3,{id:"intended-deployment",children:"Intended Deployment"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy is designed to run on a ",(0,r.jsx)(s.strong,{children:"private local network"})," (e.g. a home lab), accessed by a ",(0,r.jsx)(s.strong,{children:"small number of trusted users"}),". It is a convenience tool for organizing links to self-hosted services - it is not designed to protect sensitive resources or act as an access control layer."]}),"\n",(0,r.jsxs)(s.p,{children:["If exposed to the internet, Dashy ",(0,r.jsx)(s.strong,{children:"must"})," be placed behind a reverse proxy with server-side authentication (e.g. Authelia, Authentik, Cloudflare Access). The built-in client-side auth is a convenience feature for private networks, not a security boundary."]}),"\n",(0,r.jsx)(s.h3,{id:"trust-boundaries",children:"Trust Boundaries"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Boundary"}),(0,r.jsx)(s.th,{children:"Trusted Side"}),(0,r.jsx)(s.th,{children:"Untrusted Side"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Local network"}),(0,r.jsx)(s.td,{children:"LAN users, self-hosted services"}),(0,r.jsx)(s.td,{children:"The public internet"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsxs)(s.td,{children:["Config file (",(0,r.jsx)(s.code,{children:"conf.yml"}),")"]}),(0,r.jsx)(s.td,{children:"Server admin who writes the config"}),(0,r.jsx)(s.td,{children:"End users who view the dashboard"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Browser storage"}),(0,r.jsx)(s.td,{children:"The current browser session"}),(0,r.jsx)(s.td,{children:"Other domains, other users of the same device"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"CORS proxy / status checks"}),(0,r.jsx)(s.td,{children:"Configured target URLs (set by admin)"}),(0,r.jsx)(s.td,{children:"Arbitrary URLs (if auth is not enabled)"})]})]})]}),"\n",(0,r.jsx)(s.h3,{id:"assets",children:"Assets"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Asset"}),(0,r.jsx)(s.th,{children:"Description"}),(0,r.jsx)(s.th,{children:"Storage"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Dashboard configuration"}),(0,r.jsx)(s.td,{children:"Service URLs, section layout, app settings"}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"conf.yml"})," on server, optionally cached in browser localStorage"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"User credentials"}),(0,r.jsx)(s.td,{children:"SHA-256 password hashes, Keycloak/OIDC client IDs"}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"conf.yml"})," on server"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"API keys"}),(0,r.jsx)(s.td,{children:"Keys for widget services (weather, stocks, etc.)"}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"conf.yml"})," on server or environment variables"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Auth tokens"}),(0,r.jsx)(s.td,{children:"Session token derived from credentials"}),(0,r.jsxs)(s.td,{children:["Browser cookie (",(0,r.jsx)(s.code,{children:"dashyAuthToken"}),")"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"User preferences"}),(0,r.jsx)(s.td,{children:"Theme, layout, language, collapsed sections"}),(0,r.jsx)(s.td,{children:"Browser localStorage"})]})]})]}),"\n",(0,r.jsx)(s.h3,{id:"when-dashy-is-not-the-right-choice",children:"When Dashy is NOT the Right Choice"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["You need a ",(0,r.jsx)(s.strong,{children:"multi-tenant"})," dashboard with per-user audit trails"]}),"\n",(0,r.jsxs)(s.li,{children:["You are deploying on the ",(0,r.jsx)(s.strong,{children:"public internet without a reverse proxy"})]}),"\n",(0,r.jsxs)(s.li,{children:["Your dashboard contains ",(0,r.jsx)(s.strong,{children:"secrets or credentials"})," that must be protected from all users who can reach the server"]}),"\n",(0,r.jsxs)(s.li,{children:["You require ",(0,r.jsx)(s.strong,{children:"FIPS-compliant"})," or ",(0,r.jsx)(s.strong,{children:"SOC 2"})," certified software"]}),"\n"]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"known-limitations",children:"Known Limitations"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Report"}),(0,r.jsx)(s.th,{children:"Response"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Client-side auth can be bypassed via browser dev tools"'}),(0,r.jsxs)(s.td,{children:["Correct (only if neither ",(0,r.jsx)(s.code,{children:"ENABLE_HTTP_AUTH"})," is set, nor any other auth mode). Client-side auth is a convenience for private networks, not a security boundary. Use server-side auth for untrusted environments."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"CORS proxy can make requests to internal services"'}),(0,r.jsx)(s.td,{children:"Correct. The CORS proxy is designed to reach services on your network. It is protected by auth middleware when auth is enabled. Do not expose Dashy without auth on an untrusted network."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Status checks can be used for SSRF"'}),(0,r.jsxs)(s.td,{children:["Status check target URLs are set by the server admin in ",(0,r.jsx)(s.code,{children:"conf.yml"}),", not by end users. If auth is enabled, the endpoint is protected."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Password hashes are stored in plaintext in conf.yml"'}),(0,r.jsx)(s.td,{children:"They are SHA-256 hashes, not plaintext passwords. The config file should be readable only by the server admin, and protected by HTTP auth when served."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"localStorage/cookies are not encrypted"'}),(0,r.jsx)(s.td,{children:"Browser storage is scoped to the origin and inaccessible to other domains. On a shared device, use your browser's profile isolation."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"No CSRF protection"'}),(0,r.jsx)(s.td,{children:"Dashy's state-changing operations (config save) are protected by auth middleware. CSRF is a low risk on a private network dashboard."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Docker container runs as root"'}),(0,r.jsxs)(s.td,{children:["The container is sandboxed. For hardened deployments, override with ",(0,r.jsx)(s.code,{children:"--user"})," flag or configure in your Docker Compose file as described in the ",(0,r.jsx)(s.a,{href:"/docs/management#container-security",children:"container security docs"}),"."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Auth cookie is not HttpOnly/Secure"'}),(0,r.jsxs)(s.td,{children:["The token is needed by client-side JavaScript for auth state. On a private network over plain HTTP, the ",(0,r.jsx)(s.code,{children:"Secure"})," flag would break auth. Use HTTPS + a reverse proxy to add these flags if needed."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Iframe/embed widget can load arbitrary URLs"'}),(0,r.jsxs)(s.td,{children:["The widget config is written by the server admin, not end users. If you don't trust your config authors, disable the config editor with ",(0,r.jsx)(s.code,{children:"disableConfiguration"}),"."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"RSS widget renders HTML content"'}),(0,r.jsx)(s.td,{children:"RSS content is sanitized with DOMPurify before rendering. Script tags, event handlers and other dangerous elements are stripped."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"No Content-Security-Policy headers"'}),(0,r.jsx)(s.td,{children:"CSP should be configured at the reverse proxy layer, since the correct policy depends on which widgets and icon CDNs you use. Dashy can't set a universal CSP that works for all configurations."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Config backups are not encrypted at rest"'}),(0,r.jsxs)(s.td,{children:["Backups are stored server-side alongside the original config. If an attacker has filesystem access, they already have ",(0,r.jsx)(s.code,{children:"conf.yml"}),". Encryption at rest is the responsibility of the host OS/volume."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"No rate limiting on endpoints"'}),(0,r.jsx)(s.td,{children:"Rate limiting should be applied at the reverse proxy layer, where it can be tuned per-deployment. Dashy is not designed to be directly exposed to untrusted traffic."})]})]})]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"update--patch-policy",children:"Update & Patch Policy"}),"\n",(0,r.jsxs)(s.p,{children:["We follow Semantic Versioning for all releases. Security fixes are shipped as patch releases as quickly as possible and are published via immutable Git tags and Docker image tags. Users are encouraged to pin to a specific version in production and monitor releases on GitHub for security updates. The ",(0,r.jsx)(s.code,{children:":latest"})," Docker tag is provided for convenience but should not be relied on in production environments."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"reporting-a-security-issue",children:"Reporting a Security Issue"}),"\n",(0,r.jsxs)(s.p,{children:["Please see our ",(0,r.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/?tab=security-ov-file",children:"Security.md"})," doc for how to report issues.\nWe have an actively monitored security mailbox supporting PGP, as well as a GitHub Advisories vulnerability reporting program."]})]})}function h(e={}){const{wrapper:s}={...(0,t.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},28453(e,s,n){n.d(s,{R:()=>o,x:()=>d});var i=n(96540);const r={},t=i.createContext(r);function o(e){const s=i.useContext(t);return i.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function d(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),i.createElement(t.Provider,{value:s},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[5330],{69356(e,s,n){n.r(s),n.d(s,{assets:()=>c,contentTitle:()=>d,default:()=>h,frontMatter:()=>o,metadata:()=>i,toc:()=>a});const i=JSON.parse('{"id":"privacy","title":"Privacy & Security","description":"Dashy was built with privacy in mind.","source":"@site/docs/privacy.md","sourceDirName":".","slug":"/privacy","permalink":"/docs/privacy","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/privacy.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Development Guides","permalink":"/docs/development-guides"},"next":{"title":"Credits","permalink":"/docs/credits"}}');var r=n(74848),t=n(28453);const o={},d="Privacy & Security",c={},a=[{value:"Contents",id:"contents",level:2},{value:"Browser Storage",id:"browser-storage",level:2},{value:"Cookies",id:"cookies",level:3},{value:"Session Storage",id:"session-storage",level:3},{value:"Local Storage",id:"local-storage",level:3},{value:"Deleting Stored Data",id:"deleting-stored-data",level:3},{value:"External Requests",id:"external-requests",level:2},{value:"Icons",id:"icons",level:3},{value:"Font Awesome",id:"font-awesome",level:4},{value:"Material Design Icons",id:"material-design-icons",level:4},{value:"Favicon Fetching",id:"favicon-fetching",level:4},{value:"Generative Icons",id:"generative-icons",level:4},{value:"Self-Hosted Icons",id:"self-hosted-icons",level:4},{value:"Other Icons",id:"other-icons",level:4},{value:"Web Assets",id:"web-assets",level:4},{value:"Themes",id:"themes",level:3},{value:"Status Checking",id:"status-checking",level:3},{value:"Update Checks",id:"update-checks",level:3},{value:"Cloud Backup",id:"cloud-backup",level:3},{value:"Web Search",id:"web-search",level:3},{value:"Initialization Page",id:"initialization-page",level:3},{value:"Anonymous Error Reporting",id:"anonymous-error-reporting",level:3},{value:"Widgets",id:"widgets",level:3},{value:"Dependencies",id:"dependencies",level:2},{value:"Securing your Environment",id:"securing-your-environment",level:2},{value:"Security Features",id:"security-features",level:2},{value:"Subresource Integrity",id:"subresource-integrity",level:3},{value:"SSL",id:"ssl",level:3},{value:"Authentication",id:"authentication",level:3},{value:"Configuration Lockdown",id:"configuration-lockdown",level:3},{value:"Disabling Features",id:"disabling-features",level:3},{value:"Threat Model",id:"threat-model",level:2},{value:"Intended Deployment",id:"intended-deployment",level:3},{value:"Trust Boundaries",id:"trust-boundaries",level:3},{value:"Assets",id:"assets",level:3},{value:"When Dashy is NOT the Right Choice",id:"when-dashy-is-not-the-right-choice",level:3},{value:"Known Limitations",id:"known-limitations",level:2},{value:"Update & Patch Policy",id:"update--patch-policy",level:2},{value:"Reporting a Security Issue",id:"reporting-a-security-issue",level:2}];function l(e){const s={a:"a",admonition:"admonition",blockquote:"blockquote",br:"br",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",kbd:"kbd",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.header,{children:(0,r.jsx)(s.h1,{id:"privacy--security",children:"Privacy & Security"})}),"\n",(0,r.jsx)(s.p,{children:"Dashy was built with privacy in mind.\nSelf-hosting your own apps and services is a great way to protect yourself from the mass data collection employed by big tech companies, and Dashy was designed to make self-hosting easier."}),"\n",(0,r.jsx)(s.p,{children:"Dashy operates on the premise, that:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"No external data requests should ever be made, unless explicitly enabled by the user"}),"\n",(0,r.jsx)(s.li,{children:"All code is 100% open source, clearly documented and intended to be easily auditable"}),"\n",(0,r.jsx)(s.li,{children:"Privacy-respecting by default. No premium features, analytics, tracking or ads"}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"This document outlines all network requests, data storage requirements, security configurations and data handling processes."}),"\n",(0,r.jsx)(s.admonition,{type:"tip",children:(0,r.jsxs)(s.p,{children:["Btw (shameless plug), if you care about your privacy, you might also like ",(0,r.jsx)(s.a,{href:"https://github.com/Lissy93/awesome-privacy/",children:"awesome-privacy"}),"!",(0,r.jsx)(s.br,{})]})}),"\n",(0,r.jsx)(s.h2,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["Privacy\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#browser-storage",children:"Browser Storage"})}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.a,{href:"#external-requests",children:"External Requests"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#icons",children:"Icons"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#themes",children:"Themes"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#status-checking",children:"Status Checking"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#update-checks",children:"Update Checks"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#cloud-backup",children:"Cloud Backup"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#web-search",children:"Web Search"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#initialization-page",children:"Initialization Page"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#anonymous-error-reporting",children:"Anonymous Error Reporting"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#widgets",children:"Widgets"})}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(s.li,{children:["Security\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#dependencies",children:"Dependencies"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#securing-your-environment",children:"Securing your Environment"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#security-features",children:"Security Features"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#threat-model",children:"Threat Model"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#known-limitations",children:"Known Limitations"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#update--patch-policy",children:"Update & Patch Policy"})}),"\n",(0,r.jsx)(s.li,{children:(0,r.jsx)(s.a,{href:"#reporting-a-security-issue",children:"Reporting a Security Issue"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"browser-storage",children:"Browser Storage"}),"\n",(0,r.jsx)(s.p,{children:"In order for user preferences to be persisted some data is stored locally in your browsers storage.\nNo personal info is kept here, none of this data can be accessed by other domains, no data is ever sent to any server without your prior consent, and all data is removed when no longer needed unless you delete it sooner."}),"\n",(0,r.jsx)(s.p,{children:"The following section outlines all data that is stored in the browsers, as cookies, session storage or local storage."}),"\n",(0,r.jsx)(s.h3,{id:"cookies",children:"Cookies"}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies",children:"Cookies"})," will expire after their pre-defined lifetime.\nDashy uses cookies for authentication, when enabled."]}),"\n"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"AUTH_TOKEN"})," - A unique token, generated from a hash of users credentials, to verify they are authenticated. Only used when auth is enabled."]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"session-storage",children:"Session Storage"}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage",children:"Session storage"})," is deleted when the current session ends (tab / window is closed).\nDashy uses session storage service worker status and error list."]}),"\n"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"SW_STATUS"})," - The current status of any service workers"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"ERROR_LOG"})," - List of recent errors"]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"local-storage",children:"Local Storage"}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage",children:"Local storage"})," is persisted between sessions, and only deleted when manually removed.\nDashy can use local storage to keep track of your preferences."]}),"\n"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"LANGUAGE"})," - The locale to show app text in"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"HIDE_INFO_NOTIFICATION"})," - Set to true once user dismissed welcome message, so that it's not shown again"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"LAYOUT_ORIENTATION"})," - Preferred section layout, either auto, horizontal, vertical or masonry"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"COLLAPSE_STATE"})," - Remembers which sections are collapsed"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"ICON_SIZE"})," - Size of items, either small, medium or large"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"THEME"})," - Users applied theme"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"CUSTOM_COLORS"})," - Any color modifications made to a given theme"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"BACKUP_ID"})," - If a backup has been made, the ID is stored here"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"BACKUP_HASH"})," - A unique hash of the previous backups meta data"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"HIDE_SETTINGS"})," - Lets user hide or show the settings menu"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"USERNAME"})," - If user logged in, store username. Only used to show welcome message, not used for auth"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"CONF_SECTIONS"})," - Array of sections, only used when user applies changes locally"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"PAGE_INFO"})," - Config page info, only used when user applies changes locally"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"APP_CONFIG"})," - App config, only used when user applies changes locally"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"MOST_USED"})," - If smart sort is used to order items by most used, store open count"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"LAST_USED"})," - If smart sort is used to order items by last used, store timestamps"]}),"\n"]}),"\n",(0,r.jsx)(s.h3,{id:"deleting-stored-data",children:"Deleting Stored Data"}),"\n",(0,r.jsxs)(s.p,{children:["You can manually view and delete session storage, local storage and cookies at anytime. First ",(0,r.jsx)(s.a,{href:"/docs/troubleshooting#how-to-open-browser-console",children:"open"})," your browsers developer tools (usually ",(0,r.jsx)(s.kbd,{children:"F12"}),"), then under the Application tab select the storage category. Here you will see a list of stored data, and you can select any item and delete it."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"external-requests",children:"External Requests"}),"\n",(0,r.jsx)(s.p,{children:"By default, Dashy will not make any external requests, unless you configure it to. Some features (which are off by default) do require internet access, and this section outlines those features, the services used, and (where applicable) links to their privacy policies."}),"\n",(0,r.jsx)(s.h3,{id:"icons",children:"Icons"}),"\n",(0,r.jsx)(s.h4,{id:"font-awesome",children:"Font Awesome"}),"\n",(0,r.jsxs)(s.p,{children:["If either any of your sections, items or themes are using icons from font-awesome, then it will be automatically enabled. But you can also manually enable or disable it by setting ",(0,r.jsx)(s.code,{children:"appConfig.enableFontAwesome"})," to ",(0,r.jsx)(s.code,{children:"true"})," / ",(0,r.jsx)(s.code,{children:"false"}),". Requests are made directly to Font-Awesome CDN, for more info, see the ",(0,r.jsx)(s.a,{href:"https://fontawesome.com/privacy",children:"Font Awesome Privacy Policy"}),"."]}),"\n",(0,r.jsx)(s.h4,{id:"material-design-icons",children:"Material Design Icons"}),"\n",(0,r.jsxs)(s.p,{children:["If either any of your sections, items or themes are mdi icons, then it will be automatically enabled. But you can also manually enable or disable it by setting ",(0,r.jsx)(s.code,{children:"appConfig.enableMaterialDesignIcons"})," to ",(0,r.jsx)(s.code,{children:"true"})," / ",(0,r.jsx)(s.code,{children:"false"}),". Requests are made directly to Material-Design-Icons CDN, for more info, see the ",(0,r.jsx)(s.a,{href:"https://materialdesignicons.com/",children:"Material Design Icons Website"}),"."]}),"\n",(0,r.jsx)(s.h4,{id:"favicon-fetching",children:"Favicon Fetching"}),"\n",(0,r.jsxs)(s.p,{children:["If an item's icon is set to ",(0,r.jsx)(s.code,{children:"favicon"}),", then it will be auto-fetched from the corresponding URL. Since not all websites have their icon located at ",(0,r.jsx)(s.code,{children:"/favicon.ico"}),", and if they do, it's often very low resolution (like ",(0,r.jsx)(s.code,{children:"16 x 16 px"}),"). Therefore, the default behavior is for Dashy to check if the URL is public, and if so will use an API to fetch the favicon. For self-hosted services, the favicon will be fetched from the default path, and no external requests will be made."]}),"\n",(0,r.jsxs)(s.p,{children:["The default favicon API is ",(0,r.jsx)(s.a,{href:"https://favicon.allesedv.com/",children:"allesedv.com"}),", but this can be changed by setting ",(0,r.jsx)(s.code,{children:"appConfig.faviconApi"})," to an alternate source (",(0,r.jsx)(s.code,{children:"iconhorse"}),", ",(0,r.jsx)(s.code,{children:"faviconkit"}),", ",(0,r.jsx)(s.code,{children:"besticon"}),", ",(0,r.jsx)(s.code,{children:"duckduckgo"}),", ",(0,r.jsx)(s.code,{children:"google"})," and ",(0,r.jsx)(s.code,{children:"allesedv"})," are supported). If you do not want to use any API, then you can set this property to ",(0,r.jsx)(s.code,{children:"local"}),", and the favicon will be fetched from the default path. For hosted services, this will still incur an external request."]}),"\n",(0,r.jsx)(s.h4,{id:"generative-icons",children:"Generative Icons"}),"\n",(0,r.jsxs)(s.p,{children:["If an item has the icon set to ",(0,r.jsx)(s.code,{children:"generative"}),", then an external request it made to ",(0,r.jsx)(s.a,{href:"https://dicebear.com/",children:"Dice Bear"})," to fetch the uniquely generated icon. The URL of a given service is used as the key for generating the icon, but it is first hashed and encoded for basic privacy. For more info, please reference the ",(0,r.jsx)(s.a,{href:"https://avatars.dicebear.com/legal/privacy-policy",children:"Dicebear Privacy Policy"})]}),"\n",(0,r.jsxs)(s.p,{children:["As a fallback, if Dicebear fails, then ",(0,r.jsx)(s.a,{href:"https://evatar.io/",children:"Evatar"})," is used."]}),"\n",(0,r.jsx)(s.h4,{id:"self-hosted-icons",children:"Self-Hosted Icons"}),"\n",(0,r.jsxs)(s.p,{children:["If an item's icon uses the ",(0,r.jsx)(s.code,{children:"sh-"})," prefix, icons are fetched from the ",(0,r.jsx)(s.a,{href:"https://selfh.st/icons/",children:"selfh.st icons"})," CDN at ",(0,r.jsx)(s.code,{children:"https://cdn.jsdelivr.net/gh/selfhst/icons"}),". This only applies when you explicitly use the ",(0,r.jsx)(s.code,{children:"sh-"})," prefix for an icon."]}),"\n",(0,r.jsx)(s.h4,{id:"other-icons",children:"Other Icons"}),"\n",(0,r.jsxs)(s.p,{children:["Section icons, item icons and app icons are able to accept a URL to a raw image, if the image is hosted online then an external request will be made. To avoid the need to make external requests for icon assets, you can either use a self-hosted CDN, or store your images within ",(0,r.jsx)(s.code,{children:"./public/item-icons"})," (which can be mounted as a volume if you're using Docker)."]}),"\n",(0,r.jsx)(s.h4,{id:"web-assets",children:"Web Assets"}),"\n",(0,r.jsx)(s.p,{children:"By default, all assets required by Dashy come bundled within the source, and so no external requests are made. If you add an additional font, which is imported from a CDN, then that will incur an external request. The same applies for other web assets, like external images, scripts or styles."}),"\n",(0,r.jsx)(s.h3,{id:"themes",children:"Themes"}),"\n",(0,r.jsx)(s.p,{children:"Certain themes may use external assets (such as fonts or images). These are only loaded if you actively select the theme. Currently, this applies to: Adventure, Vaporwave, Glass, Glass-2 and Night Sky themes, which load background images from external sources."}),"\n",(0,r.jsx)(s.h3,{id:"status-checking",children:"Status Checking"}),"\n",(0,r.jsx)(s.p,{children:"The status checking feature allows you to ping your apps/services and hosts to check if they are currently online and operational."}),"\n",(0,r.jsx)(s.p,{children:"Dashy will ping your services directly, and does not rely on any third party. If you are checking the uptime status of a public/hosted application, then please refer to that services privacy policy. For all self-hosted services, requests happen locally within your network, and are not external."}),"\n",(0,r.jsx)(s.h3,{id:"update-checks",children:"Update Checks"}),"\n",(0,r.jsxs)(s.p,{children:["When the application loads, it checks for updates. The results of which are displayed in the config menu of the UI. This was implemented because using a very outdated version of Dashy may have unfixed issues. Your version is fetched from the source (local request), but the latest version is fetched from GitHub, which is an external request. This can be disabled by setting ",(0,r.jsx)(s.code,{children:"appConfig.disableUpdateChecks: true"})]}),"\n",(0,r.jsx)(s.h3,{id:"cloud-backup",children:"Cloud Backup"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy has an optional End-to-End encrypted ",(0,r.jsx)(s.a,{href:"/docs/backup-restore",children:"cloud backup feature"}),". No data is ever transmitted unless you actively enable this feature through the UI."]}),"\n",(0,r.jsxs)(s.p,{children:["All data is encrypted before being sent to the backend. This is done in ",(0,r.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/CloudBackup.js",children:(0,r.jsx)(s.code,{children:"CloudBackup.js"})}),", using ",(0,r.jsx)(s.a,{href:"https://github.com/brix/crypto-js",children:"crypto.js"}),"'s AES method, using the users chosen password as the key. The data is then sent to a ",(0,r.jsx)(s.a,{href:"https://developers.cloudflare.com/workers/learning/how-workers-works",children:"Cloudflare worker"})," (a platform for running serverless functions), and stored in a ",(0,r.jsx)(s.a,{href:"https://developers.cloudflare.com/workers/learning/how-kv-works",children:"KV"})," data store."]}),"\n",(0,r.jsx)(s.p,{children:"Your selected password never leaves your device, and is hashed before being compared. It is only possible to restore a configuration if you have both the backup ID and decryption password. Because the data is encrypted on the client-side (before being sent to the cloud), it is not possible for a man-in-the-middle, government entity, website owner, or even Cloudflare to be able read any of your data."}),"\n",(0,r.jsx)(s.h3,{id:"web-search",children:"Web Search"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy has a primitive ",(0,r.jsx)(s.a,{href:"/docs/searching#web-search",children:"web search feature"}),". No external requests are made, instead you are redirected to your chosen search engine (defaults to DuckDuckGo), using your chosen opening method."]}),"\n",(0,r.jsxs)(s.p,{children:["This feature can be disabled under appConfig, with ",(0,r.jsx)(s.code,{children:"webSearch: { disableWebSearch: true }"})]}),"\n",(0,r.jsx)(s.h3,{id:"initialization-page",children:"Initialization Page"}),"\n",(0,r.jsxs)(s.p,{children:["When Dashy is first building (before the compiled app is ready), a static initialization page is displayed. This page loads a Google Font (",(0,r.jsx)(s.a,{href:"https://fonts.google.com/specimen/Fredoka+One",children:"Fredoka One"}),") from ",(0,r.jsx)(s.code,{children:"https://fonts.googleapis.com"}),". This only occurs during the build process, not during normal app usage. For more info, see ",(0,r.jsx)(s.a,{href:"https://policies.google.com/privacy",children:"Google's Privacy Policy"}),"."]}),"\n",(0,r.jsx)(s.h3,{id:"anonymous-error-reporting",children:"Anonymous Error Reporting"}),"\n",(0,r.jsxs)(s.p,{children:["Error reporting is disabled by default, and no data will ever be sent without your explicit consent. In fact, the error tracking code isn't even imported unless you have actively enabled it. ",(0,r.jsx)(s.a,{href:"https://github.com/getsentry/sentry",children:"Sentry"})," is used for this, it's an open source error tracking and performance monitoring tool, used to identify any issues which occur in the production app (if you enable it)."]}),"\n",(0,r.jsxs)(s.p,{children:["The crash report includes the file or line of code that triggered the error, and a 2-layer deep stack trace. Reoccurring errors will also include the following user information: OS type (Mac, Windows, Linux, Android or iOS) and browser type (Firefox, Chrome, IE, Safari). Data scrubbing is enabled. IP address will not be stored. If any potentially identifiable data ever finds its way into a crash report, it will be automatically and permanently erased. All statistics collected are anonymized and stored securely, and are automatically deleted after 14 days. For more about privacy and security, see the ",(0,r.jsx)(s.a,{href:"https://sentry.io/security/",children:"Sentry Docs"}),"."]}),"\n",(0,r.jsxs)(s.p,{children:["Enabling anonymous error reporting helps me to discover bugs I was unaware of, and then fix them, in order to make Dashy more reliable long term. Error reporting is activated by setting ",(0,r.jsx)(s.code,{children:"appConfig.enableErrorReporting: true"}),"."]}),"\n",(0,r.jsxs)(s.p,{children:["If you need to monitor bugs yourself, then you can ",(0,r.jsx)(s.a,{href:"https://develop.sentry.dev/self-hosted/",children:"self-host your own Sentry Server"}),", and use it by setting ",(0,r.jsx)(s.code,{children:"appConfig.sentryDsn"})," to your Sentry instances ",(0,r.jsx)(s.a,{href:"https://docs.sentry.io/product/sentry-basics/dsn-explainer/",children:"Data Source Name"}),", then just enable error reporting in Dashy."]}),"\n",(0,r.jsx)(s.h3,{id:"widgets",children:"Widgets"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy supports ",(0,r.jsx)(s.a,{href:"/docs/widgets",children:"Widgets"})," for displaying dynamic content. Below is a list of all widgets that make external data requests, along with the endpoint they call and a link to the Privacy Policy of that service."]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Widget"}),(0,r.jsx)(s.th,{children:"Endpoint"}),(0,r.jsx)(s.th,{children:"Privacy Policy"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.a,{href:"/docs/widgets#weather",children:"Weather"})," / ",(0,r.jsx)(s.a,{href:"/docs/widgets#weather-forecast",children:"Weather Forecast"})]}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.openweathermap.org"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://openweather.co.uk/privacy-policy",children:"OWM Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#rss-feed",children:"RSS Feed"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.rss2json.com/v1/api.json"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://rss2json.com/privacy-policy",children:"Rss2Json Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#public-ip",children:"IP Address"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://free.freeipapi.com/api/json"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://freeipapi.com/",children:"FreeIPAPI"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://ipinfo.io/json"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://ipinfo.io/privacy-policy",children:"IPInfo Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.ipquery.io/"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://ipquery.io/",children:"IPQuery"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"http://ip-api.com/json"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://ip-api.com/docs/legal",children:"IP-API Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.ipgeolocation.io/ipgeo"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://ipgeolocation.io/privacy.html",children:"IPGeoLocation Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#ip-blacklist",children:"IP Blacklist"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.blacklistchecker.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://blacklistchecker.com/privacy",children:"Blacklist Checker Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#domain-monitor",children:"Domain Monitor"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.whoapi.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://whoapi.com/privacy-policy/",children:"WhoAPI Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.a,{href:"/docs/widgets#crypto-watch-list",children:"Crypto Watch List"})," / ",(0,r.jsx)(s.a,{href:"/docs/widgets#crypto-token-price-history",children:"Token Price History"})]}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.coingecko.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.coingecko.com/en/privacy",children:"CoinGecko Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#wallet-balance",children:"Wallet Balance"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.blockcypher.com/"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.blockcypher.com/privacy.html",children:"BlockCypher Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#code-stats",children:"Code::Stats"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://codestats.net"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://codestats.net/tos#privacy",children:"Code::Stats Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#addyio",children:"addy.io"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://app.addy.io"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://addy.io/privacy/",children:"addy.io Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#vulnerability-feed",children:"Vulnerability Feed"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://services.nvd.nist.gov/rest/json/cves/2.0"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.nist.gov/privacy-policy",children:"NIST Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#exchange-rates",children:"Exchange Rate"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://v6.exchangerate-api.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.exchangerate-api.com/terms",children:"ExchangeRateAPI Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#public-holidays",children:"Public Holidays"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://kayaposoft.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://github.com/jurajmajer/enrico",children:"jurajmajer/enrico"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#covid-19-status",children:"Covid-19 Status"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://disease.sh/v3/covid-19"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://github.com/disease-sh/api",children:"disease-sh/api"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#sports-scores",children:"Sports Scores"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://thesportsdb.com"})}),(0,r.jsx)(s.td,{children:"No Policy Available"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#news-headlines",children:"News Headlines"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.currentsapi.services"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://currentsapi.services/privacy",children:"CurrentsAPI Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#mullvad-status",children:"Mullvad Status"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://am.i.mullvad.net"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://mullvad.net/en/help/privacy-policy/",children:"Mullvad Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#tfl-status",children:"TFL Status"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.tfl.gov.uk"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://tfl.gov.uk/corporate/privacy-and-cookies/",children:"TFL Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#stock-price-history",children:"Stock Price History"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://www.alphavantage.co"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.alphavantage.co/privacy/",children:"AlphaVantage Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#eth-gas-prices",children:"ETH Gas Prices"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://ethgas.watch"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://github.com/wslyvh/ethgaswatch",children:"wslyvh/ethgaswatch"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#joke",children:"Joke"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://v2.jokeapi.dev"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://sv443.net/privacypolicy/en",children:"SV443's Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#chuck-norris-jokes",children:"Chuck Norris Jokes"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.chucknorris.io"})}),(0,r.jsx)(s.td,{children:"No Policy Available"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#xkcd-comic",children:"XKCD Comic"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://xkcd.vercel.app"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://xkcd.com",children:"XKCD"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#flight-data",children:"Flight Data"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://aerodatabox.p.rapidapi.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.aerodatabox.com/#h.p_cxtiyzwf_wqd",children:"AeroDataBox Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#astronomy-picture-of-the-day",children:"Astronomy Picture of the Day"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://apod.as93.net"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.nasa.gov/about/highlights/HP_Privacy.html",children:"NASA's Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#github-trending",children:"GitHub Trending"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://trend.doforce.xyz"})}),(0,r.jsx)(s.td,{children:"No Policy Available"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#github-profile-stats",children:"GitHub Profile Stats"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://github-readme-stats.vercel.app"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://docs.github.com/en/github/site-policy/github-privacy-statement",children:"GitHub's Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#healthchecks-status",children:"Healthchecks Status"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://healthchecks.io"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://healthchecks.io/privacy/",children:"Health-Checks Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#hacker-news-trending",children:"Hacker News Trending"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://hacker-news.firebaseio.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.ycombinator.com/legal#privacy",children:"Y Combinator Privacy Policy"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#minecraft-server-status",children:"Minecraft Server Status"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://api.mcsrvstat.us"})}),(0,r.jsx)(s.td,{children:"No Policy Available"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#mvg",children:"MVG"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://www.mvg.de/api/fib/v2/"})}),(0,r.jsx)(s.td,{children:"No Policy Available"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"/docs/widgets#rescue-time",children:"RescueTime"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"https://www.rescuetime.com"})}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.a,{href:"https://www.rescuetime.com/privacy",children:"RescueTime Privacy Policy"})})]})]})]}),"\n",(0,r.jsx)(s.p,{children:"Note: There are also many widgets that connect to self-hosted services (such as Pi-hole, AdGuard, Glances, Nextcloud, Proxmox, Uptime Kuma, etc.). These only make requests to your own configured server addresses and do not contact any third-party services."}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"dependencies",children:"Dependencies"}),"\n",(0,r.jsxs)(s.p,{children:["As with most web projects, Dashy relies on several ",(0,r.jsx)(s.a,{href:"/docs/credits#dependencies",children:"dependencies"}),"."]}),"\n",(0,r.jsxs)(s.p,{children:["Dependencies can introduce security vulnerabilities, but since all these packages are open source any issues are usually very quickly spotted. Dashy is using Snyk for dependency security monitoring, and you can see ",(0,r.jsx)(s.a,{href:"https://snyk.io/test/github/lissy93/dashy",children:"the latest report here"}),". If any issue is detected by Snyk, a note about it will appear at the top of the Readme, and will usually be fixed within 48 hours."]}),"\n",(0,r.jsxs)(s.p,{children:["Note that packages listed under ",(0,r.jsx)(s.code,{children:"devDependencies"})," section are only used for building the project, and are not included in the production environment."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"securing-your-environment",children:"Securing your Environment"}),"\n",(0,r.jsx)(s.p,{children:"There is very little complexity involved with Dashy, and therefore the attack surface is reasonably small, but it is still important to follow best practices for all your self-hosted apps:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Use SSL/HTTPS"})," for securing traffic in transit, see ",(0,r.jsx)(s.a,{href:"/docs/management#ssl-certificates",children:"Management Docs: SSL Certificates"})]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Configure authentication"})," to prevent unauthorized access, see ",(0,r.jsx)(s.a,{href:"/docs/authentication",children:"Authentication Docs"}),". For internet-facing instances, use ",(0,r.jsx)(s.a,{href:"/docs/authentication#keycloak",children:"Keycloak"}),", ",(0,r.jsx)(s.a,{href:"/docs/authentication#oidc",children:"OIDC"}),", or an ",(0,r.jsx)(s.a,{href:"/docs/authentication#alternative-authentication-methods",children:"alternative server-side method"})]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Place behind a reverse proxy"})," if exposing to the internet, see ",(0,r.jsx)(s.a,{href:"/docs/management#network-exposure",children:"Management Docs: Network Exposure"})]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Harden your containers"})," if running in Docker, see ",(0,r.jsx)(s.a,{href:"/docs/management#container-security",children:"Management Docs: Container Security"})]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Keep Dashy and your system up-to-date"})," to ensure known vulnerabilities are patched"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Configure firewall rules"})," to restrict access to only necessary ports and networks"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.strong,{children:"Use a VPN"})," for private access without exposing Dashy to the public internet"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsxs)(s.strong,{children:["Follow ",(0,r.jsx)(s.a,{href:"https://docs.docker.com/engine/security/",children:"Docker security best practices"})]})," including running as non-root, limiting capabilities, and using read-only volumes"]}),"\n"]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"security-features",children:"Security Features"}),"\n",(0,r.jsx)(s.h3,{id:"subresource-integrity",children:"Subresource Integrity"}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.a,{href:"https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity",children:"Subresource Integrity"})," or SRI is a security feature that enables browsers to verify that resources they fetch are delivered without unexpected manipulation. It works by allowing you to provide a cryptographic hash that a fetched resource must match. This prevents the app from loading any resources that have been manipulated, by verifying the files hashes. It safeguards against the risk of an attacker injecting arbitrary malicious content into any files served up via a CDN."]}),"\n",(0,r.jsxs)(s.p,{children:["Dashy supports SRI, and it is recommended to enable this if you are hosting your dashboard via a public CDN. To enable SRI, set the ",(0,r.jsx)(s.code,{children:"INTEGRITY"})," environmental variable to ",(0,r.jsx)(s.code,{children:"true"}),"."]}),"\n",(0,r.jsx)(s.h3,{id:"ssl",children:"SSL"}),"\n",(0,r.jsxs)(s.p,{children:["Native SSL support is enabled, for setup instructions, see the ",(0,r.jsx)(s.a,{href:"/docs/management#ssl-certificates",children:"Management Docs"})]}),"\n",(0,r.jsx)(s.h3,{id:"authentication",children:"Authentication"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy supports built-in auth, server-based SSO using Keycloak or any OIDC provider, and header-based authentication for reverse proxy setups. Full details of which, along with alternate authentication methods can be found in the ",(0,r.jsx)(s.a,{href:"/docs/authentication",children:"Authentication Docs"}),". If your dashboard is exposed to the internet and/ or contains any sensitive info it is strongly recommended to configure access control with Keycloak, OIDC, or another server-side method."]}),"\n",(0,r.jsx)(s.h3,{id:"configuration-lockdown",children:"Configuration Lockdown"}),"\n",(0,r.jsx)(s.p,{children:"Dashy provides several options to restrict what users can modify:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"appConfig.preventWriteToDisk"})," - Prevents config changes from being saved to the server"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"appConfig.preventLocalSave"})," - Prevents config changes from being saved to browser storage"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"appConfig.disableConfiguration"})," - Hides the config UI from all users"]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"appConfig.disableConfigurationForNonAdmin"})," - Hides the config UI for non-admin users"]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["These can be combined with the ",(0,r.jsx)(s.code,{children:"admin"})," and ",(0,r.jsx)(s.code,{children:"normal"})," user roles to give fine-grained control. Admin users can save config changes, while normal users have read-only access. For more details, see the ",(0,r.jsx)(s.a,{href:"/docs/authentication#permissions",children:"Authentication Docs: Permissions"}),"."]}),"\n",(0,r.jsx)(s.h3,{id:"disabling-features",children:"Disabling Features"}),"\n",(0,r.jsx)(s.p,{children:"You may wish to disable features that you don't want to use, if they involve storing data in the browser or making network requests."}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["To disable smart-sort (uses local storage), set ",(0,r.jsx)(s.code,{children:"appConfig.disableSmartSort: true"})]}),"\n",(0,r.jsxs)(s.li,{children:["To disable update checks (makes external request to GH), set ",(0,r.jsx)(s.code,{children:"appConfig.disableUpdateChecks: true"})]}),"\n",(0,r.jsxs)(s.li,{children:["To disable web search (redirect to external / internal content), set ",(0,r.jsx)(s.code,{children:"appConfig.webSearch.disableWebSearch: true"})]}),"\n",(0,r.jsxs)(s.li,{children:["To keep status checks disabled (external / internal requests), set ",(0,r.jsx)(s.code,{children:"appConfig.statusCheck: false"})]}),"\n",(0,r.jsxs)(s.li,{children:["To keep ping checks disabled (external / internal requests), set ",(0,r.jsx)(s.code,{children:"appConfig.pingCheckEnabled: false"})]}),"\n",(0,r.jsxs)(s.li,{children:["To keep font-awesome icons disabled (external requests), set ",(0,r.jsx)(s.code,{children:"appConfig.enableFontAwesome: false"})]}),"\n",(0,r.jsxs)(s.li,{children:["To keep error reporting disabled (external requests and data collection), set ",(0,r.jsx)(s.code,{children:"appConfig.enableErrorReporting: false"})]}),"\n",(0,r.jsxs)(s.li,{children:["To keep the service worker disabled (stores cache of app in browser data), set ",(0,r.jsx)(s.code,{children:"appConfig.enableServiceWorker: false"})]}),"\n"]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"threat-model",children:"Threat Model"}),"\n",(0,r.jsx)(s.p,{children:"Dashy is a statically-hosted dashboard application, designed to be self-hosted on a private network. This threat model outlines the intended deployment context, trust boundaries, known risks and accepted trade-offs, to help users assess whether Dashy is appropriate for their environment."}),"\n",(0,r.jsx)(s.h3,{id:"intended-deployment",children:"Intended Deployment"}),"\n",(0,r.jsxs)(s.p,{children:["Dashy is designed to run on a ",(0,r.jsx)(s.strong,{children:"private local network"})," (e.g. a home lab), accessed by a ",(0,r.jsx)(s.strong,{children:"small number of trusted users"}),". It is a convenience tool for organizing links to self-hosted services - it is not designed to protect sensitive resources or act as an access control layer."]}),"\n",(0,r.jsxs)(s.p,{children:["If exposed to the internet, Dashy ",(0,r.jsx)(s.strong,{children:"must"})," be placed behind a reverse proxy with server-side authentication (e.g. Authelia, Authentik, Cloudflare Access). The built-in client-side auth is a convenience feature for private networks, not a security boundary."]}),"\n",(0,r.jsx)(s.h3,{id:"trust-boundaries",children:"Trust Boundaries"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Boundary"}),(0,r.jsx)(s.th,{children:"Trusted Side"}),(0,r.jsx)(s.th,{children:"Untrusted Side"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Local network"}),(0,r.jsx)(s.td,{children:"LAN users, self-hosted services"}),(0,r.jsx)(s.td,{children:"The public internet"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsxs)(s.td,{children:["Config file (",(0,r.jsx)(s.code,{children:"conf.yml"}),")"]}),(0,r.jsx)(s.td,{children:"Server admin who writes the config"}),(0,r.jsx)(s.td,{children:"End users who view the dashboard"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Browser storage"}),(0,r.jsx)(s.td,{children:"The current browser session"}),(0,r.jsx)(s.td,{children:"Other domains, other users of the same device"})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"CORS proxy / status checks"}),(0,r.jsx)(s.td,{children:"Configured target URLs (set by admin)"}),(0,r.jsx)(s.td,{children:"Arbitrary URLs (if auth is not enabled)"})]})]})]}),"\n",(0,r.jsx)(s.h3,{id:"assets",children:"Assets"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Asset"}),(0,r.jsx)(s.th,{children:"Description"}),(0,r.jsx)(s.th,{children:"Storage"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Dashboard configuration"}),(0,r.jsx)(s.td,{children:"Service URLs, section layout, app settings"}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"conf.yml"})," on server, optionally cached in browser localStorage"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"User credentials"}),(0,r.jsx)(s.td,{children:"SHA-256 password hashes, Keycloak/OIDC client IDs"}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"conf.yml"})," on server"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"API keys"}),(0,r.jsx)(s.td,{children:"Keys for widget services (weather, stocks, etc.)"}),(0,r.jsxs)(s.td,{children:[(0,r.jsx)(s.code,{children:"conf.yml"})," on server or environment variables"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Auth tokens"}),(0,r.jsx)(s.td,{children:"Session token derived from credentials"}),(0,r.jsxs)(s.td,{children:["Browser cookie (",(0,r.jsx)(s.code,{children:"dashyAuthToken"}),")"]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"User preferences"}),(0,r.jsx)(s.td,{children:"Theme, layout, language, collapsed sections"}),(0,r.jsx)(s.td,{children:"Browser localStorage"})]})]})]}),"\n",(0,r.jsx)(s.h3,{id:"when-dashy-is-not-the-right-choice",children:"When Dashy is NOT the Right Choice"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["You need a ",(0,r.jsx)(s.strong,{children:"multi-tenant"})," dashboard with per-user audit trails"]}),"\n",(0,r.jsxs)(s.li,{children:["You are deploying on the ",(0,r.jsx)(s.strong,{children:"public internet without a reverse proxy"})]}),"\n",(0,r.jsxs)(s.li,{children:["Your dashboard contains ",(0,r.jsx)(s.strong,{children:"secrets or credentials"})," that must be protected from all users who can reach the server"]}),"\n",(0,r.jsxs)(s.li,{children:["You require ",(0,r.jsx)(s.strong,{children:"FIPS-compliant"})," or ",(0,r.jsx)(s.strong,{children:"SOC 2"})," certified software"]}),"\n"]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"known-limitations",children:"Known Limitations"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Report"}),(0,r.jsx)(s.th,{children:"Response"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Client-side auth can be bypassed via browser dev tools"'}),(0,r.jsxs)(s.td,{children:["Correct (only if neither ",(0,r.jsx)(s.code,{children:"ENABLE_HTTP_AUTH"})," is set, nor any other auth mode). Client-side auth is a convenience for private networks, not a security boundary. Use server-side auth for untrusted environments."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"CORS proxy can make requests to internal services"'}),(0,r.jsx)(s.td,{children:"Correct. The CORS proxy is designed to reach services on your network. It is protected by auth middleware when auth is enabled. Do not expose Dashy without auth on an untrusted network."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Status checks can be used for SSRF"'}),(0,r.jsxs)(s.td,{children:["Status check target URLs are set by the server admin in ",(0,r.jsx)(s.code,{children:"conf.yml"}),", not by end users. If auth is enabled, the endpoint is protected."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Password hashes are stored in plaintext in conf.yml"'}),(0,r.jsx)(s.td,{children:"They are SHA-256 hashes, not plaintext passwords. The config file should be readable only by the server admin, and protected by HTTP auth when served."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"localStorage/cookies are not encrypted"'}),(0,r.jsx)(s.td,{children:"Browser storage is scoped to the origin and inaccessible to other domains. On a shared device, use your browser's profile isolation."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"No CSRF protection"'}),(0,r.jsx)(s.td,{children:"Dashy's state-changing operations (config save) are protected by auth middleware. CSRF is a low risk on a private network dashboard."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Docker container runs as root"'}),(0,r.jsxs)(s.td,{children:["The container is sandboxed. For hardened deployments, override with ",(0,r.jsx)(s.code,{children:"--user"})," flag or configure in your Docker Compose file as described in the ",(0,r.jsx)(s.a,{href:"/docs/management#container-security",children:"container security docs"}),"."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Auth cookie is not HttpOnly/Secure"'}),(0,r.jsxs)(s.td,{children:["The token is needed by client-side JavaScript for auth state. On a private network over plain HTTP, the ",(0,r.jsx)(s.code,{children:"Secure"})," flag would break auth. Use HTTPS + a reverse proxy to add these flags if needed."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Iframe/embed widget can load arbitrary URLs"'}),(0,r.jsxs)(s.td,{children:["The widget config is written by the server admin, not end users. If you don't trust your config authors, disable the config editor with ",(0,r.jsx)(s.code,{children:"disableConfiguration"}),"."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"RSS widget renders HTML content"'}),(0,r.jsx)(s.td,{children:"RSS content is sanitized with DOMPurify before rendering. Script tags, event handlers and other dangerous elements are stripped."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"No Content-Security-Policy headers"'}),(0,r.jsx)(s.td,{children:"CSP should be configured at the reverse proxy layer, since the correct policy depends on which widgets and icon CDNs you use. Dashy can't set a universal CSP that works for all configurations."})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"Config backups are not encrypted at rest"'}),(0,r.jsxs)(s.td,{children:["Backups are stored server-side alongside the original config. If an attacker has filesystem access, they already have ",(0,r.jsx)(s.code,{children:"conf.yml"}),". Encryption at rest is the responsibility of the host OS/volume."]})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:'"No rate limiting on endpoints"'}),(0,r.jsx)(s.td,{children:"Rate limiting should be applied at the reverse proxy layer, where it can be tuned per-deployment. Dashy is not designed to be directly exposed to untrusted traffic."})]})]})]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"update--patch-policy",children:"Update & Patch Policy"}),"\n",(0,r.jsxs)(s.p,{children:["We follow Semantic Versioning for all releases. Security fixes are shipped as patch releases as quickly as possible and are published via immutable Git tags and Docker image tags. Users are encouraged to pin to a specific version in production and monitor releases on GitHub for security updates. The ",(0,r.jsx)(s.code,{children:":latest"})," Docker tag is provided for convenience but should not be relied on in production environments."]}),"\n",(0,r.jsx)(s.hr,{}),"\n",(0,r.jsx)(s.h2,{id:"reporting-a-security-issue",children:"Reporting a Security Issue"}),"\n",(0,r.jsxs)(s.p,{children:["Please see our ",(0,r.jsx)(s.a,{href:"https://github.com/Lissy93/dashy/?tab=security-ov-file",children:"Security.md"})," doc for how to report issues.\nWe have an actively monitored security mailbox supporting PGP, as well as a GitHub Advisories vulnerability reporting program."]})]})}function h(e={}){const{wrapper:s}={...(0,t.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},28453(e,s,n){n.d(s,{R:()=>o,x:()=>d});var i=n(96540);const r={},t=i.createContext(r);function o(e){const s=i.useContext(t);return i.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function d(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),i.createElement(t.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/9be9f402.139a9412.js b/assets/js/9be9f402.139a9412.js deleted file mode 100644 index 4f2179a0..00000000 --- a/assets/js/9be9f402.139a9412.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[5552],{38803(e,n,s){s.r(n),s.d(n,{assets:()=>c,contentTitle:()=>t,default:()=>x,frontMatter:()=>l,metadata:()=>i,toc:()=>h});const i=JSON.parse('{"id":"widgets","title":"Widgets","description":"Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets available out-of-the-box as well as support for custom widgets to display stats from almost any service with an API.","source":"@site/docs/widgets.md","sourceDirName":".","slug":"/widgets","permalink":"/docs/widgets","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/widgets.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Icons","permalink":"/docs/icons"},"next":{"title":"Theming","permalink":"/docs/theming"}}');var r=s(74848),d=s(28453);const l={},t="Widgets",c={},h=[{value:"Contents",id:"contents",level:2},{value:"General Widgets",id:"general-widgets",level:2},{value:"Clock",id:"clock",level:3},{value:"Options",id:"options",level:4},{value:"Example",id:"example",level:4},{value:"Info",id:"info",level:4},{value:"Weather",id:"weather",level:3},{value:"Options",id:"options-1",level:4},{value:"Example",id:"example-1",level:4},{value:"Info",id:"info-1",level:4},{value:"Weather Forecast",id:"weather-forecast",level:3},{value:"Options",id:"options-2",level:4},{value:"Example",id:"example-2",level:4},{value:"Info",id:"info-2",level:4},{value:"RSS Feed",id:"rss-feed",level:3},{value:"Options",id:"options-3",level:4},{value:"Example",id:"example-3",level:4},{value:"Info",id:"info-3",level:4},{value:"Image",id:"image",level:3},{value:"Options",id:"options-4",level:4},{value:"Example",id:"example-4",level:4},{value:"Info",id:"info-4",level:4},{value:"Public IP",id:"public-ip",level:3},{value:"Options",id:"options-5",level:4},{value:"Example",id:"example-5",level:4},{value:"Supported providers",id:"supported-providers",level:4},{value:"Info",id:"info-5",level:4},{value:"IP Blacklist",id:"ip-blacklist",level:3},{value:"Options",id:"options-6",level:4},{value:"Example",id:"example-6",level:4},{value:"Info",id:"info-6",level:4},{value:"Domain Monitor",id:"domain-monitor",level:3},{value:"Options",id:"options-7",level:4},{value:"Example",id:"example-7",level:4},{value:"Info",id:"info-7",level:4},{value:"Crypto Watch List",id:"crypto-watch-list",level:3},{value:"Options",id:"options-8",level:4},{value:"Example",id:"example-8",level:4},{value:"Info",id:"info-8",level:4},{value:"Crypto Token Price History",id:"crypto-token-price-history",level:3},{value:"Options",id:"options-9",level:4},{value:"Example",id:"example-9",level:4},{value:"Info",id:"info-9",level:4},{value:"Wallet Balance",id:"wallet-balance",level:3},{value:"Options",id:"options-10",level:4},{value:"Example",id:"example-10",level:4},{value:"Info",id:"info-10",level:4},{value:"Code Stats",id:"code-stats",level:3},{value:"Options",id:"options-11",level:4},{value:"Example",id:"example-11",level:4},{value:"Info",id:"info-11",level:4},{value:"Mullvad Status",id:"mullvad-status",level:3},{value:"Options",id:"options-12",level:4},{value:"Example",id:"example-12",level:4},{value:"Info",id:"info-12",level:4},{value:"addy.io",id:"addyio",level:3},{value:"Options",id:"options-13",level:4},{value:"Example",id:"example-13",level:4},{value:"Info",id:"info-13",level:4},{value:"Vulnerability Feed",id:"vulnerability-feed",level:3},{value:"Options",id:"options-14",level:4},{value:"Example",id:"example-14",level:4},{value:"Info",id:"info-14",level:4},{value:"Exchange Rates",id:"exchange-rates",level:3},{value:"Options",id:"options-15",level:4},{value:"Example",id:"example-15",level:4},{value:"Info",id:"info-15",level:4},{value:"Public Holidays",id:"public-holidays",level:3},{value:"Options",id:"options-16",level:4},{value:"Example",id:"example-16",level:4},{value:"Info",id:"info-16",level:4},{value:"Covid-19 Status",id:"covid-19-status",level:3},{value:"Options",id:"options-17",level:4},{value:"Example",id:"example-17",level:4},{value:"Info",id:"info-17",level:4},{value:"Sports Scores",id:"sports-scores",level:3},{value:"Options",id:"options-18",level:4},{value:"Example",id:"example-18",level:4},{value:"Info",id:"info-18",level:4},{value:"News Headlines",id:"news-headlines",level:3},{value:"Options",id:"options-19",level:4},{value:"Example",id:"example-19",level:4},{value:"Info",id:"info-19",level:4},{value:"TFL Status",id:"tfl-status",level:3},{value:"Options",id:"options-20",level:4},{value:"Example",id:"example-20",level:4},{value:"Info",id:"info-20",level:4},{value:"Stock Price History",id:"stock-price-history",level:3},{value:"Options",id:"options-21",level:4},{value:"Example",id:"example-21",level:4},{value:"Info",id:"info-21",level:4},{value:"ETH Gas Prices",id:"eth-gas-prices",level:3},{value:"Options",id:"options-22",level:4},{value:"Example",id:"example-22",level:4},{value:"Info",id:"info-22",level:4},{value:"Joke",id:"joke",level:3},{value:"Options",id:"options-23",level:4},{value:"Example",id:"example-23",level:4},{value:"Info",id:"info-23",level:4},{value:"Chuck Norris quotes",id:"chuck-norris-quotes",level:3},{value:"Options",id:"options-24",level:4},{value:"Example",id:"example-24",level:4},{value:"Info",id:"info-24",level:4},{value:"XKCD Comics",id:"xkcd-comics",level:3},{value:"Options",id:"options-25",level:4},{value:"Example",id:"example-25",level:4},{value:"Info",id:"info-25",level:4},{value:"Flight Data",id:"flight-data",level:3},{value:"Options",id:"options-26",level:4},{value:"Example",id:"example-26",level:4},{value:"Info",id:"info-26",level:4},{value:"Astronomy Picture of the Day",id:"astronomy-picture-of-the-day",level:3},{value:"Options",id:"options-27",level:4},{value:"Example",id:"example-27",level:4},{value:"Info",id:"info-27",level:4},{value:"GitHub Trending",id:"github-trending",level:3},{value:"Options",id:"options-28",level:4},{value:"Example",id:"example-28",level:4},{value:"Info",id:"info-28",level:4},{value:"GitHub Profile Stats",id:"github-profile-stats",level:3},{value:"Options",id:"options-29",level:4},{value:"Example",id:"example-29",level:4},{value:"Info",id:"info-29",level:4},{value:"HealthChecks Status",id:"healthchecks-status",level:3},{value:"Options",id:"options-30",level:4},{value:"Example",id:"example-30",level:5},{value:"Info",id:"info-30",level:4},{value:"Hackernews Trending",id:"hackernews-trending",level:3},{value:"Options",id:"options-31",level:4},{value:"Example",id:"example-31",level:5},{value:"MVG Departure",id:"mvg-departure",level:3},{value:"Options",id:"options-32",level:4},{value:"Info",id:"info-31",level:4},{value:"MVG Connection",id:"mvg-connection",level:3},{value:"Options",id:"options-33",level:4},{value:"Info",id:"info-32",level:4},{value:"Custom List",id:"custom-list",level:3},{value:"Options",id:"options-34",level:4},{value:"Json Schema",id:"json-schema",level:4},{value:"Notes",id:"notes",level:4},{value:"Example",id:"example-32",level:4},{value:"Info",id:"info-33",level:4},{value:"Custom search",id:"custom-search",level:3},{value:"Options",id:"options-35",level:4},{value:"Notes",id:"notes-1",level:4},{value:"Example",id:"example-33",level:4},{value:"Info",id:"info-34",level:4},{value:"RescueTime Overview",id:"rescuetime-overview",level:3},{value:"Options",id:"options-36",level:4},{value:"Example",id:"example-34",level:4},{value:"Info",id:"info-35",level:4},{value:"Minecraft Server",id:"minecraft-server",level:3},{value:"Options",id:"options-37",level:4},{value:"Example",id:"example-35",level:4},{value:"Info",id:"info-36",level:4},{value:"Self-Hosted Services Widgets",id:"self-hosted-services-widgets",level:2},{value:"System Info",id:"system-info",level:3},{value:"Options",id:"options-38",level:4},{value:"Example",id:"example-36",level:4},{value:"Info",id:"info-37",level:4},{value:"CPU History (NetData)",id:"cpu-history-netdata",level:3},{value:"Options",id:"options-39",level:4},{value:"Example",id:"example-37",level:4},{value:"Info",id:"info-38",level:4},{value:"Memory History (NetData)",id:"memory-history-netdata",level:3},{value:"Options",id:"options-40",level:4},{value:"Example",id:"example-38",level:4},{value:"Info",id:"info-39",level:4},{value:"Load History (NetData)",id:"load-history-netdata",level:3},{value:"Options",id:"options-41",level:4},{value:"Example",id:"example-39",level:4},{value:"Info",id:"info-40",level:4},{value:"Pi-Hole Stats",id:"pi-hole-stats",level:3},{value:"Options",id:"options-42",level:4},{value:"Example",id:"example-40",level:4},{value:"Info",id:"info-41",level:4},{value:"Pi-Hole Stats v6",id:"pi-hole-stats-v6",level:3},{value:"Options",id:"options-43",level:4},{value:"Example",id:"example-41",level:4},{value:"Info",id:"info-42",level:4},{value:"Pi-Hole Queries",id:"pi-hole-queries",level:3},{value:"Options",id:"options-44",level:4},{value:"Example",id:"example-42",level:4},{value:"Info",id:"info-43",level:4},{value:"Pi-Hole Queries v6",id:"pi-hole-queries-v6",level:3},{value:"Options",id:"options-45",level:4},{value:"Example",id:"example-43",level:4},{value:"Info",id:"info-44",level:4},{value:"Pi-Hole Recent Traffic",id:"pi-hole-recent-traffic",level:3},{value:"Options",id:"options-46",level:4},{value:"Example",id:"example-44",level:4},{value:"Info",id:"info-45",level:4},{value:"Pi-Hole Recent Traffic v6",id:"pi-hole-recent-traffic-v6",level:3},{value:"Options",id:"options-47",level:4},{value:"Example",id:"example-45",level:4},{value:"Info",id:"info-46",level:4},{value:"Stat Ping Statuses",id:"stat-ping-statuses",level:3},{value:"Options",id:"options-48",level:4},{value:"Example",id:"example-46",level:4},{value:"Info",id:"info-47",level:4},{value:"Synology Download Station",id:"synology-download-station",level:3},{value:"Options",id:"options-49",level:4},{value:"Example",id:"example-47",level:4},{value:"Info",id:"info-48",level:4},{value:"AdGuard Home Block Stats",id:"adguard-home-block-stats",level:3},{value:"Options",id:"options-50",level:4},{value:"Example",id:"example-48",level:4},{value:"Info",id:"info-49",level:4},{value:"AdGuard Home Filters",id:"adguard-home-filters",level:3},{value:"Options",id:"options-51",level:4},{value:"Example",id:"example-49",level:4},{value:"Info",id:"info-50",level:4},{value:"AdGuard Home DNS Info",id:"adguard-home-dns-info",level:3},{value:"Options",id:"options-52",level:4},{value:"Example",id:"example-50",level:4},{value:"Info",id:"info-51",level:4},{value:"AdGuard Home Top Domains",id:"adguard-home-top-domains",level:3},{value:"Options",id:"options-53",level:4},{value:"Example",id:"example-51",level:4},{value:"Info",id:"info-52",level:4},{value:"Nextcloud User",id:"nextcloud-user",level:3},{value:"Options",id:"options-54",level:4},{value:"Example",id:"example-52",level:4},{value:"Info",id:"info-53",level:4},{value:"Nextcloud User Statuses",id:"nextcloud-user-statuses",level:3},{value:"Options",id:"options-55",level:4},{value:"Example",id:"example-53",level:4},{value:"Info",id:"info-54",level:4},{value:"Nextcloud Notifications",id:"nextcloud-notifications",level:3},{value:"Options",id:"options-56",level:4},{value:"Example",id:"example-54",level:4},{value:"Info",id:"info-55",level:4},{value:"Nextcloud System",id:"nextcloud-system",level:3},{value:"Options",id:"options-57",level:4},{value:"Example",id:"example-55",level:4},{value:"Info",id:"info-56",level:4},{value:"Nextcloud Stats",id:"nextcloud-stats",level:3},{value:"Options",id:"options-58",level:4},{value:"Example",id:"example-56",level:4},{value:"Info",id:"info-57",level:4},{value:"Nextcloud PHP OPcache Stats",id:"nextcloud-php-opcache-stats",level:3},{value:"Options",id:"options-59",level:4},{value:"Example",id:"example-57",level:4},{value:"Info",id:"info-58",level:4},{value:"ntfy stream",id:"ntfy-stream",level:3},{value:"Options",id:"options-60",level:4},{value:"Example",id:"example-58",level:4},{value:"Info",id:"info-59",level:4},{value:"Proxmox lists",id:"proxmox-lists",level:3},{value:"Options",id:"options-61",level:4},{value:"Example",id:"example-59",level:4},{value:"Info",id:"info-60",level:4},{value:"Troubleshooting",id:"troubleshooting",level:4},{value:"Sabnzbd",id:"sabnzbd",level:3},{value:"Options",id:"options-62",level:4},{value:"Example",id:"example-60",level:4},{value:"Info",id:"info-61",level:4},{value:"Gluetun VPN Info",id:"gluetun-vpn-info",level:3},{value:"Options",id:"options-63",level:4},{value:"Example",id:"example-61",level:4},{value:"Info",id:"info-62",level:4},{value:"Drone CI Builds",id:"drone-ci-builds",level:3},{value:"Options",id:"options-64",level:4},{value:"Example",id:"example-62",level:4},{value:"Info",id:"info-63",level:4},{value:"Filebrowser",id:"filebrowser",level:3},{value:"Options",id:"options-65",level:4},{value:"Example",id:"example-63",level:4},{value:"Widget Sections",id:"widget-sections",level:4},{value:"Info",id:"info-64",level:4},{value:"Linkding",id:"linkding",level:3},{value:"Options",id:"options-66",level:4},{value:"Example",id:"example-64",level:4},{value:"Info",id:"info-65",level:4},{value:"Uptime Kuma",id:"uptime-kuma",level:3},{value:"Options",id:"options-67",level:4},{value:"Example",id:"example-65",level:4},{value:"Info",id:"info-66",level:4},{value:"Uptime Kuma Status Page",id:"uptime-kuma-status-page",level:3},{value:"Options",id:"options-68",level:4},{value:"Example",id:"example-66",level:4},{value:"Info",id:"info-67",level:4},{value:"Tactical RMM",id:"tactical-rmm",level:3},{value:"Options",id:"options-69",level:4},{value:"Example",id:"example-67",level:4},{value:"Info",id:"info-68",level:4},{value:"System Resource Monitoring",id:"system-resource-monitoring",level:2},{value:"Glances",id:"glances",level:3},{value:"Options",id:"options-70",level:4},{value:"Info",id:"info-69",level:4},{value:"Screenshot",id:"screenshot",level:4},{value:"Current CPU Usage",id:"current-cpu-usage",level:3},{value:"Example",id:"example-68",level:4},{value:"Current CPU Usage Speedometer",id:"current-cpu-usage-speedometer",level:3},{value:"Example",id:"example-69",level:4},{value:"CPU Usage Per Core",id:"cpu-usage-per-core",level:3},{value:"Example",id:"example-70",level:4},{value:"CPU Usage History",id:"cpu-usage-history",level:3},{value:"Options",id:"options-71",level:4},{value:"Example",id:"example-71",level:4},{value:"Current Memory Usage",id:"current-memory-usage",level:3},{value:"Example",id:"example-72",level:4},{value:"Current Memory Usage Speedometer",id:"current-memory-usage-speedometer",level:3},{value:"Example",id:"example-73",level:4},{value:"Memory Usage History",id:"memory-usage-history",level:3},{value:"Options",id:"options-72",level:4},{value:"Example",id:"example-74",level:4},{value:"Disk Space",id:"disk-space",level:3},{value:"Example",id:"example-75",level:4},{value:"Disk IO",id:"disk-io",level:3},{value:"Example",id:"example-76",level:4},{value:"System Load",id:"system-load",level:3},{value:"Example",id:"example-77",level:4},{value:"Uptime",id:"uptime",level:3},{value:"Example",id:"example-78",level:4},{value:"System Load History",id:"system-load-history",level:3},{value:"Example",id:"example-79",level:4},{value:"Network Interfaces",id:"network-interfaces",level:3},{value:"Example",id:"example-80",level:4},{value:"Network Traffic",id:"network-traffic",level:3},{value:"Example",id:"example-81",level:4},{value:"Resource Usage Alerts",id:"resource-usage-alerts",level:3},{value:"Example",id:"example-82",level:4},{value:"IP Address",id:"ip-address",level:3},{value:"Example",id:"example-83",level:4},{value:"CPU Temp",id:"cpu-temp",level:3},{value:"Options",id:"options-73",level:4},{value:"Example",id:"example-84",level:4},{value:"Compact Metrics",id:"compact-metrics",level:3},{value:"Options",id:"options-74",level:4},{value:"Example",id:"example-85",level:4},{value:"Dynamic Widgets",id:"dynamic-widgets",level:2},{value:"Iframe Widget",id:"iframe-widget",level:3},{value:"Options",id:"options-75",level:4},{value:"Example",id:"example-86",level:4},{value:"HTML Embedded Widget",id:"html-embedded-widget",level:3},{value:"Options",id:"options-76",level:4},{value:"Example",id:"example-87",level:4},{value:"API Response",id:"api-response",level:3},{value:"Options",id:"options-77",level:4},{value:"Example",id:"example-88",level:4},{value:"Info",id:"info-70",level:4},{value:"Prometheus Data",id:"prometheus-data",level:3},{value:"Data Feed",id:"data-feed",level:3},{value:"Example",id:"example-89",level:4},{value:"Usage & Customizations",id:"usage--customizations",level:2},{value:"Widget Usage Guide",id:"widget-usage-guide",level:3},{value:"Continuous Updates",id:"continuous-updates",level:3},{value:"Proxying Requests",id:"proxying-requests",level:3},{value:"Handling Secrets",id:"handling-secrets",level:3},{value:"Setting Timeout",id:"setting-timeout",level:3},{value:"Adding Labels",id:"adding-labels",level:3},{value:"Ignoring Errors",id:"ignoring-errors",level:3},{value:"Widget Styling",id:"widget-styling",level:3},{value:"Customizing Charts",id:"customizing-charts",level:3},{value:"Language Translations",id:"language-translations",level:3},{value:"Widget UI Options",id:"widget-ui-options",level:3},{value:"Build your own Widget",id:"build-your-own-widget",level:3},{value:"Requesting a Widget",id:"requesting-a-widget",level:3},{value:"Troubleshooting Widget Errors",id:"troubleshooting-widget-errors",level:3},{value:"CORS Errors",id:"cors-errors",level:4},{value:"Option 1 - Ensure Correct Protocol",id:"option-1---ensure-correct-protocol",level:4},{value:"Option 2 - Set Headers",id:"option-2---set-headers",level:4},{value:"Option 3 - Proxying Request",id:"option-3---proxying-request",level:4},{value:"Option 4 - Use a plugin",id:"option-4---use-a-plugin",level:4},{value:"Raising an Issue",id:"raising-an-issue",level:3}];function o(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",h5:"h5",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"widgets",children:"Widgets"})}),"\n",(0,r.jsx)(n.p,{children:"Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets available out-of-the-box as well as support for custom widgets to display stats from almost any service with an API."}),"\n",(0,r.jsx)(n.h2,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#general-widgets",children:"General Widgets"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#clock",children:"Clock"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#weather",children:"Weather"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#weather-forecast",children:"Weather Forecast"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#rss-feed",children:"RSS Feed"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#image",children:"Image"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#public-ip",children:"Public IP Address"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ip-blacklist",children:"IP Blacklist Checker"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#domain-monitor",children:"Domain Monitor"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#crypto-watch-list",children:"Crypto Watch List"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#crypto-token-price-history",children:"Crypto Price History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#wallet-balance",children:"Crypto Wallet Balance"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#code-stats",children:"Code Stats"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#mullvad-status",children:"Mullvad Status"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#addyio",children:"Email Aliases (addy.io)"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#vulnerability-feed",children:"Vulnerability Feed"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#exchange-rates",children:"Exchange Rates"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#public-holidays",children:"Public Holidays"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#covid-19-status",children:"Covid-19 Status"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sports-scores",children:"Sports Scores"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#news-headlines",children:"News Headlines"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#tfl-status",children:"TFL Status"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#stock-price-history",children:"Stock Price History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#eth-gas-prices",children:"ETH Gas Prices"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#joke",children:"Joke of the Day"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#chuck-norris-quotes",children:"Chuck Norris quotes"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#xkcd-comics",children:"XKCD Comics"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#flight-data",children:"Flight Data"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#astronomy-picture-of-the-day",children:"NASA APOD"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#github-trending",children:"GitHub Trending"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#github-profile-stats",children:"GitHub Profile Stats"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#healthchecks-status",children:"Healthchecks Status"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#hackernews-trending",children:"Hackernews Trending"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#mvg-departure",children:"Mvg Departure"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#mvg-connection",children:"Mvg Connection"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#custom-search",children:"Custom search"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#rescuetime-overview",children:"Rescuetime overview"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#minecraft-server",children:"Minecraft Server"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#self-hosted-services-widgets",children:"Self-Hosted Services Widgets"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#system-info",children:"System Info"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cpu-history-netdata",children:"CPU History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#memory-history-netdata",children:"Memory History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#load-history-netdata",children:"System Load History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-stats",children:"Pi-Hole Stats"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-stats-v6",children:"Pi-Hole Stats v6"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-queries",children:"Pi-Hole Queries"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-queries-v6",children:"Pi-Hole Queries v6"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-recent-traffic",children:"Pi-Hole Recent Traffic"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-recent-traffic-v6",children:"Pi-Hole Recent Traffic v6"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#stat-ping-statuses",children:"Stat Ping Statuses"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#synology-download-station",children:"Synology Download Station"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#adguard-home-block-stats",children:"AdGuard Home Block Stats"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#adguard-home-filters",children:"AdGuard Home Filters"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#adguard-home-dns-info",children:"AdGuard Home DNS Info"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#adguard-home-top-domains",children:"AdGuard Home Top Domains"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-user",children:"Nextcloud User"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-user-statuses",children:"Nextcloud User Statuses"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-notifications",children:"Nextcloud Notifications"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-system",children:"Nextcloud System"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-stats",children:"Nextcloud Stats"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-php-opcache-stats",children:"Nextcloud PHP OPcache"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ntfy-stream",children:"Ntfy stream"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#proxmox-lists",children:"Proxmox lists"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sabnzbd",children:"Sabnzbd"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#gluetun-vpn-info",children:"Gluetun VPN Info"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#drone-ci-builds",children:"Drone CI Build"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#filebrowser",children:"Filebrowser"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#linkding",children:"Linkding"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#uptime-kuma",children:"Uptime Kuma"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#uptime-kuma-status-page",children:"Uptime Kuma Status Page"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#tactical-rmm",children:"Tactical RMM"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#system-resource-monitoring",children:"System Resource Monitoring"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#current-cpu-usage",children:"CPU Usage Current"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cpu-usage-per-core",children:"CPU Usage Per Core"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cpu-usage-history",children:"CPU Usage History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#current-memory-usage",children:"Memory Usage Current"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#memory-usage-history",children:"Memory Usage History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#disk-space",children:"Disk Space"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#disk-io",children:"Disk IO"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#system-load",children:"System Load"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#system-load-history",children:"System Load History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#network-interfaces",children:"Network Interfaces"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#network-traffic",children:"Network Traffic"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#resource-usage-alerts",children:"Resource Usage Alerts"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ip-address",children:"Public & Private IP"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cpu-temp",children:"CPU Temperature"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#compact-metrics",children:"Compact Metrics"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#dynamic-widgets",children:"Dynamic Widgets"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#iframe-widget",children:"Iframe Widget"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#html-embedded-widget",children:"HTML Embed Widget"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#api-response",children:"API Response"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#prometheus-data",children:"Prometheus Data"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#data-feed",children:"Data Feed"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#usage--customizations",children:"Usage & Customizations"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-usage-guide",children:"Widget Usage Guide"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#continuous-updates",children:"Continuous Updates"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#proxying-requests",children:"Proxying Requests"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#handling-secrets",children:"Handling Secrets"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#setting-timeout",children:"Setting Timeout"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#adding-labels",children:"Adding Labels"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ignoring-errors",children:"Ignoring Errors"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-styling",children:"Custom CSS Styling"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#customizing-charts",children:"Customizing Charts"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#language-translations",children:"Language Translations"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-ui-options",children:"Widget UI Options"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#build-your-own-widget",children:"Building a Widget"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#requesting-a-widget",children:"Requesting a Widget"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#troubleshooting-widget-errors",children:"Troubleshooting"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"general-widgets",children:"General Widgets"}),"\n",(0,r.jsx)(n.h3,{id:"clock",children:"Clock"}),"\n",(0,r.jsx)(n.p,{children:"A simple, live-updating time and date widget with time-zone support. All fields are optional."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/clock"})}),"\n",(0,r.jsx)(n.h4,{id:"options",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"timeZone"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The time zone to display date and time in.",(0,r.jsx)(n.br,{})," Specified as Region/City, for example: ",(0,r.jsx)(n.code,{children:"Australia/Melbourne"}),". See the ",(0,r.jsx)(n.a,{href:"https://timezonedb.com/time-zones",children:"Time Zone DB"})," for a full list of supported TZs. Defaults to the browser / device's local time"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"format"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["A country code for displaying the date and time in local format.",(0,r.jsx)(n.br,{}),"Specified as ",(0,r.jsx)(n.code,{children:"[ISO-3166]-[ISO-639]"}),", for example: ",(0,r.jsx)(n.code,{children:"en-AU"}),". See ",(0,r.jsx)(n.a,{href:"https://www.fincher.org/Utilities/CountryLanguageList.shtml",children:"here"})," for a full list of locales. Defaults to the browser / device's region"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"customCityName"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"By default the city from the time-zone is shown, but setting this value will override that text"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideDate"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", the date and city will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideSeconds"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", seconds will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"use12Hour"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", 12 hour time will be displayed. Defaults to the settings suggested by the current ",(0,r.jsx)(n.code,{children:"format"})," and ",(0,r.jsx)(n.code,{children:"timeZone"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: clock\n options:\n timeZone: Europe/London\n format: en-GB\n hideDate: false\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info",children:"Info"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"No external data requests."})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"weather",children:"Weather"}),"\n",(0,r.jsx)(n.p,{children:"A simple, live-updating local weather component, showing temperature, conditions and more info."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/weather"})}),"\n",(0,r.jsx)(n.h4,{id:"options-1",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your OpenWeatherMap API key. You can get one for free at ",(0,r.jsx)(n.a,{href:"https://openweathermap.org/",children:"openweathermap.org"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"city"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A city name to use for fetching weather. This can also be a state code or country code, following the ISO-3166 format"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cityId"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["An OpenWeatherMap numeric city ID, used to disambiguate cities that share a name. You can find the ID in the URL of the city's page on ",(0,r.jsx)(n.a,{href:"https://openweathermap.org/",children:"openweathermap.org"})," (e.g. ",(0,r.jsx)(n.code,{children:"2643743"})," for London, GB). If provided, this will override the ",(0,r.jsx)(n.code,{children:"city"})," option"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"units"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The units to use for displaying data, can be either ",(0,r.jsx)(n.code,{children:"metric"})," or ",(0,r.jsx)(n.code,{children:"imperial"}),". Defaults to ",(0,r.jsx)(n.code,{children:"metric"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideDetails"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", the additional details (wind, humidity, pressure, etc) will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lat"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["To show weather for a specific location, you can provide the latitude and longitude coordinates. If provided, this will override the ",(0,r.jsx)(n.code,{children:"city"})," and ",(0,r.jsx)(n.code,{children:"cityId"})," options"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lon"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["To show weather for a specific location, you can provide the latitude and longitude coordinates. If provided, this will override the ",(0,r.jsx)(n.code,{children:"city"})," and ",(0,r.jsx)(n.code,{children:"cityId"})," options"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-1",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: weather\n options:\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n city: London\n units: metric\n hideDetails: true\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-1",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://openweather.co.uk/privacy-policy",children:"OWM Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"weather-forecast",children:"Weather Forecast"}),"\n",(0,r.jsxs)(n.p,{children:["Displays the weather (temperature and conditions) for the next few days for a given location. Note that this requires either the free ",(0,r.jsx)(n.a,{href:"https://home.openweathermap.org/students",children:"OpenWeatherMap Student Plan"}),", or the Premium Plan."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/weather-forecast"})}),"\n",(0,r.jsx)(n.h4,{id:"options-2",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your OpenWeatherMap API key. You can get one at ",(0,r.jsx)(n.a,{href:"https://openweathermap.org/",children:"openweathermap.org"})," or for free via the ",(0,r.jsx)(n.a,{href:"https://home.openweathermap.org/students",children:"OWM Student Plan"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"city"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A city name to use for fetching weather. This can also be a state code or country code, following the ISO-3166 format"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cityId"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["An OpenWeatherMap numeric city ID, used to disambiguate cities that share a name. You can find the ID in the URL of the city's page on ",(0,r.jsx)(n.a,{href:"https://openweathermap.org/",children:"openweathermap.org"})," (e.g. ",(0,r.jsx)(n.code,{children:"2643743"})," for London, GB). If provided, this will override the ",(0,r.jsx)(n.code,{children:"city"})," option"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lat"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Latitude for a specific location. If provided alongside ",(0,r.jsx)(n.code,{children:"lon"}),", this will override the ",(0,r.jsx)(n.code,{children:"city"})," and ",(0,r.jsx)(n.code,{children:"cityId"})," options"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lon"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Longitude for a specific location. If provided alongside ",(0,r.jsx)(n.code,{children:"lat"}),", this will override the ",(0,r.jsx)(n.code,{children:"city"})," and ",(0,r.jsx)(n.code,{children:"cityId"})," options"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"numDays"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of days to display of forecast info to display. Defaults to ",(0,r.jsx)(n.code,{children:"4"}),", max ",(0,r.jsx)(n.code,{children:"16"})," days"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"units"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The units to use for displaying data, can be either ",(0,r.jsx)(n.code,{children:"metric"})," or ",(0,r.jsx)(n.code,{children:"imperial"}),". Defaults to ",(0,r.jsx)(n.code,{children:"metric"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideDetails"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", the additional details (wind, humidity, pressure, etc) will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-2",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: weather-forecast\n options:\n city: California\n numDays: 6\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n units: imperial\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-2",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udd34 Premium (free for personal use only)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://openweather.co.uk/privacy-policy",children:"OWM Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"rss-feed",children:"RSS Feed"}),"\n",(0,r.jsx)(n.p,{children:"Display news and updates from any RSS-enabled service."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/rss"})}),"\n",(0,r.jsx)(n.h4,{id:"options-3",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"rssUrl"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL location of your RSS feed"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["An API key for ",(0,r.jsx)(n.a,{href:"https://rss2json.com/",children:"rss2json"}),". It's free, and will allow you to make 10,000 requests per day, you can sign up ",(0,r.jsx)(n.a,{href:"https://rss2json.com/sign-up",children:"here"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"The number of posts to return. If you haven't specified an API key, this will be limited to 10"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"orderBy"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["How results should be sorted. Can be either ",(0,r.jsx)(n.code,{children:"pubDate"}),", ",(0,r.jsx)(n.code,{children:"author"})," or ",(0,r.jsx)(n.code,{children:"title"}),". Defaults to ",(0,r.jsx)(n.code,{children:"pubDate"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"orderDirection"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Order direction of feed items to return. Can be either ",(0,r.jsx)(n.code,{children:"asc"})," or ",(0,r.jsx)(n.code,{children:"desc"}),". Defaults to ",(0,r.jsx)(n.code,{children:"desc"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"parseLocally"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If true parse the rss feed locally instead of using the rss2json API."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-3",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: rss-feed\n options:\n rssUrl: https://www.schneier.com/blog/atom.xml\n apiKey: xxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-3",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free Plan (up to 10,000 requests / day)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://rss2json.com/privacy-policy",children:"Rss2Json Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"image",children:"Image"}),"\n",(0,r.jsx)(n.p,{children:"Displays an image."}),"\n",(0,r.jsxs)(n.p,{children:["This may be useful if you have a service (such as Grafana - ",(0,r.jsx)(n.a,{href:"https://mattionline.de/grafana-api-export-graph-as-png/",children:"see example"}),"), which periodically exports charts or other data as an image."]}),"\n",(0,r.jsxs)(n.p,{children:["You can also store images within Dashy's public directory (using a Docker volume), and reference them directly. E.g. ",(0,r.jsx)(n.code,{children:"-v ./path/to/my-homelab-logo.png:/app/public/logo.png"}),", then in the widget ",(0,r.jsx)(n.code,{children:"imagePath: /logo.png"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Similarly, any web service that serves up widgets as image can be used. E.g. you could show current star chart for a GitHub repo, with: ",(0,r.jsx)(n.code,{children:"imagePath: https://starchart.cc/Lissy93/dashy.svg"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["If you'd like to embed a live screenshot, of all or just part of a website, then this can be done using ",(0,r.jsx)(n.a,{href:"https://apiflash.com/",children:"API Flash"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Or what about showing a photo of the day? Try ",(0,r.jsx)(n.code,{children:"https://source.unsplash.com/random/400x300"})," or ",(0,r.jsx)(n.code,{children:"https://picsum.photos/400/300"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"300",src:"https://pixelflare.cc/alicia/dashy/image"})}),"\n",(0,r.jsx)(n.h4,{id:"options-4",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"imagePath"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The path (local or remote) of the image to display"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"imageWidth"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify a fixed width for rendered image. Accepts either integer value in ",(0,r.jsx)(n.code,{children:"px"}),", or any string value with units (e.g. ",(0,r.jsx)(n.code,{children:"420"}),", ",(0,r.jsx)(n.code,{children:"100px"}),", ",(0,r.jsx)(n.code,{children:"6.9rem"}),") (defaults to ",(0,r.jsx)(n.code,{children:"auto"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"imageHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify a fixed height for rendered image. Accepts either integer value in ",(0,r.jsx)(n.code,{children:"px"}),", or any string value with units (e.g. ",(0,r.jsx)(n.code,{children:"420"}),", ",(0,r.jsx)(n.code,{children:"100px"}),", ",(0,r.jsx)(n.code,{children:"6.9rem"}),") (defaults to ",(0,r.jsx)(n.code,{children:"auto"}),")"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-4",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: image\n options:\n imagePath: https://i.ibb.co/yhbt6CY/dashy.png\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-4",children:"Info"}),"\n",(0,r.jsx)(n.p,{children:"Unless image fetched from remote source, no external data request is made."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"public-ip",children:"Public IP"}),"\n",(0,r.jsx)(n.p,{children:"Displays your public IP address, ISP and approximate location."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/public-ip"})}),"\n",(0,r.jsx)(n.h4,{id:"options-5",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"provider"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["One of ",(0,r.jsx)(n.code,{children:"ipinfo"})," ",(0,r.jsx)(n.em,{children:"(default)"}),", ",(0,r.jsx)(n.code,{children:"freeipapi"}),", ",(0,r.jsx)(n.code,{children:"ipquery"}),", ",(0,r.jsx)(n.code,{children:"ip-api"}),", or ",(0,r.jsx)(n.code,{children:"ipgeolocation"}),". See the table below"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Required for ",(0,r.jsx)(n.code,{children:"ipgeolocation"}),". Optional for ",(0,r.jsx)(n.code,{children:"ipinfo"})," (a ",(0,r.jsx)(n.a,{href:"https://ipinfo.io/signup",children:"free token"})," raises the rate limit from ~1k/day to ~50k/month)"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideLocation"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set to ",(0,r.jsx)(n.code,{children:"true"})," to hide the flag, ISP name and city/region \u2014 only the IP address is shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-5",children:"Example"}),"\n",(0,r.jsx)(n.p,{children:"Default (no options needed)"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: public-ip\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Using ",(0,r.jsx)(n.code,{children:"ip-api"})," via the proxy (gets server IP, because of ",(0,r.jsx)(n.code,{children:"useProxy"}),")"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: public-ip\n useProxy: true\n options:\n provider: ip-api\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Using ",(0,r.jsx)(n.code,{children:"ipgeolocation"})," with a key:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: public-ip\n options:\n provider: ipgeolocation\n apiKey: xxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["Setting ",(0,r.jsx)(n.code,{children:"useProxy: true"})," makes the lookup happen from Dashy's server instead of your browser, so the upstream API returns the public IP of the machine running Dashy, whereas without ",(0,r.jsx)(n.code,{children:"useProxy"})," it will show your public IP."]})}),"\n",(0,r.jsx)(n.h4,{id:"supported-providers",children:"Supported providers"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Provider"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Key"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Proxy"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Notes"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"ipinfo"})," ",(0,r.jsx)(n.em,{children:"(default)"})]}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed / \ud83d\udfe0 Optional"}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsxs)(n.td,{children:["Service from ",(0,r.jsx)(n.a,{href:"https://ipinfo.io/",children:"ipinfo.io"}),". Keyless gives ~1k/day; a ",(0,r.jsx)(n.a,{href:"https://ipinfo.io/signup",children:"free token"})," raises this to ~50k/month. Sometimes blocked by adblockers."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"freeipapi"})}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsxs)(n.td,{children:["Keyless, 60 req/min via ",(0,r.jsx)(n.a,{href:"https://freeipapi.com/",children:"freeipapi.com"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"ipquery"})}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsxs)(n.td,{children:["Modern keyless API from ",(0,r.jsx)(n.a,{href:"https://ipquery.io/",children:"ipquery.io"}),". Includes VPN/Tor/datacenter risk flags in the raw response."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"ip-api"})}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsxs)(n.td,{children:["\ud83d\udd34 ",(0,r.jsx)(n.strong,{children:"Required"})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.a,{href:"https://ip-api.com/",children:"ip-api.com"})," is HTTP-only on the free tier, so the proxy is needed to avoid mixed-content errors."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"ipgeolocation"})}),(0,r.jsxs)(n.td,{children:["\ud83d\udd34 ",(0,r.jsx)(n.strong,{children:"Required"})]}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsxs)(n.td,{children:["Get a free key from ",(0,r.jsx)(n.a,{href:"https://ipgeolocation.io/signup.html",children:"ipgeolocation.io"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"info-5",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled (only ",(0,r.jsx)(n.code,{children:"provider: ip-api"})," needs the proxy)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional (only ",(0,r.jsx)(n.code,{children:"ipgeolocation"})," requires a key, and ",(0,r.jsx)(n.code,{children:"ipinfo"})," is optional)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": See the policy for whichever provider you choose: ",(0,r.jsx)(n.a,{href:"https://freeipapi.com/",children:"freeipapi.com"}),", ",(0,r.jsx)(n.a,{href:"https://ipinfo.io/privacy-policy",children:"ipinfo.io"}),", ",(0,r.jsx)(n.a,{href:"https://ipquery.io/",children:"ipquery.io"}),", ",(0,r.jsx)(n.a,{href:"https://ip-api.com/docs/legal",children:"ip-api.com"}),", ",(0,r.jsx)(n.a,{href:"https://ipgeolocation.io/privacy.html",children:"IPGeoLocation"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"ip-blacklist",children:"IP Blacklist"}),"\n",(0,r.jsxs)(n.p,{children:["Notice certain web pages aren't loading? This widget quickly shows which blacklists your IP address (or host, or email) appears on, using data from ",(0,r.jsx)(n.a,{href:"https://blacklistchecker.com/",children:"blacklistchecker.com"}),"."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/ip-blacklist"})}),"\n",(0,r.jsx)(n.h4,{id:"options-6",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"ipAddress"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"The IP to check. This can also be a domain/ host name or even an email address. If left blank, Dashy will use your current public IP address."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["You can get your free API key from ",(0,r.jsx)(n.a,{href:"https://blacklistchecker.com/keys",children:"blacklistchecker.com"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-6",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: blacklist-check\n options:\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n ipAddress: 1.1.1.1\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-6",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free Plan"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://blacklistchecker.com/privacy",children:"BlacklistChecker Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"domain-monitor",children:"Domain Monitor"}),"\n",(0,r.jsxs)(n.p,{children:["Keep an eye on the expiry dates of your domain names, using public whois records fetched from ",(0,r.jsx)(n.a,{href:"https://whoapi.com/",children:"whoapi.com"}),". Click the domain name to view additional info, like registrar, name servers and date last updated."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/domain-monitor"})}),"\n",(0,r.jsx)(n.h4,{id:"options-7",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"domain"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The domain to check"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["You can get your free API key from ",(0,r.jsx)(n.a,{href:"https://my.whoapi.com/user/signup",children:"my.whoapi.com"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showFullInfo"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If set to true, the toggle-full-info panel will be open by default"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-7",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: domain-monitor\n options:\n domain: example.com\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n - type: domain-monitor\n options:\n domain: example2.com\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-7",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free Plan (10,000 requests)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://whoapi.com/privacy-policy/",children:"WhoAPI Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"crypto-watch-list",children:"Crypto Watch List"}),"\n",(0,r.jsxs)(n.p,{children:["Keep track of price changes of your favorite crypto assets. Data is fetched from ",(0,r.jsx)(n.a,{href:"https://www.coingecko.com/",children:"CoinGecko"}),". All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/crypto-prices"})}),"\n",(0,r.jsx)(n.h4,{id:"options-8",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"assets"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["An array of cryptocurrencies, coins and tokens. See ",(0,r.jsx)(n.a,{href:"https://api.coingecko.com/api/v3/asset_platforms",children:"list of supported assets"}),". If none are specified, then the top coins by ",(0,r.jsx)(n.code,{children:"sortBy"})," (defaults to market cap) will be returned"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"currency"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The fiat currency to display price in, expressed as an ISO-4217 alpha code (see ",(0,r.jsx)(n.a,{href:"https://www.iban.com/currency-codes",children:"list of currencies"}),"). Defaults to ",(0,r.jsx)(n.code,{children:"USD"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"sortBy"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The method of sorting results. Can be ",(0,r.jsx)(n.code,{children:"marketCap"}),", ",(0,r.jsx)(n.code,{children:"volume"})," or ",(0,r.jsx)(n.code,{children:"alphabetical"}),". Defaults to ",(0,r.jsx)(n.code,{children:"marketCap"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Number of results to return, useful when no assets are specified. Defaults to either ",(0,r.jsx)(n.code,{children:"all"})," or ",(0,r.jsx)(n.code,{children:"100"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-8",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: crypto-watch-list\n options:\n limit: 10\n"})}),"\n",(0,r.jsx)(n.p,{children:"Or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: crypto-watch-list\n options:\n currency: GBP\n sortBy: marketCap\n assets:\n - bitcoin\n - ethereum\n - monero\n - cosmos\n - polkadot\n - dogecoin\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-8",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.coingecko.com/en/privacy",children:"CoinGecko Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"crypto-token-price-history",children:"Crypto Token Price History"}),"\n",(0,r.jsxs)(n.p,{children:["Shows recent price history for a given crypto asset, using price data fetched from ",(0,r.jsx)(n.a,{href:"https://www.coingecko.com/",children:"CoinGecko"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/crypto-price-history"})}),"\n",(0,r.jsx)(n.h4,{id:"options-9",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"asset"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Name of a crypto asset, coin or token to fetch price data for, see ",(0,r.jsx)(n.a,{href:"https://api.coingecko.com/api/v3/asset_platforms",children:"list of supported assets"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"currency"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The fiat currency to display results in, expressed as an ISO-4217 alpha code (see ",(0,r.jsx)(n.a,{href:"https://www.iban.com/currency-codes",children:"list of currencies"}),"). Defaults to ",(0,r.jsx)(n.code,{children:"USD"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"numDays"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of days of price history to render. Defaults to ",(0,r.jsx)(n.code,{children:"7"}),", min: ",(0,r.jsx)(n.code,{children:"1"}),", max: ",(0,r.jsx)(n.code,{children:"30"})," days"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColor"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Color of the chart value. Defaults to ",(0,r.jsx)(n.code,{children:"--widget-text-color"})," which inherits dashboard primary color"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The height of rendered chart in px. Defaults to ",(0,r.jsx)(n.code,{children:"300"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-9",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: crypto-price-chart\n options:\n asset: bitcoin\n currency: GBP\n numDays: 7\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-9",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.coingecko.com/en/privacy",children:"CoinGecko Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"wallet-balance",children:"Wallet Balance"}),"\n",(0,r.jsxs)(n.p,{children:["Keep track of your crypto balances and see recent transactions. Data is fetched from ",(0,r.jsx)(n.a,{href:"https://www.blockcypher.com/dev/",children:"BlockCypher"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/crypto-wallet"})}),"\n",(0,r.jsx)(n.h4,{id:"options-10",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"coin"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Symbol of coin or asset, e.g. ",(0,r.jsx)(n.code,{children:"btc"}),", ",(0,r.jsx)(n.code,{children:"eth"})," or ",(0,r.jsx)(n.code,{children:"doge"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"address"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Address to monitor. This is your wallet's ",(0,r.jsx)(n.strong,{children:"public"})," / receiving address"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"network"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["To use a different network, other than mainnet. Defaults to ",(0,r.jsx)(n.code,{children:"main"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit the number of transactions to display. Defaults to ",(0,r.jsx)(n.code,{children:"10"}),", set to large number to show all"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-10",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: wallet-balance\n options:\n coin: btc\n address: 3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-10",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.blockcypher.com/privacy.html",children:"BlockCypher Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"code-stats",children:"Code Stats"}),"\n",(0,r.jsxs)(n.p,{children:["Display your coding summary. ",(0,r.jsx)(n.a,{href:"https://codestats.net/",children:"Code::Stats"})," is a free and open source app that aggregates statistics about your programming activity. Dashy supports both the public instance, as well as self-hosted versions."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/code-stats"})}),"\n",(0,r.jsx)(n.h4,{id:"options-11",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Your CodeStats username"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If your self-hosting CodeStats, then supply the host name. By default it will use the public hosted instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"monthsToShow"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify the number of months to render in the historical data chart. Defaults to ",(0,r.jsx)(n.code,{children:"6"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideMeta"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide the meta section (username, level, all-time and recent XP)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideHistory"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide the historical calendar heat map"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideLanguages"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide the programming languages pie chart"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideMachines"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide the machines percentage chart"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-11",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: code-stats\n options:\n username: alicia\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-11",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted or Managed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://codestats.net/tos#privacy",children:"Code::Stats Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"mullvad-status",children:"Mullvad Status"}),"\n",(0,r.jsxs)(n.p,{children:["Shows your Mullvad VPN connection status, as well as server info. Fetched from ",(0,r.jsx)(n.a,{href:"https://mullvad.net/en/check/",children:"am.i.mullvad.net"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/mullvad"})}),"\n",(0,r.jsx)(n.h4,{id:"options-12",children:"Options"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"No Options."})}),"\n",(0,r.jsx)(n.h4,{id:"example-12",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: mullvad-status\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-12",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://mullvad.net/en/help/privacy-policy/",children:"Mullvad Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"addyio",children:"addy.io"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://addy.io/",children:"addy.io"})," is a free and open source mail forwarding service. Use it to protect your real email address, by using a different alias for each of your online accounts, and have all emails land in your normal inbox(es). Supports custom domains, email replies, PGP-encryption, multiple recipients and more"]}),"\n",(0,r.jsx)(n.p,{children:"This widget display email addresses / aliases from addy.io. Click an email address to copy to clipboard, or use the toggle switch to enable/ disable it. Shows usage stats (bandwidth, used aliases etc), as well as total messages received, blocked and sent. Works with both self-hosted and managed instances of addy.io."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/addy"})}),"\n",(0,r.jsx)(n.h4,{id:"options-13",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your addy.io API Key / Personal Access Token. You can generate this under ",(0,r.jsx)(n.a,{href:"https://app.addy.io/settings/api",children:"API Settings"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If your self-hosting addy.io, then supply the host name. By default it will use the public hosted instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiVersion"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If you're using an API version that is not version ",(0,r.jsx)(n.code,{children:"v1"}),", then specify it here"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit the number of emails shown per page. Defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"sortBy"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify the sort order for email addresses. Defaults to ",(0,r.jsx)(n.code,{children:"updated_at"}),". Can be either: ",(0,r.jsx)(n.code,{children:"local_part"}),", ",(0,r.jsx)(n.code,{children:"domain"}),", ",(0,r.jsx)(n.code,{children:"email"}),", ",(0,r.jsx)(n.code,{children:"emails_forwarded"}),", ",(0,r.jsx)(n.code,{children:"emails_blocked"}),", ",(0,r.jsx)(n.code,{children:"emails_replied"}),", ",(0,r.jsx)(n.code,{children:"emails_sent"}),", ",(0,r.jsx)(n.code,{children:"created_at"}),", ",(0,r.jsx)(n.code,{children:"updated_at"})," or ",(0,r.jsx)(n.code,{children:"deleted_at"}),". Precede with a ",(0,r.jsx)(n.code,{children:"-"})," character to reverse order."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"searchTerm"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"A search term to filter results by, will search the email, description and domain"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"disableControls"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Prevent any changes being made to account through the widget. User will not be able to enable or disable aliases through UI when this option is set"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideMeta"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Don't show account meta info (forward/ block count, quota usage etc)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideAliases"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Don't show email address / alias list. Will only show account meta info"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-13",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:' - type: addy\n options:\n apiKey: "xxxxxxxxxxxxxxxxxxxxxxxx\\\n xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\\\n xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"\n limit: 5\n sortBy: created_at\n disableControls: true\n'})}),"\n",(0,r.jsx)(n.h4,{id:"info-13",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free for Self-Hosted / Free Plan available on managed instance or $1/month for premium"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted or Managed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://addy.io/privacy/",children:"addy.io Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"vulnerability-feed",children:"Vulnerability Feed"}),"\n",(0,r.jsxs)(n.p,{children:["Keep track of recent security advisories and vulnerabilities, with optional filtering by score, exploits, vendor and product. Using data from ",(0,r.jsx)(n.a,{href:"https://nvd.nist.gov/developers/vulnerabilities",children:"NIST Vulnerability API"}),". All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/cve"})}),"\n",(0,r.jsx)(n.h4,{id:"options-14",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cveTag"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["This parameter returns only the CVE records that include the provided cveTag. Options are ",(0,r.jsx)(n.strong,{children:"disputed"}),", ",(0,r.jsx)(n.strong,{children:"unsupported-when-assigned"})," or ",(0,r.jsx)(n.strong,{children:"exclusively-hosted-service"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of results to fetch. Can be between ",(0,r.jsx)(n.code,{children:"5"})," and ",(0,r.jsx)(n.code,{children:"30"}),", defaults to ",(0,r.jsx)(n.code,{children:"5"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cvssV2Severity"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["This parameter returns only the CVEs that match the provided CVSSv2 qualitative severity rating. Options are ",(0,r.jsx)(n.strong,{children:"LOW"}),", ",(0,r.jsx)(n.strong,{children:"MEDIUM"}),", ",(0,r.jsx)(n.strong,{children:"HIGH"})," or ",(0,r.jsx)(n.strong,{children:"CRITICAL"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cvssV3Severity"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["This parameter returns only the CVEs that match the provided CVSSv3 qualitative severity rating. Options are ",(0,r.jsx)(n.strong,{children:"LOW"}),", ",(0,r.jsx)(n.strong,{children:"MEDIUM"}),", ",(0,r.jsx)(n.strong,{children:"HIGH"})," or ",(0,r.jsx)(n.strong,{children:"CRITICAL"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cvssV4Severity"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["This parameter returns only the CVEs that match the provided CVSSv4 qualitative severity rating. Options are ",(0,r.jsx)(n.strong,{children:"LOW"}),", ",(0,r.jsx)(n.strong,{children:"MEDIUM"}),", ",(0,r.jsx)(n.strong,{children:"HIGH"})," or ",(0,r.jsx)(n.strong,{children:"CRITICAL"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"keywordSearch"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"This parameter returns only the CVEs where a word or phrase is found in the current description"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Without a key you're limited to 5 requests every 30 seconds. You can get a free API key from ",(0,r.jsx)(n.a,{href:"https://nvd.nist.gov/developers/request-an-api-key",children:"here"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-14",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: cve-vulnerabilities\n"})}),"\n",(0,r.jsx)(n.p,{children:"or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: cve-vulnerabilities\n options:\n cveTag: disputed\n cvssV2Severity: CRITICAL\n limit: 30\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-14",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required (free apiKey recommended for multiple widgets)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.cvedetails.com/privacy.php",children:"CVE Details Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"exchange-rates",children:"Exchange Rates"}),"\n",(0,r.jsx)(n.p,{children:"Display current FX rates in your native currency. Hover over a row to view more info, or click to show rates in that currency."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/currencies"})}),"\n",(0,r.jsx)(n.h4,{id:"options-15",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"inputCurrency"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The base currency to show results in. Specified as a 3-letter ISO-4217 code, see ",(0,r.jsx)(n.a,{href:"https://www.exchangerate-api.com/docs/supported-currencies",children:"here"})," for the full list of supported currencies, and their symbols"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"outputCurrencies"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["List or currencies to show results for. Specified as a 3-letter ISO-4217 code, see ",(0,r.jsx)(n.a,{href:"https://www.exchangerate-api.com/docs/supported-currencies",children:"here"})," for the full list of supported currencies, and their symbols"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["API key for ",(0,r.jsx)(n.a,{href:"https://www.exchangerate-api.com/",children:"exchangerate-api.com"}),", usually a 24-digit alpha-numeric string. You can sign up for a free account ",(0,r.jsx)(n.a,{href:"https://app.exchangerate-api.com/sign-up",children:"here"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-15",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: exchange-rates\n options:\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxx\n inputCurrency: GBP\n outputCurrencies:\n - USD\n - JPY\n - HKD\n - KPW\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-15",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan (up to 100,000 requests/ month)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.exchangerate-api.com/terms",children:"ExchangeRateAPI Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"public-holidays",children:"Public Holidays"}),"\n",(0,r.jsxs)(n.p,{children:["Counting down to the next day off work? This widget displays upcoming public holidays for your country. Data is fetched from ",(0,r.jsx)(n.a,{href:"http://kayaposoft.com/enrico/",children:"Enrico"})]}),"\n",(0,r.jsxs)(n.p,{children:["Note, config for this widget is case-sensitive (see ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1268",children:"#1268"}),")"]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/holidays"})}),"\n",(0,r.jsx)(n.h4,{id:"options-16",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"country"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The country to fetch holiday data for, specified as a country code, e.g. ",(0,r.jsx)(n.code,{children:"GB"})," or ",(0,r.jsx)(n.code,{children:"US"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"state"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["restrict a country to a specific state defined by ",(0,r.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/ISO_3166-2",children:"ISO_3166-2"}),", e.g. ",(0,r.jsx)(n.code,{children:"LND"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"holidayType"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The type of holidays to fetch. Can be: ",(0,r.jsx)(n.code,{children:"all"}),", ",(0,r.jsx)(n.code,{children:"public_holiday"}),", ",(0,r.jsx)(n.code,{children:"observance"}),", ",(0,r.jsx)(n.code,{children:"school_holiday"}),", ",(0,r.jsx)(n.code,{children:"other_day"})," or ",(0,r.jsx)(n.code,{children:"extra_working_day"}),". Defaults to ",(0,r.jsx)(n.code,{children:"public_holiday"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"monthsToShow"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of months in advance to show. Min: ",(0,r.jsx)(n.code,{children:"1"}),", max: ",(0,r.jsx)(n.code,{children:"24"}),". Defaults to ",(0,r.jsx)(n.code,{children:"12"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lang"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The language in which the events should be. Usually local languages and english are available. Default to first available in the country. e.g. ",(0,r.jsx)(n.code,{children:"en"})," or ",(0,r.jsx)(n.code,{children:"fr"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-16",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: public-holidays\n options:\n country: GB\n state: LND\n holidayType: all\n monthsToShow: 12\n lang: en\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-16",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/jurajmajer/enrico",children:"jurajmajer/enrico"}),") or Managed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"covid-19-status",children:"Covid-19 Status"}),"\n",(0,r.jsxs)(n.p,{children:["Keep track of the current COVID-19 status. Optionally also show cases by country, and a time-series chart. Uses live data from various sources, computed by ",(0,r.jsx)(n.a,{href:"https://disease.sh/",children:"disease.sh"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/covid"})}),"\n",(0,r.jsx)(n.h4,{id:"options-17",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showChart"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Also display a time-series chart showing number of recent cases"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showCountries"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Also display a list of cases per country"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"numDays"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Specify number of days worth of history to render on the chart"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"countries"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string[]"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["An array of countries to display, specified by their ",(0,r.jsx)(n.a,{href:"https://www.iso.org/obp/ui",children:"ISO-3 codes"}),". Leave blank to show all, sorted by most cases. ",(0,r.jsx)(n.code,{children:"showCountries"})," must be set to ",(0,r.jsx)(n.code,{children:"true"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If showing all countries, set a limit for number of results to return. Defaults to ",(0,r.jsx)(n.code,{children:"10"}),", no maximum"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-17",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: covid-stats\n"})}),"\n",(0,r.jsx)(n.p,{children:"Or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: covid-stats\n options:\n showChart: true\n showCountries: true\n countries:\n - GBR\n - USA\n - IND\n - RUS\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-17",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/disease-sh/api",children:"disease-sh/api"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Conditions"}),": ",(0,r.jsx)(n.a,{href:"https://github.com/disease-sh/api/blob/master/TERMS",children:"Terms of Use"})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"sports-scores",children:"Sports Scores"}),"\n",(0,r.jsxs)(n.p,{children:["Show recent scores and upcoming matches from your favorite sports team. Data is fetched from ",(0,r.jsx)(n.a,{href:"https://www.thesportsdb.com/",children:"TheSportsDB.com"}),". From the UI, you can click any other team to view their scores and upcoming games, or click a league name to see all teams."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/sports"})}),"\n",(0,r.jsx)(n.h4,{id:"options-18",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"teamId"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The ID of a team to fetch scores from. You can search for your team on the ",(0,r.jsx)(n.a,{href:"https://www.thesportsdb.com/teams_main.php",children:"Teams Page"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"leagueId"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Alternatively, provide a league ID to fetch all games from. You can find the ID on the ",(0,r.jsx)(n.a,{href:"https://www.thesportsdb.com/Sport/Leagues",children:"Leagues Page"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"pastOrFuture"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set to ",(0,r.jsx)(n.code,{children:"past"})," to show scores for recent games, or ",(0,r.jsx)(n.code,{children:"future"})," to show upcoming games. Defaults to ",(0,r.jsx)(n.code,{children:"past"}),". You can change this within the UI"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Optionally specify your API key, which you can sign up for at ",(0,r.jsx)(n.a,{href:"https://www.thesportsdb.com/",children:"TheSportsDB.com"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["To limit output to a certain number of matches, defaults to ",(0,r.jsx)(n.code,{children:"15"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideImage"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set to ",(0,r.jsx)(n.code,{children:"true"})," to not render the team / match banner image, defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-18",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: sports-scores\n options:\n teamId: 133636\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-18",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan (up to 30 requests / minute, limited endpoints)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"news-headlines",children:"News Headlines"}),"\n",(0,r.jsxs)(n.p,{children:["Displays the latest news, click to read full article. Date is fetched from various news sources using ",(0,r.jsx)(n.a,{href:"https://currentsapi.services/en",children:"Currents API"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://pixelflare.cc/alicia/dashy/news"})}),"\n",(0,r.jsx)(n.h4,{id:"options-19",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your API key for CurrentsAPI. This is free, and you can ",(0,r.jsx)(n.a,{href:"https://currentsapi.services/en/register",children:"get one here"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"country"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Fetch news only from a certain country or region. Specified as a country code, e.g. ",(0,r.jsx)(n.code,{children:"GB"})," or ",(0,r.jsx)(n.code,{children:"US"}),". See ",(0,r.jsx)(n.a,{href:"https://api.currentsapi.services/v1/available/regions",children:"here"})," for a list of supported regions"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"category"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Only return news from within a given category, e.g. ",(0,r.jsx)(n.code,{children:"sports"}),", ",(0,r.jsx)(n.code,{children:"programming"}),", ",(0,r.jsx)(n.code,{children:"world"}),", ",(0,r.jsx)(n.code,{children:"science"}),". The ",(0,r.jsx)(n.a,{href:"https://api.currentsapi.services/v1/available/categories",children:"following categories"})," are supported"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lang"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify the language for returned articles as a 2-digit ISO code (limited article support). The ",(0,r.jsx)(n.a,{href:"https://api.currentsapi.services/v1/available/languages",children:"following languages"})," are supported, defaults to ",(0,r.jsx)(n.code,{children:"en"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"count"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit the number of results. Can be between ",(0,r.jsx)(n.code,{children:"1"})," and ",(0,r.jsx)(n.code,{children:"200"}),", defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"keywords"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Only return articles that contain an exact match within their title or description"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideImages"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", then article image thumbnails will not be displayed"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-19",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: news-headlines\n options:\n apiKey: xxxxxxx\n category: world\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-19",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan (up to 600 requests / day)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://currentsapi.services/privacy",children:"CurrentsAPI Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"tfl-status",children:"TFL Status"}),"\n",(0,r.jsx)(n.p,{children:"Shows real-time tube status of the London Underground. All fields are optional."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/tfl"})}),"\n",(0,r.jsx)(n.h4,{id:"options-20",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showAll"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["By default, details for lines with a Good Service are not visible, but you can click More Details to see all. Setting this option to ",(0,r.jsx)(n.code,{children:"true"})," will show all lines on initial page load"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"sortAlphabetically"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["By default lines are sorted by current status, set this option to ",(0,r.jsx)(n.code,{children:"true"})," to instead sort them alphabetically"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"linesToShow"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"By default all lines are shown. If you're only interested in the status of a few lines, then pass in an array of lines to show, specified by name"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-20",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: tfl-status\n"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: tfl-status\n options:\n showAll: true\n sortAlphabetically: true\n linesToShow:\n - District\n - Jubilee\n - Central\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-20",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://tfl.gov.uk/corporate/privacy-and-cookies/",children:"TFL Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"stock-price-history",children:"Stock Price History"}),"\n",(0,r.jsx)(n.p,{children:"Shows recent price history for a given publicly-traded stock or share"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/stocks"})}),"\n",(0,r.jsx)(n.h4,{id:"options-21",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["API key for ",(0,r.jsx)(n.a,{href:"https://www.alphavantage.co/",children:"Alpha Vantage"}),", you can get a free API key ",(0,r.jsx)(n.a,{href:"https://www.alphavantage.co/support/#api-key",children:"here"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"stock"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The stock symbol for the asset to fetch data for"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"priceTime"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The time to fetch price for. Can be ",(0,r.jsx)(n.code,{children:"high"}),", ",(0,r.jsx)(n.code,{children:"low"}),", ",(0,r.jsx)(n.code,{children:"open"})," or ",(0,r.jsx)(n.code,{children:"close"}),". Defaults to ",(0,r.jsx)(n.code,{children:"high"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColor"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Color of the chart value. Defaults to ",(0,r.jsx)(n.code,{children:"--widget-text-color"})," which inherits dashboard primary color"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The height of rendered chart in px. Defaults to ",(0,r.jsx)(n.code,{children:"300"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-21",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: stock-price-chart\n options:\n stock: NET\n apiKey: PGUWSWD6CZTXMT8N\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-21",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan (up to 500 requests/day)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.alphavantage.co/privacy/",children:"AlphaVantage Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"eth-gas-prices",children:"ETH Gas Prices"}),"\n",(0,r.jsxs)(n.p,{children:["Renders the current Gas cost of transactions on the Ethereum network (in both GWEI and USD), along with recent historical prices. Useful for spotting a good time to transact. Uses data from ",(0,r.jsx)(n.a,{href:"https://ethgas.watch/",children:"ethgas.watch"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/eth-gas"})}),"\n",(0,r.jsx)(n.h4,{id:"options-22",children:"Options"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"No config options."})}),"\n",(0,r.jsx)(n.h4,{id:"example-22",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: eth-gas-prices\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-22",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/wslyvh/ethgaswatch",children:"wslyvh/ethgaswatch"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"joke",children:"Joke"}),"\n",(0,r.jsxs)(n.p,{children:["Renders a programming or generic joke. Data is fetched from the ",(0,r.jsx)(n.a,{href:"https://github.com/Sv443/JokeAPI",children:"JokesAPI"})," by @Sv443. All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/joke"})}),"\n",(0,r.jsx)(n.h4,{id:"options-23",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"category"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set the category of jokes to return. Use a string to specify a single category, or an array to pass in multiple options. Available options are: ",(0,r.jsx)(n.code,{children:"all"}),", ",(0,r.jsx)(n.code,{children:"programming"}),", ",(0,r.jsx)(n.code,{children:"pun"}),", ",(0,r.jsx)(n.code,{children:"dark"}),", ",(0,r.jsx)(n.code,{children:"spooky"}),", ",(0,r.jsx)(n.code,{children:"christmas"})," and ",(0,r.jsx)(n.code,{children:"misc"}),". An up-to-date list of supported categories can be found ",(0,r.jsx)(n.a,{href:"https://v2.jokeapi.dev/categories",children:"here"}),". Defaults to ",(0,r.jsx)(n.code,{children:"all"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"safeMode"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set to ",(0,r.jsx)(n.code,{children:"true"}),", to prevent the fetching of any NSFW jokes. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"language"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify the language for returned jokes. The following languages are supported: ",(0,r.jsx)(n.code,{children:"en"}),", ",(0,r.jsx)(n.code,{children:"cs"}),", ",(0,r.jsx)(n.code,{children:"de"}),", ",(0,r.jsx)(n.code,{children:"es"}),", ",(0,r.jsx)(n.code,{children:"fr"})," and ",(0,r.jsx)(n.code,{children:"pt"}),", and an up-to-date list of supported languages can be found ",(0,r.jsx)(n.a,{href:"https://v2.jokeapi.dev/languages",children:"here"}),". By default, your system language will be used, if it's supported, otherwise English"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-23",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: joke\n options:\n safeMode: true\n language: en\n category: Programming\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-23",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/Sv443/JokeAPI",children:"Sv443/JokeAPI"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://sv443.net/privacypolicy/en",children:"SV443's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"chuck-norris-quotes",children:"Chuck Norris quotes"}),"\n",(0,r.jsxs)(n.p,{children:["Renders a Chuck Norris quote. Data is fetched from the ",(0,r.jsx)(n.a,{href:"https://api.chucknorris.io/",children:"ChuckNorrisAPI"})," by @matchilling. All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://tbd"})}),"\n",(0,r.jsx)(n.h4,{id:"options-24",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"categories"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set the category of jokes to return. Use a string to specify a single category, or an array to pass in multiple options. Available options are: ",(0,r.jsx)(n.code,{children:"animal"}),",",(0,r.jsx)(n.code,{children:"career"}),",",(0,r.jsx)(n.code,{children:"celebrity"}),",",(0,r.jsx)(n.code,{children:"dev"}),",",(0,r.jsx)(n.code,{children:"explicit"}),",",(0,r.jsx)(n.code,{children:"fashion"}),",",(0,r.jsx)(n.code,{children:"food"}),",",(0,r.jsx)(n.code,{children:"history"}),",",(0,r.jsx)(n.code,{children:"money"}),",",(0,r.jsx)(n.code,{children:"movie"}),",",(0,r.jsx)(n.code,{children:"music"}),",",(0,r.jsx)(n.code,{children:"political"}),",",(0,r.jsx)(n.code,{children:"religion"}),",",(0,r.jsx)(n.code,{children:"science"}),",",(0,r.jsx)(n.code,{children:"sport"})," and ",(0,r.jsx)(n.code,{children:"travel"}),". An up-to-date list of supported categories can be found ",(0,r.jsx)(n.a,{href:"https://api.chucknorris.io/jokes/categories",children:"here"}),". Defaults to not explicitely set and therefore any of the categories can come up."]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-24",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: chucknorris\n options:\n categories: history,sport\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-24",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://api.chucknorris.io/privacy",children:"matchilling's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"xkcd-comics",children:"XKCD Comics"}),"\n",(0,r.jsxs)(n.p,{children:["Have a laugh with the daily comic from ",(0,r.jsx)(n.a,{href:"https://xkcd.com/",children:"XKCD"}),". A classic webcomic website covering everything from Linux, math, romance, science and language. All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/xkcd"})}),"\n",(0,r.jsx)(n.h4,{id:"options-25",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"comic"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string / number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Choose which comic to display. Set to either ",(0,r.jsx)(n.code,{children:"random"}),", ",(0,r.jsx)(n.code,{children:"latest"})," or the series number of a specific comic, like ",(0,r.jsx)(n.code,{children:"627"}),". Defaults to ",(0,r.jsx)(n.code,{children:"latest"})]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-25",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: xkcd-comic\n options:\n comic: latest\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-25",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"flight-data",children:"Flight Data"}),"\n",(0,r.jsxs)(n.p,{children:["Displays airport departure and arrival flights, using data from ",(0,r.jsx)(n.a,{href:"https://www.aerodatabox.com/",children:"AeroDataBox"}),". Useful if you live near an airport and often wonder where the flight overhead is going to. Hover over a row for more flight data."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/flights"})}),"\n",(0,r.jsx)(n.h4,{id:"options-26",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"airport"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The airport to show flight data from. Should be specified as a 4-character ICAO-code, a full list of which can be found ",(0,r.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/ICAO_airport_code",children:"here"})," (example: ",(0,r.jsx)(n.code,{children:"KBJC"})," or ",(0,r.jsx)(n.code,{children:"EGKK"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["A valid ",(0,r.jsx)(n.a,{href:"https://rapidapi.com/",children:"RapidAPI"})," Key, with ",(0,r.jsx)(n.a,{href:"https://rapidapi.com/aerodatabox/api/aerodatabox/",children:"AeroDataBox"})," enabled (check in your ",(0,r.jsx)(n.a,{href:"https://rapidapi.com/developer/billing/subscriptions-and-usage",children:"Subscription Dashboard"}),"). This API is free to sign up for and use"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"For busy airports, you may wish to limit the number of results visible"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"direction"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["By default, both departure and arrival flights will be fetched, if you would like to only show flights in one direction, set this to wither ",(0,r.jsx)(n.code,{children:"departure"})," or ",(0,r.jsx)(n.code,{children:"arrival"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-26",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: flight-data\n options:\n airport: EGLC\n apiKey: XXXXX\n limit: 12\n direction: all\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-26",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan (up to 150 requests / month)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.aerodatabox.com/#h.p_cxtiyzwf_wqd",children:"AeroDataBox"})," and ",(0,r.jsx)(n.a,{href:"https://rapidapi.com/privacy/",children:"RapidAPI Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"astronomy-picture-of-the-day",children:"Astronomy Picture of the Day"}),"\n",(0,r.jsxs)(n.p,{children:["Show the NASA Astronomy Picture of the Day. Data is fetched from ",(0,r.jsx)(n.a,{href:"https://apod.nasa.gov/apod/",children:"APOD"})," using ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/go-apod",children:"@Lissy93/go-apod"})," / hosted at ",(0,r.jsx)(n.a,{href:"https://apod.as93.net/",children:"apod.as93.net"}),"."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://storage.googleapis.com/as93-screenshots/dashy/apod.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-27",children:"Options"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"No config options."})}),"\n",(0,r.jsx)(n.h4,{id:"example-27",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: apod\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-27",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/go-apod",children:"@Lissy93/go-apod"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.nasa.gov/about/highlights/HP_Privacy.html",children:"NASA's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"github-trending",children:"GitHub Trending"}),"\n",(0,r.jsxs)(n.p,{children:["Displays currently trending projects on GitHub. Optionally specify a language and time-frame. Data is fetched from ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/gh-trending-no-cors",children:"Lissy93/gh-trending-no-cors"})," using the GitHub API. All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://pixelflare.cc/alicia/dashy/github-trending"})}),"\n",(0,r.jsx)(n.h4,{id:"options-28",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lang"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["A programming language to fetch trending repos from that category. E.g. ",(0,r.jsx)(n.code,{children:"javascript"})," or ",(0,r.jsx)(n.code,{children:"go"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"since"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The timeframe to use when calculating trends. Can be either ",(0,r.jsx)(n.code,{children:"daily"}),", ",(0,r.jsx)(n.code,{children:"weekly"})," or ",(0,r.jsx)(n.code,{children:"monthly"}),". Defaults to ",(0,r.jsx)(n.code,{children:"daily"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Optionally limit the number of results. Max ",(0,r.jsx)(n.code,{children:"25"}),", default is ",(0,r.jsx)(n.code,{children:"10"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-28",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: github-trending-repos\n options:\n limit: 8\n since: weekly\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-28",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/gh-trending-no-cors",children:"Lissy93/gh-trending-no-cors"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://docs.github.com/en/github/site-policy/github-privacy-statement",children:"GitHub's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"github-profile-stats",children:"GitHub Profile Stats"}),"\n",(0,r.jsxs)(n.p,{children:["Display stats from your GitHub profile, using embedded cards from ",(0,r.jsx)(n.a,{href:"https://github.com/anuraghazra/github-readme-stats",children:"anuraghazra/github-readme-stats"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://pixelflare.cc/alicia/dashy/github-stats"})}),"\n",(0,r.jsx)(n.h4,{id:"options-29",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The GitHub username to fetch info for. E.g. ",(0,r.jsx)(n.code,{children:"lissy93"}),". (Not required if ",(0,r.jsx)(n.code,{children:"hideProfileCard"})," and ",(0,r.jsx)(n.code,{children:"hideLanguagesCard"})," are both set to ",(0,r.jsx)(n.code,{children:"true"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideProfileCard"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", the users profile card will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideLanguagesCard"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", the users top languages card will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"repos"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If you'd like to also display stats for some GitHub repositories, then add an array or repo names here. Specified as ",(0,r.jsx)(n.code,{children:"[username]/[repo-name]"}),", e.g. ",(0,r.jsx)(n.code,{children:"lissy93/dashy"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-29",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: github-profile-stats\n options:\n username: Lissy93\n hideLanguagesCard: true\n repos:\n - lissy93/dashy\n - lissy93/personal-security-checklist\n - lissy93/twitter-sentiment-visualisation\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-29",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/anuraghazra/github-readme-stats",children:"anuraghazra/github-readme-stats"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://docs.github.com/en/github/site-policy/github-privacy-statement",children:"GitHub's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"healthchecks-status",children:"HealthChecks Status"}),"\n",(0,r.jsx)(n.p,{children:"Display cron job monitor status of one or more HealthChecks project(s). Works with healthchecks.io and your selfhosted instance."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/cron-monitor"})}),"\n",(0,r.jsx)(n.h4,{id:"options-30",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Optional"}),(0,r.jsxs)(n.td,{children:["The base url of your instance, default is ",(0,r.jsx)(n.code,{children:"https://healthchecks.io"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"string"})," (or ",(0,r.jsx)(n.code,{children:"string[]"})," for multiple)"]}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"API key(s) for project(s) to monitor. Generate a separate key per project (read-only is fine), under Project --\x3e Settings --\x3e API Access"})]})]})]}),"\n",(0,r.jsx)(n.h5,{id:"example-30",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: health-checks\n options:\n host: https://healthcheck.your-domain.de\n apiKey: \n - abcdefg...\n - zxywvu...\n"})}),"\n",(0,r.jsx)(n.p,{children:"Or, a single project:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: health-checks\n options:\n host: https://healthchecks.io\n apiKey: hcw_xxxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-30",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free / Paid / Self-hosted"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/healthchecks/healthchecks",children:"healthchecks/healthchecks"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://healthchecks.io/privacy/",children:"Healthchecks.io Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"hackernews-trending",children:"Hackernews Trending"}),"\n",(0,r.jsx)(n.p,{children:"Display new and trending Posts from Hackernews"}),"\n",(0,r.jsx)(n.h4,{id:"options-31",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"stories"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["HN Stories to display defaults to ",(0,r.jsx)(n.code,{children:"topstories"}),". Options are: ",(0,r.jsx)(n.code,{children:"beststories"}),", ",(0,r.jsx)(n.code,{children:"topstories"})," or ",(0,r.jsx)(n.code,{children:"newstories"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"int"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"The size of the list of Posts to show."})]})]})]}),"\n",(0,r.jsx)(n.h5,{id:"example-31",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: hackernews-trending\n options:\n stories: newstories\n limit: 10\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"mvg-departure",children:"MVG Departure"}),"\n",(0,r.jsx)(n.p,{children:"Display departure time of a MVG (M\xfcnchner Verkehrs Gesellschaft) station."}),"\n",(0,r.jsxs)(n.p,{children:["From ",(0,r.jsx)(n.a,{href:"https://www.mvg.de/impressum.html",children:"https://www.mvg.de/impressum.html"}),":"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:"[...] Die Verarbeitung unserer Inhalte oder Daten durch Dritte erfordert unsere ausdr\xfcckliche Zustimmung. F\xfcr private, nicht-kommerzielle Zwecke, wird eine gem\xe4\xdfigte Nutzung ohne unsere ausdr\xfcckliche Zustimmung geduldet. Jegliche Form von Data-Mining stellt keine gem\xe4\xdfigte Nutzung dar.[...]"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"In other words: Private, noncomercial, moderate use of the API is tolerated. They don\u2019t consider data mining as moderate use. (This is not a legal advice)"}),"\n",(0,r.jsx)(n.h4,{id:"options-32",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"location"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The name of the location (exact) or the location id, startin with ",(0,r.jsx)(n.code,{children:"de:09162:"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"integer"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Limit number of entries, defaults to 10."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"A custom title to be displayed."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"header"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"bool"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Shall the title be shown?"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.line"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for given line(s)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.product"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for specific product (TRAM, UBAHN, SBAHN, BUS)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.destination"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for specific destination(s)"})]})]})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: mvg\n options:\n location: Marienplatz\n limit: 5\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-31",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free / Private use only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": ",(0,r.jsx)(n.a,{href:"https://mvg.de",children:"MVG"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.mvg.de/datenschutz-mvg.html",children:"MVG Datenschutz"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"mvg-connection",children:"MVG Connection"}),"\n",(0,r.jsx)(n.p,{children:"Display the next connection for two addresses/coordinates, stations or POI within Munich using MVG MVG (M\xfcnchner Verkehrs Gesellschaft)."}),"\n",(0,r.jsxs)(n.p,{children:["From ",(0,r.jsx)(n.a,{href:"https://www.mvg.de/impressum.html",children:"https://www.mvg.de/impressum.html"}),":"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:"[...] Die Verarbeitung unserer Inhalte oder Daten durch Dritte erfordert unsere ausdr\xfcckliche Zustimmung. F\xfcr private, nicht-kommerzielle Zwecke, wird eine gem\xe4\xdfigte Nutzung ohne unsere ausdr\xfcckliche Zustimmung geduldet. Jegliche Form von Data-Mining stellt keine gem\xe4\xdfigte Nutzung dar.[...]"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"In other words: Private, noncomercial, moderate use of the API is tolerated. They don\u2019t consider data mining as moderate use. (This is not a legal advice)"}),"\n",(0,r.jsx)(n.h4,{id:"options-33",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"origin"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Origin of the connection."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"destination"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Destination of the connection."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"A custom title to be displayed."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"header"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"bool"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Shall the title be shown?"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.line"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for given line(s)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.product"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for specific product (TRAM, UBAHN, SBAHN, BUS)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.destination"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for specific destination(s)"})]})]})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: mvg-connection\n options:\n from: Marienplatz\n from: Dachauer Stra\xdfe 123\n header: true\n filters:\n product: [UBAHN]\n line: [U1,U2,U4,U5]\n\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-32",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free / Private use only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": ",(0,r.jsx)(n.a,{href:"https://mvg.de",children:"MVG"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.mvg.de/datenschutz-mvg.html",children:"MVG Datenschutz"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"custom-list",children:"Custom List"}),"\n",(0,r.jsx)(n.p,{children:"Renders custom schema-compliant JOSN in a list."}),"\n",(0,r.jsx)(n.h4,{id:"options-34",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"text"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A string containing the url of a json file."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"text"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"A title for the widget. Can be helpful if stacking multiple lists in the same section."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"daysForNew"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:"Optional"}),(0,r.jsx)(n.td,{children:"Used to highlight new items."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"json-schema",children:"Json Schema"}),"\n",(0,r.jsx)(n.p,{children:"The input file should comply with the following schema:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n "$schema": "http://json-schema.org/draft-04/schema#",\n "type": "array",\n "items": [\n {\n "type": "object",\n "properties": {\n "link": {\n "type": "object",\n "properties": {\n "text": {\n "type": "string"\n },\n "url": {\n "type": "string"\n },\n "title": {\n "type": "string"\n }\n },\n "required": [\n "text",\n "url",\n "title"\n ]\n },\n "value": {\n "type": "object",\n "properties": {\n "text": {\n "type": "string"\n },\n "title": {\n "type": "string"\n }\n },\n "required": [\n "text",\n "title"\n ]\n },\n "date": {\n "type": "string"\n }\n },\n "required": [\n "link",\n "value",\n "date"\n ]\n }\n ]\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"Example: This json data was generated by a data worflow that gets the new releases of a few projects from GitHub. The system used to build the data workflow is n8n."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'[\n {\n "link": {\n "text": "jellyfin/jellyfin",\n "url": "https://github.com/jellyfin/jellyfin/releases/tag/v10.10.7",\n "title": ""\n },\n "value": {\n "text": "v10.10.7",\n "title": "2025-04-05"\n },\n "date": "2025-04-05T19:14:59Z"\n },\n {\n "link": {\n "text": "jellyfin/jellyfin-web",\n "url": "https://github.com/jellyfin/jellyfin-web/releases/tag/v10.10.7",\n "title": ""\n },\n "value": {\n "text": "v10.10.7",\n "title": "2025-04-05"\n },\n "date": "2025-04-05T19:15:00Z"\n },\n {\n "link": {\n "text": "lissy93/dashy",\n "url": "https://github.com/Lissy93/dashy/releases/tag/3.1.1",\n "title": ""\n },\n "value": {\n "text": "3.1.1",\n "title": "2024-05-30"\n },\n "date": "2024-05-30T17:20:53Z"\n },\n {\n "link": {\n "text": "VSCodium/vscodium",\n "url": "https://github.com/VSCodium/vscodium/releases/tag/1.102.14746",\n "title": ""\n },\n "value": {\n "text": "1.102.14746",\n "title": "2025-07-16"\n },\n "date": "2025-07-16T18:27:49Z"\n }\n]\n'})}),"\n",(0,r.jsx)(n.h4,{id:"notes",children:"Notes"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"This widget is designed to render data generated by another system that complies with the schema. The example JSON data above was generated using a n8n workflow, and other ETL or workflow systems can generate similar results."}),"\n",(0,r.jsx)(n.li,{children:"To avoid requests to a different system in each refresh, you can save the input files locally in the user-data folder inside your Dashy installation."}),"\n",(0,r.jsxs)(n.li,{children:["To use json files from a different domain, remember to add ",(0,r.jsx)(n.code,{children:"useProxy: true"})," to the widget configuration. I have not tested this use case because I save all my input data locally on the Dashy server. Please open a ticket if you have an issue in this use case."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"example-32",children:"Example"}),"\n",(0,r.jsxs)(n.p,{children:["This widget renders a json file that from a ",(0,r.jsx)(n.code,{children:"json-data"})," directory inside the ",(0,r.jsx)(n.code,{children:"user-data"})," directory on the Dashy server."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: custom-list\n options:\n url: /json-data/github-releases.json\n title: 'Github Releases'\n daysForNew: 5\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-33",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Not needed for files hosted inside the ",(0,r.jsx)(n.code,{children:"user-data"})," directory. Use ",(0,r.jsx)(n.code,{children:"useProxy: true"})," to bypass CORS restrictions when using data from a different server."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": user defined"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": depends on the user defined host."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"custom-search",children:"Custom search"}),"\n",(0,r.jsx)(n.p,{children:"Allows web search using multiple user-defined search engines and other websites."}),"\n",(0,r.jsx)(n.h4,{id:"options-35",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"engines"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:"required"}),(0,r.jsxs)(n.td,{children:["An array of search engine objects. Each search engine object should have two required properties: ",(0,r.jsx)(n.strong,{children:"title"})," and ",(0,r.jsx)(n.strong,{children:"url"}),". See the example below."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"placeholder"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"Placeholder text in the search box."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"openingMethod"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsxs)(n.td,{children:["Open search in one of ",(0,r.jsx)(n.code,{children:"newtab"}),", ",(0,r.jsx)(n.code,{children:"sametab"})," or ",(0,r.jsx)(n.code,{children:"workspace"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"notes-1",children:"Notes"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["The first search engine in the engines array will be treated as the default search engine, and used when the user presses ",(0,r.jsx)(n.code,{children:"Enter"})," in the search box."]}),"\n",(0,r.jsx)(n.li,{children:"Popup blockers can interfere with opening a new search window."}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"example-33",children:"Example"}),"\n",(0,r.jsx)(n.p,{children:"This widget allows searching multiple search engines from dashy."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: 'custom-search'\n options:\n placeholder: Search for something using the buttons below\n engines:\n - title: SearXNG\n url: https://searx.lan/?q=\n - title: Quant\n url: https://www.qwant.com/?q=\n - title: Bing Web\n url: http://www.bing.com/search?q=\n - title: Bing Images\n url: http://www.bing.com/images/search?q=\n - title: Bing Maps\n url: http://www.bing.com/maps/search?q=\n - title: Yandex\n url: https://www.yandex.com/search/?text=\n - title: Passmark\n url: https://www.passmark.com/search/zoomsearch.php?zoom_query=\n - title: IMDB\n url: http://www.imdb.com/find?q=\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-34",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Not needed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": user defined"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": depends on the user defined search engines."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"rescuetime-overview",children:"RescueTime Overview"}),"\n",(0,r.jsx)(n.p,{children:"Show an overview of how you have spent your time for the current day."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/rescue-time"})}),"\n",(0,r.jsx)(n.h4,{id:"options-36",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"required"}),(0,r.jsx)(n.td,{children:"The API-Key generated in the RescueTime UI."})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-34",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: rescue-time\n useProxy: true\n options:\n apiKey: abcdefghijkl_mnop\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-35",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Depends on user subscription"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": ",(0,r.jsx)(n.a,{href:"https://www.rescuetime.com",children:"RescueTime"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.rescuetime.com/privacy",children:"RescueTime Privacy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"minecraft-server",children:"Minecraft Server"}),"\n",(0,r.jsx)(n.p,{children:"Show minecraft server status"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://i.ibb.co/hcmd4Wf/minecraft-widget.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-37",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Display title for server uses server address if not set."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"server"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Server hostname or ip(:port) will use srv records."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"bedrock"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If server is a bedrock edition server. (default false)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showMods"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Display mod list if available"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showPlayers"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Display player list if available"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showPlugins"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Display plugin list if available"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-35",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: minecraft-status\n options:\n title: Venity Network\n server: play.venitymc.com\n bedrock: true\n showMods: true\n showPlayers: true\n showPlugins: true\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-36",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": ",(0,r.jsx)(n.a,{href:"https://mcsrvstat.us/",children:"Minecraft Server Status"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://mcsrvstat.us/faq",children:"Minecraft Server Status FAQ"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"self-hosted-services-widgets",children:"Self-Hosted Services Widgets"}),"\n",(0,r.jsx)(n.h3,{id:"system-info",children:"System Info"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.mvg.de/datenschutz-mvg.html",children:"MVG Datenschutz"})]}),"\nDisplays info about the server which Dashy is hosted on. Includes user + host, operating system, uptime and basic memory & load data."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/system-info"})}),"\n",(0,r.jsx)(n.h4,{id:"options-38",children:"Options"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"No config options."})}),"\n",(0,r.jsx)(n.h4,{id:"example-36",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: system-info\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-37",children:"Info"}),"\n",(0,r.jsx)(n.p,{children:"No external data requests made"}),"\n",(0,r.jsxs)(n.p,{children:["Note that this widget is not available if you are running Dashy in a container or VM. Instead you can use the ",(0,r.jsx)(n.a,{href:"#system-resource-monitoring",children:"System Monitoring"})," widgets to display stats from the host system instead."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"cpu-history-netdata",children:"CPU History (NetData)"}),"\n",(0,r.jsx)(n.p,{children:"Pull recent CPU usage history from NetData."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/cpu-history"})}),"\n",(0,r.jsx)(n.h4,{id:"options-39",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your NetData instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The height of rendered chart in px. Defaults to ",(0,r.jsx)(n.code,{children:"300"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColor"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColors"})})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"string"})," / ",(0,r.jsx)(n.code,{children:"array"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Color of the chart value(s) as hex codes. ",(0,r.jsx)(n.code,{children:"chartColor"})," is a single value (defaults to ",(0,r.jsx)(n.code,{children:"--widget-text-color"}),"), whereas ",(0,r.jsx)(n.code,{children:"chartColors"})," is an array of colors"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-37",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nd-cpu-history\n options:\n host: http://192.168.1.1:19999\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-38",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/netdata/netdata",children:"GitHub - NetData"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.netdata.cloud/data-privacy/",children:"NetData Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"memory-history-netdata",children:"Memory History (NetData)"}),"\n",(0,r.jsx)(n.p,{children:"Pull recent system RAM usage from NetData, and show as a breakdown of different categories."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://i.ibb.co/2dsSWnk/nd-memory-history.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-40",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your NetData instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The height of rendered chart in px. Defaults to ",(0,r.jsx)(n.code,{children:"300"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColor"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColors"})})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"string"})," / ",(0,r.jsx)(n.code,{children:"array"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Color of the chart value(s) as hex codes. ",(0,r.jsx)(n.code,{children:"chartColor"})," is a single value (defaults to ",(0,r.jsx)(n.code,{children:"--widget-text-color"}),"), whereas ",(0,r.jsx)(n.code,{children:"chartColors"})," is an array of colors"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-38",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nd-ram-history\n options:\n host: http://192.168.1.1:19999\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-39",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/netdata/netdata",children:"GitHub - NetData"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.netdata.cloud/data-privacy/",children:"NetData Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"load-history-netdata",children:"Load History (NetData)"}),"\n",(0,r.jsx)(n.p,{children:"Pull recent load usage in 1, 5 and 15 minute intervals, from NetData."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/load-history"})}),"\n",(0,r.jsx)(n.h4,{id:"options-41",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your NetData instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The height of rendered chart in px. Defaults to ",(0,r.jsx)(n.code,{children:"300"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColor"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColors"})})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"string"})," / ",(0,r.jsx)(n.code,{children:"array"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Color of the chart value(s) as hex codes. ",(0,r.jsx)(n.code,{children:"chartColor"})," is a single value (defaults to ",(0,r.jsx)(n.code,{children:"--widget-text-color"}),"), whereas ",(0,r.jsx)(n.code,{children:"chartColors"})," is an array of colors"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-39",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nd-load-history\n options:\n host: http://192.168.1.1:19999\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-40",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/netdata/netdata",children:"GitHub - NetData"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.netdata.cloud/data-privacy/",children:"NetData Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-stats",children:"Pi-Hole Stats"}),"\n",(0,r.jsxs)(n.p,{children:["Displays the number of queries blocked by ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"}),"."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/pi-hole"})}),"\n",(0,r.jsx)(n.h4,{id:"options-42",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideStatus"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideChart"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideInfo"})})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide any of the three parts of the widget"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password. It is ",(0,r.jsx)(n.strong,{children:"NOT"})," your pi-hole admin interface or server password. It can be found in ",(0,r.jsx)(n.code,{children:"/etc/pihole/setupVars.conf"}),", and is a 64-character located on the line that starts with ",(0,r.jsx)(n.code,{children:"WEBPASSWORD"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-40",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-stats\n options:\n hostname: http://192.168.130.1\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["To avoid storing secrets in plaintext, both ",(0,r.jsx)(n.code,{children:"hostname"})," and ",(0,r.jsx)(n.code,{children:"apiKey"})," can be supplied via environment variables. See ",(0,r.jsx)(n.a,{href:"#handling-secrets",children:"Handling Secrets"}),"."]})}),"\n",(0,r.jsx)(n.h4,{id:"info-41",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-Hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-stats-v6",children:"Pi-Hole Stats v6"}),"\n",(0,r.jsxs)(n.p,{children:["Displays the number of queries blocked by ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"}),". Use this version of the widget if you have a v6+ Pi-Hole instance."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://i.ibb.co/zftCLJN/pi-hole-stats.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-43",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideStatus"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideChart"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideInfo"})})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide any of the three parts of the widget"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password or application password. It ",(0,r.jsx)(n.strong,{children:"IS"})," your Pi-Hole admin interface password ",(0,r.jsx)(n.strong,{children:"UNLESS"})," you have 2FA turned on (in contrast to the old widget). If you have 2FA turned on you will need to create an application password. Refer to Pi-Hole documentation for how to create an application password."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-41",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-stats-v6\n options:\n hostname: http://192.168.130.1\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["To avoid storing secrets in plaintext, both ",(0,r.jsx)(n.code,{children:"hostname"})," and ",(0,r.jsx)(n.code,{children:"apiKey"})," can be supplied via environment variables. See ",(0,r.jsx)(n.a,{href:"#handling-secrets",children:"Handling Secrets"}),"."]})}),"\n",(0,r.jsx)(n.h4,{id:"info-42",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-Hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-queries",children:"Pi-Hole Queries"}),"\n",(0,r.jsxs)(n.p,{children:["Shows top queries that were blocked and allowed by ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"}),"."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/pi-hole-queries"})}),"\n",(0,r.jsx)(n.h4,{id:"options-44",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password. It is ",(0,r.jsx)(n.strong,{children:"NOT"})," your pi-hole admin interface or server password. It can be found in ",(0,r.jsx)(n.code,{children:"/etc/pihole/setupVars.conf"}),", and is a 64-character located on the line that starts with ",(0,r.jsx)(n.code,{children:"WEBPASSWORD"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"count"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of queries to display. Defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-42",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-top-queries\n options:\n hostname: https://pi-hole.local\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-43",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-queries-v6",children:"Pi-Hole Queries v6"}),"\n",(0,r.jsxs)(n.p,{children:["Shows top queries that were blocked and allowed by ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"}),". Use this version of the widget if you have a v6+ Pi-Hole instance."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://i.ibb.co/pXR0bdQ/pi-hole-queries.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-45",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password or application password. It ",(0,r.jsx)(n.strong,{children:"IS"})," your Pi-Hole admin interface password ",(0,r.jsx)(n.strong,{children:"UNLESS"})," you have 2FA turned on (in contrast to the old widget). If you have 2FA turned on you will need to create an application password. Refer to Pi-Hole documentation for how to create an application password."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"count"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of queries to display. Defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-43",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-top-queries-v6\n options:\n hostname: https://pi-hole.local\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-44",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-recent-traffic",children:"Pi-Hole Recent Traffic"}),"\n",(0,r.jsxs)(n.p,{children:["Shows number of recent traffic, using allowed and blocked queries from ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://pixelflare.cc/alicia/dashy/pi-hole-trafic"})}),"\n",(0,r.jsx)(n.h4,{id:"options-46",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password. It is ",(0,r.jsx)(n.strong,{children:"NOT"})," your pi-hole admin interface or server password. It can be found in ",(0,r.jsx)(n.code,{children:"/etc/pihole/setupVars.conf"}),", and is a 64-character located on the line that starts with ",(0,r.jsx)(n.code,{children:"WEBPASSWORD"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-44",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-traffic\n options:\n hostname: https://pi-hole.local\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-45",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-recent-traffic-v6",children:"Pi-Hole Recent Traffic v6"}),"\n",(0,r.jsxs)(n.p,{children:["Shows number of recent traffic, using allowed and blocked queries from ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"}),". Use this version of the widget if you have a v6+ Pi-Hole instance."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://i.ibb.co/7kdxxwx/pi-hole-recent-queries.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-47",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password or application password. It ",(0,r.jsx)(n.strong,{children:"IS"})," your Pi-Hole admin interface password ",(0,r.jsx)(n.strong,{children:"UNLESS"})," you have 2FA turned on (in contrast to the old widget). If you have 2FA turned on you will need to create an application password. Refer to Pi-Hole documentation for how to create an application password."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-45",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-traffic-v6\n options:\n hostname: https://pi-hole.local\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-46",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"stat-ping-statuses",children:"Stat Ping Statuses"}),"\n",(0,r.jsxs)(n.p,{children:["Displays the current and recent uptime of your running services, via a self-hosted instance of ",(0,r.jsx)(n.a,{href:"https://github.com/statping/statping",children:"StatPing"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"300",src:"https://pixelflare.cc/alicia/dashy/statping"})}),"\n",(0,r.jsx)(n.h4,{id:"options-48",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your StatPing instance, without a trailing slash"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"groupId"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:"Optional"}),(0,r.jsxs)(n.td,{children:["If provided, only Services in the given group are displayed. Defaults to ",(0,r.jsx)(n.code,{children:"0"})," in which case all services are displayed."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showChart"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:"Optional"}),(0,r.jsxs)(n.td,{children:["If provided and ",(0,r.jsx)(n.code,{children:"false"})," then charts are not displayed. Defaults to ",(0,r.jsx)(n.code,{children:"true"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showInfo"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:"Optional"}),(0,r.jsxs)(n.td,{children:["If provided and ",(0,r.jsx)(n.code,{children:"false"})," then information summaries are not displayed. Defaults to ",(0,r.jsx)(n.code,{children:"true"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-46",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: stat-ping\n options:\n hostname: http://192.168.130.1:8080\n"})}),"\n",(0,r.jsx)(n.p,{children:"or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: stat-ping\n options:\n hostname: http://192.168.130.1:8080\n groupId: 3\n showChart: false\n showInfo: false\n"})}),"\n",(0,r.jsxs)(n.p,{children:["You can use multiple StatPing widgets with different ",(0,r.jsx)(n.code,{children:"groupId"}),"s."]}),"\n",(0,r.jsx)(n.p,{children:"Note, the Group Id is not directly visible in StatPing UI, you can inspect the group select HTML element or the API response to find out."}),"\n",(0,r.jsx)(n.h4,{id:"info-47",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/statping/statping",children:"GitHub - StatPing"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://docs.statping.com/",children:"StatPing Docs"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"synology-download-station",children:"Synology Download Station"}),"\n",(0,r.jsx)(n.p,{children:"Displays the current downloads/torrents tasks of your Synology NAS"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://pixelflare.cc/alicia/dashy/synology-downloads"})}),"\n",(0,r.jsx)(n.h4,{id:"options-49",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Synology NAS, without a trailing slash"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The username of a user on your synology NAS. You will see only this user download station tasks if he is not part of the administrator group. Currently don't support OTP protected accounts."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The password of the account specified above."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-47",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: synology-download\n options:\n hostname: http://192.168.1.1:8080\n username: dashy\n password: totally-secure-password\n\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-48",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://www.synology.com/en-us",children:"Synology"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.synology.com/en-us/company/legal/privacy",children:"Synology Privacy Statement"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"adguard-home-block-stats",children:"AdGuard Home Block Stats"}),"\n",(0,r.jsxs)(n.p,{children:["Fetches data from your ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"})," instance, and\ndisplays total number of allowed and blocked queries, plus a pie chart showing breakdown by block type."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/adguard-stats"})}),"\n",(0,r.jsx)(n.h4,{id:"options-50",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your AdGuard Home instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your username here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your password here"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-48",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: adguard-stats\n useProxy: true\n options:\n hostname: http://127.0.0.1\n username: admin\n password: test\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-49",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/privacy.html",children:"AdGuard Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"adguard-home-filters",children:"AdGuard Home Filters"}),"\n",(0,r.jsxs)(n.p,{children:["Fetches data from your ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"})," instance, to display the current status of each of your filter lists. Includes filter name, last updated, number of items, and a link to the list."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/adguard-filters"})}),"\n",(0,r.jsx)(n.h4,{id:"options-51",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your AdGuard Home instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your username here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your password here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showOnOffStatusOnly"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", will only show aggregated AdGuard filter status (on/off), instead of a list of filters"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-49",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: adguard-filter-status\n useProxy: true\n options:\n hostname: http://127.0.0.1\n username: admin\n password: test\n showOnOffStatusOnly: false\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-50",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/privacy.html",children:"AdGuard Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"adguard-home-dns-info",children:"AdGuard Home DNS Info"}),"\n",(0,r.jsxs)(n.p,{children:["Fetches data from your ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"})," instance, and displays the current status (Enabled / Disabled) of AdGuard DNS. Click show more to view detailed info, including upstream DNS provider, active ports, and the status of DNSSEC, EDNS CS, PTR and IPv6."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/adguard-dns"})}),"\n",(0,r.jsx)(n.h4,{id:"options-52",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your AdGuard Home instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your username here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your password here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showFullInfo"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),', the full DNS info will be shown by default, without having to click "Show Info"']})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-50",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: adguard-dns-info\n useProxy: true\n options:\n hostname: http://127.0.0.1\n username: admin\n password: test\n showFullInfo: false\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-51",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/privacy.html",children:"AdGuard Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"adguard-home-top-domains",children:"AdGuard Home Top Domains"}),"\n",(0,r.jsxs)(n.p,{children:["Fetches data from your ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"})," instance, and displays a list of the most queried, and most blocked domains."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/adguard-domains"})}),"\n",(0,r.jsx)(n.h4,{id:"options-53",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your AdGuard Home instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your username here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your password here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify the number of results to show, between ",(0,r.jsx)(n.code,{children:"1"})," and ",(0,r.jsx)(n.code,{children:"100"}),", defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideBlockedDomains"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Don't show the blocked domains list (queried domains only)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideQueriedDomains"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Don't show the queried domains list (blocked domains only)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-51",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: adguard-top-domains\n useProxy: true\n options:\n hostname: http://127.0.0.1\n username: admin\n password: test\n limit: 10\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-52",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/privacy.html",children:"AdGuard Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-user",children:"Nextcloud User"}),"\n",(0,r.jsxs)(n.p,{children:["Nextcloud is a ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/install/#instructions-server",children:"self hosted"})," productivity platform, it can also be used free of charge with ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/sign-up/",children:"hundreds of existing hosting providers"})," that offer a free Nextcloud account."]}),"\n",(0,r.jsx)(n.p,{children:"Displays branding information of a Nextcloud server (logo, url, slogan) and some user details (name, login name, last login, disk space or quota). Use with regular or admin user."}),"\n",(0,r.jsx)(n.p,{children:"Shows quota usage when quota is enabled for the user or disk usage when not enabled."}),"\n",(0,r.jsx)(n.p,{children:"Known issues: the User API incorrectly reports available disk space as total for admin users when quota is not enabled (which usually is the case for admins)."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/nextcloud-user",alt:"nextcloud-user"})}),"\n",(0,r.jsx)(n.h4,{id:"options-54",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud username"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-52",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-user\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-53",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-user-statuses",children:"Nextcloud User Statuses"}),"\n",(0,r.jsx)(n.p,{children:"Show user statuses for selected users."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/nextcloud-user-status",alt:"nextcloud-userstatus"})}),"\n",(0,r.jsx)(n.h4,{id:"options-55",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud username"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"users"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Nextcloud User IDs to show statuses for, list size between ",(0,r.jsx)(n.code,{children:"1"})," and ",(0,r.jsx)(n.code,{children:"100"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showEmpty"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Show statuses without a message, defaults to ",(0,r.jsx)(n.code,{children:"true"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-53",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-user-status\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n users: ['bob', 'alice']\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-54",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-notifications",children:"Nextcloud Notifications"}),"\n",(0,r.jsx)(n.p,{children:"Displays your notifications and allows deleting them."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/next-cloud-notifications",alt:"nextcloud-notifications"})}),"\n",(0,r.jsx)(n.h4,{id:"options-56",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud username"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number|string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit displayed notifications either by count, e.g. ",(0,r.jsx)(n.code,{children:"5"})," to show the 5 most recent, or by age, e.g. ",(0,r.jsx)(n.code,{children:"1d"})," to only show notifications not older than a day. Accepted suffixes for age limit are ",(0,r.jsx)(n.code,{children:"m"}),", ",(0,r.jsx)(n.code,{children:"h"})," and ",(0,r.jsx)(n.code,{children:"d"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-54",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-notifications\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n limit: 6h\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-55",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-system",children:"Nextcloud System"}),"\n",(0,r.jsx)(n.p,{children:"Visualises overall memory utilisation and CPU load averages, shows server versions."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/next-cloud-sysyem",alt:"nextcloud-system"})}),"\n",(0,r.jsx)(n.h4,{id:"options-57",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Must be a Nextcloud admin user"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-55",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-system\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-56",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-stats",children:"Nextcloud Stats"}),"\n",(0,r.jsx)(n.p,{children:"Shows key usage statistics about your Nextcloud server."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/nextcloud-stats",alt:"nextcloud-stats"})}),"\n",(0,r.jsx)(n.h4,{id:"options-58",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Must be a Nextcloud admin user"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-56",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-stats\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-57",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-php-opcache-stats",children:"Nextcloud PHP OPcache Stats"}),"\n",(0,r.jsx)(n.p,{children:"Shows statistics about PHP OPcache performance on your Nextcloud server."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/nextcloud-php",alt:"nextcloud-phpopcache"})}),"\n",(0,r.jsx)(n.h4,{id:"options-59",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Must be a Nextcloud admin user"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-57",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-php-opcache\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-58",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"ntfy-stream",children:"ntfy stream"}),"\n",(0,r.jsxs)(n.p,{children:["Subscribes to topics on a ",(0,r.jsx)(n.strong,{children:"private"})," ",(0,r.jsx)(n.a,{href:"https://docs.ntfy.sh/",children:"ntfy"})," server, and shows ",(0,r.jsx)(n.strong,{children:"new messages"})," as they arrive."]}),"\n",(0,r.jsx)(n.h4,{id:"options-60",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"server_url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The server url"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"topic"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A topic, or a comma separated list of topics"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"auth"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsxs)(n.td,{children:["An ",(0,r.jsx)(n.a,{href:"https://docs.ntfy.sh/subscribe/api/#authentication",children:"auth string"}),". \u26a0\ufe0f This is sent as query parameter."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"A title for the widget."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-58",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: ntfy-stream\n useProxy: false\n options:\n title: NTFY stream\n server_url: https://myntfy.server.tld\n topic: alert,warning,mytopic\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-59",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": Disabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": ",(0,r.jsx)(n.a,{href:"https://docs.ntfy.sh/install/",children:"Self-hosted"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsx)(n.a,{href:"https://docs.ntfy.sh/privacy/",children:"ntfy Privacy Policy"})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"proxmox-lists",children:"Proxmox lists"}),"\n",(0,r.jsx)(n.p,{children:"Shows lists of nodes, containers, and VMs in a Proxmox virtual environment cluster, with a status indicator."}),"\n",(0,r.jsx)(n.h4,{id:"options-61",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cluster_url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The URL of the proxmox cluster server. No trailing ",(0,r.jsx)(n.code,{children:"/"}),". for example: ",(0,r.jsx)(n.code,{children:"https://proxmox.lan:8006"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"user_name"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["A Proxmox API Username, for example ",(0,r.jsx)(n.code,{children:"root@pam"})," or ",(0,r.jsx)(n.code,{children:"dashy@pve"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"token_name"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A Proxmox API token name. You can get a token in the API section of the cluster management interface."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"token_uuid"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The value of the token entered above. This is normally a UUID."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"node"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"A Proxmox node name. If empty or not supplied, a list of nodes will be shown."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"node_data"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsxs)(n.td,{children:["This is required if a node is selected, Currently this accepts two values, either ",(0,r.jsx)(n.code,{children:"lxc"})," or ",(0,r.jsx)(n.code,{children:"qemu"})," but the widget can be improved to get other types of data from the Proxmox API."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"A widget title."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title_as_link"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsxs)(n.td,{children:["When this is set to anything other than 0 or false, the title will be linked to the value entered in the ",(0,r.jsx)(n.code,{children:"cluster_url"})," option."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"footer"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"A widget footer."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"footer_as_link"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsxs)(n.td,{children:["When this is set to anything other than 0 or false, the title will be linked to the value entered in the ",(0,r.jsx)(n.code,{children:"cluster_url"})," option."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hide_templates"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"When this is set to anything other than 0 or false, templates will be filtered out of the result list."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-59",children:"Example"}),"\n",(0,r.jsx)(n.p,{children:"This will show the list of nodes."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: proxmox-lists\n useProxy: true \n options:\n cluster_url: https://proxmox.lan:8006\n user_name: root@pam\n token_name: dashy\n token_uuid: bfb152df-abcd-abcd-abcd-ccb95a472d01\n"})}),"\n",(0,r.jsx)(n.p,{children:"This will show the list of VMs, with a title and a linked fotter, hiding VM templates."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: proxmox-lists\n useProxy: true \n options:\n cluster_url: https://proxmox.lan:8006\n user_name: root@pam\n token_name: dashy\n token_uuid: bfb152df-abcd-abcd-abcd-ccb95a472d01\n node: proxmox\n node_data: qemu\n title: Proxmox VMs\n title_as_link: false\n footer: Proxmox\n footer_as_link: true\n hide_templates: 1\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-60",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://proxmox.com/en/proxmox-ve",children:"Proxmox Virtual Environment"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://proxmox.com/en/privacy-policy",children:"Proxmox's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"404 Error in development mode"}),": The error might disappear in production mode ",(0,r.jsx)(n.code,{children:"yarn start"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"500 Error in production mode"}),": Try adding the certificate authority (CA) certificate of your Proxmox host to Node.js.\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Download the Proxmox CA certificate to your Dashy host."}),"\n",(0,r.jsxs)(n.li,{children:["Export environment variable ",(0,r.jsx)(n.code,{children:"NODE_EXTRA_CA_CERTS"})," and set its value to the path of the downloaded CA certificate. Example: ",(0,r.jsx)(n.code,{children:"export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/devlab_ca.pem"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"sabnzbd",children:"Sabnzbd"}),"\n",(0,r.jsx)(n.p,{children:"Shows queue information regarding your self hosted Sabnzbd server."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/sabnzbd",alt:"Sabnzbd"})}),"\n",(0,r.jsx)(n.h4,{id:"options-62",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"sabnzbdUrl"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The URL of the Sabnzbd server. No trailing ",(0,r.jsx)(n.code,{children:"/"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["API key for Sabnzbd access. Located under ",(0,r.jsx)(n.code,{children:"Config"})," -> ",(0,r.jsx)(n.code,{children:"General"})," -> ",(0,r.jsx)(n.code,{children:"Security"})," -> ",(0,r.jsx)(n.code,{children:"API Key"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideDetails"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Hides extra server queue details."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideQueue"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Hides the queue list in an expandable dropdown."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-60",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: sabnzbd\n options:\n sabnzbdUrl: 'https://sabnzbd.example.com'\n apiKey: XXXXXXXXXXXXXXXXXX\n hideDetails: false\n hideQueue: false\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-61",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://sabnzbd.org/",children:"Sabnzbd"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://forums.sabnzbd.org/ucp.php?mode=privacy",children:"Sabnzbd Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"gluetun-vpn-info",children:"Gluetun VPN Info"}),"\n",(0,r.jsx)(n.p,{children:"Display info from the Gluetun VPN container public IP API. This can show the IP and location data for the exit VPN node."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://pixelflare.cc/alicia/dashy/guletn-vpn"})}),"\n",(0,r.jsx)(n.h4,{id:"options-63",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"visibleFields"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["A comma separated list of the fields you want visible in the widget. You can have any number of the following : ",(0,r.jsx)(n.code,{children:"public_ip"}),", ",(0,r.jsx)(n.code,{children:"region"}),", ",(0,r.jsx)(n.code,{children:"country"}),", ",(0,r.jsx)(n.code,{children:"city"}),", ",(0,r.jsx)(n.code,{children:"location"}),", ",(0,r.jsx)(n.code,{children:"organisation"}),", ",(0,r.jsx)(n.code,{children:"postal_code"}),", ",(0,r.jsx)(n.code,{children:"timezone"}),". Defaults to just ",(0,r.jsx)(n.code,{children:"public_ip"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The url to the gluetun HTTP control server. E.g. ",(0,r.jsx)(n.code,{children:"http://gluetun:8000"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-61",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gluetun-status\n useProxy: true\n options:\n hostname: http://server-or-conatiner-hostname:8000\n visibleFields: public_ip,region,country,city,location,organisation,postal_code,timezone\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-62",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/qdm12/gluetun",children:"Gluetun"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/qdm12/gluetun/wiki",children:"Gluetun Wiki"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"drone-ci-builds",children:"Drone CI Builds"}),"\n",(0,r.jsxs)(n.p,{children:["Display the last builds from a ",(0,r.jsx)(n.a,{href:"https://www.drone.ci",children:"Drone CI"})," instance. A self-hosted CI system that uses docker."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://pixelflare.cc/alicia/dashy/drone-ci"})}),"\n",(0,r.jsx)(n.h4,{id:"options-64",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The hostname of the Drone CI instance."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The API key (https://[your-drone-instance]/account)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"integer"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Limit the amounts of listed builds."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"repo"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Show only builds of the specified repo"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-62",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: drone-ci\n updateInterval: 30\n options:\n host: https://drone.somedomain.com\n apiKey: my-very-secret-api-key\n limit: 10\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-63",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://www.drone.io",children:"Drone"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.drone.io",children:"Drone"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"filebrowser",children:"Filebrowser"}),"\n",(0,r.jsxs)(n.p,{children:["Displays storage statistics and file listings from a ",(0,r.jsx)(n.a,{href:"https://github.com/gtsteffaniak/filebrowser",children:"Filebrowser Quantum"})," instance. Shows directory size, file/folder counts, favorite files, and recently modified files with quick-access links."]}),"\n",(0,r.jsx)(n.h4,{id:"options-65",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of your Filebrowser instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A long-lived API key (create in Settings \u2192 API Keys)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"source"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"The source/scope name to browse. Defaults to the first available source"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"path"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The directory path to display. Defaults to ",(0,r.jsx)(n.code,{children:"/"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"favorites"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"List of filenames to show as quick-access favorites"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showRecent"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Number of recently modified files to display. Defaults to ",(0,r.jsx)(n.code,{children:"5"}),", set to ",(0,r.jsx)(n.code,{children:"0"})," to disable"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Maximum number of files to display per section. Defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideStats"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If ",(0,r.jsx)(n.code,{children:"true"}),", hides the storage statistics section"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideFavorites"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If ",(0,r.jsx)(n.code,{children:"true"}),", hides the favorites section"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideRecent"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If ",(0,r.jsx)(n.code,{children:"true"}),", hides the recent files section"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showDetailedStats"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If ",(0,r.jsx)(n.code,{children:"true"}),", shows additional statistics including last modified date, largest file, hidden file count, total items, and file type breakdown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-63",children:"Example"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Basic usage:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: filebrowser\n useProxy: true\n options:\n hostname: http://filebrowser.local:8080\n apiKey: DASHY_FILEBROWSER_KEY\n source: Documents\n path: /\n showRecent: 5\n favorites:\n - important-notes.txt\n - config.yaml\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"With detailed statistics:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: filebrowser\n useProxy: true\n options:\n hostname: http://filebrowser.local:8080\n apiKey: DASHY_FILEBROWSER_KEY\n source: Downloads\n showDetailedStats: true\n showRecent: 10\n limit: 15\n"})}),"\n",(0,r.jsx)(n.h4,{id:"widget-sections",children:"Widget Sections"}),"\n",(0,r.jsx)(n.p,{children:"The widget displays up to four sections:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Storage Stats"})," - Directory name, total size, file and folder counts"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Detailed Stats"})," (optional) - Last modified date, largest file, hidden file count, total items, and file type breakdown with badges"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Favorites"})," - Quick-access links to user-specified files"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Recent Files"})," - Most recently modified files sorted by date"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"info-64",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/gtsteffaniak/filebrowser",children:"Filebrowser Quantum"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsx)(n.em,{children:"Self-Hosted"})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"linkding",children:"Linkding"}),"\n",(0,r.jsx)(n.p,{children:"Linkding is a self-hosted bookmarking service, which has a clean interface and is simple to set up. This lists the links, filterable by tags."}),"\n",(0,r.jsx)(n.h4,{id:"options-66",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The hostname of the Drone CI instance."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The API key (",(0,r.jsx)(n.a,{href:"https://your-linkding-instance/settings/integrations",children:"https://your-linkding-instance/settings/integrations"}),")."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"tags"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"list of string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter the links by tag."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-64",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: linkding\n updateInterval: 30\n options:\n host: https://lingding.somedomain.com\n apiKey: my-very-secret-api-key\n tags: \n - rpg\n - markdown\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-65",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/sissbruecker/linkding",children:"Linkding"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/sissbruecker/linkding",children:"Linkding"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"uptime-kuma",children:"Uptime Kuma"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})," is an easy-to-use self-hosted monitoring tool."]}),"\n",(0,r.jsx)(n.h4,{id:"options-67",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Uptime Kuma instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The API key (see ",(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma/wiki/API-Keys",children:"https://github.com/louislam/uptime-kuma/wiki/API-Keys"}),")."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-65",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: uptime-kuma\n useProxy: true\n options:\n apiKey: uk2_99H0Yd3I2pPNIRfn0TqBFu4g5q85R1Mh75yZzw6H\n url: http://192.168.1.106:3691/metrics\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-66",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})," )"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"uptime-kuma-status-page",children:"Uptime Kuma Status Page"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})," is an easy-to-use self-hosted monitoring tool."]}),"\n",(0,r.jsx)(n.h4,{id:"options-68",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Uptime Kuma instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"slug"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The slug of the status page"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"monitorNames"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"strins"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Names of monitored services (in the same order as on the kuma uptime status page)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-66",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'- type: uptime-kuma-status-page\n options:\n host: http://localhost:3001\n slug: another-beautiful-status-page\n monitorNames:\n - "Name1"\n - "Name2"\n'})}),"\n",(0,r.jsx)(n.h4,{id:"info-67",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Needed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})," )"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"tactical-rmm",children:"Tactical RMM"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/amidaware/tacticalrmm",children:"Tactical RMM"})," is a self-hosted remote monitoring & management tool."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsxs)(n.a,{href:"https://github.com/user-attachments/assets/152a7205-e5de-401f-bad8-19063ddfaf3c",children:["\n ",(0,r.jsx)(n.img,{src:"https://github.com/user-attachments/assets/5921d46f-d84c-494d-8aaf-6b20cc592640",alt:"Capture",border:"0"})]})}),"\n",(0,r.jsx)(n.h4,{id:"options-69",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The status endpoint URL (",(0,r.jsx)(n.a,{href:"https://api.example.com/core/v2/status/",children:"https://api.example.com/core/v2/status/"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"token"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The MON_TOKEN (see ",(0,r.jsx)(n.a,{href:"https://docs.tacticalrmm.com/tipsntricks/#monitor-your-trmm-instance-via-the-built-in-monitoring-endpoint",children:"https://docs.tacticalrmm.com/tipsntricks/#monitor-your-trmm-instance-via-the-built-in-monitoring-endpoint"}),")."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-67",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: trmm\n useProxy: true\n options:\n token: PkPVKMzbmXgeQDlJWb0WXYvsIk3JvZyadURud2cSTdMia6hUbQ\n url: https://api.example.com/core/v2/status/\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-68",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/amidaware/tacticalrmm",children:"Tactical RMM"})," )"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/amidaware/tacticalrmm",children:"Tactical RMM"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"system-resource-monitoring",children:"System Resource Monitoring"}),"\n",(0,r.jsx)(n.h3,{id:"glances",children:"Glances"}),"\n",(0,r.jsxs)(n.p,{children:["The easiest method for displaying system info and resource usage in Dashy is with ",(0,r.jsx)(n.a,{href:"https://nicolargo.github.io/glances/",children:"Glances"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Glances is a cross-platform monitoring tool developed by ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo",children:"@nicolargo"}),". It's similar to top/htop but with a ",(0,r.jsx)(n.a,{href:"https://glances.readthedocs.io/en/latest/api.html",children:"Rest API"})," and many ",(0,r.jsx)(n.a,{href:"https://glances.readthedocs.io/en/latest/gw/index.html",children:"data exporters"})," available. Under the hood, it uses ",(0,r.jsx)(n.a,{href:"https://github.com/giampaolo/psutil",children:"psutil"})," for retrieving system info."]}),"\n",(0,r.jsxs)(n.p,{children:["If you don't already have it installed, either follow the ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo/glances/blob/master/README.rst",children:"Installation Guide"})," for your system, or setup ",(0,r.jsx)(n.a,{href:"https://glances.readthedocs.io/en/latest/docker.html",children:"with Docker"}),", or use the one-line install script: ",(0,r.jsx)(n.code,{children:"curl -L https://bit.ly/glances | /bin/bash"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["If you are using Docker to run glances make sure to add the enviroment variable ",(0,r.jsx)(n.code,{children:"-e TZ = {YourTimeZone}"}),". You can get a list of valid timezones by running ",(0,r.jsx)(n.code,{children:"timedatectl list-timezones"})," on any linux system. This is needed so the graphs show the currect time."]}),"\n",(0,r.jsx)(n.p,{children:"Here an example for Docker"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:" docker run -d \\\n --name glances \\\n --restart unless-stopped \\\n -v /var/run/docker.sock:/var/run/docker.sock:ro \\\n -p 61208:61208 \\\n --pid host \\\n --privileged \\\n -e GLANCES_OPT=-w \\\n -e PUID=1000 \\\n -e PGID=1000 \\\n -e TZ=Europe/Zurich \\\n nicolargo/glances:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Glances can be launched with the ",(0,r.jsx)(n.code,{children:"glances"})," command. You'll need to run it in web server mode, using the ",(0,r.jsx)(n.code,{children:"-w"})," option for the API to be reachable. If you don't plan on using the Web UI, then you can disable it using ",(0,r.jsx)(n.code,{children:"--disable-webui"}),". See the ",(0,r.jsx)(n.a,{href:"https://glances.readthedocs.io/en/latest/cmds.html",children:"command reference docs"})," for more info."]}),"\n",(0,r.jsxs)(n.p,{children:["If Glances is running on a Windows system it is recommended to add the following arguments ",(0,r.jsx)(n.code,{children:"--disable-plugin all --enable-plugin cpu,mem,diskio,ip,network,containers,quicklook,load,fs,alert -w"})," This is due to Glances not being that stable on windows, so disabling all plugins that aren't used by Dashy widgets can save on ressources."]}),"\n",(0,r.jsx)(n.h4,{id:"options-70",children:"Options"}),"\n",(0,r.jsxs)(n.p,{children:["All Glance's based widgets require a ",(0,r.jsx)(n.code,{children:"hostname"}),". All other parameters are optional."]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL or IP + port to your Glances instance (without a trailing slash)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If you have setup basic auth on Glances, specify username here (defaults to ",(0,r.jsx)(n.code,{children:"glances"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If you have setup basic auth on Glances, specify password here. ",(0,r.jsx)(n.strong,{children:"Note"}),": since this password is in plaintext, it is important not to reuse it anywhere else"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiVersion"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify an API version, defaults to V ",(0,r.jsx)(n.code,{children:"3"}),". Note that support for older versions is limited"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"For widgets that show a time-series chart, optionally limit the number of data points returned. A higher number will show more historical results, but will take longer to load. A value between 300 - 800 is usually optimal"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["Note that if auth is configured, requests must be proxied with ",(0,r.jsx)(n.code,{children:"useProxy: true"})]}),"\n",(0,r.jsx)(n.h4,{id:"info-69",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo/glances",children:"GitHub - Nicolargo/Glances"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"screenshot",children:"Screenshot"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.a,{href:"https://ibb.co/pR6dMZT",children:(0,r.jsx)(n.img,{src:"https://pixelflare.cc/alicia/dashy/monitor-board",alt:"example-screenshot"})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"current-cpu-usage",children:"Current CPU Usage"}),"\n",(0,r.jsx)(n.p,{children:"Live-updating current CPU usage, as a combined average across all cores"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/current-cpu"})}),"\n",(0,r.jsx)(n.h4,{id:"example-68",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-current-cpu\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"current-cpu-usage-speedometer",children:"Current CPU Usage Speedometer"}),"\n",(0,r.jsx)(n.p,{children:"Speedometer styled version of the Current CPU Usage widget"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/current-cpu-speedometer"})}),"\n",(0,r.jsx)(n.h4,{id:"example-69",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-cpu-speedometer\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"cpu-usage-per-core",children:"CPU Usage Per Core"}),"\n",(0,r.jsx)(n.p,{children:"Live-updating CPU usage breakdown per core"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/cpu-per-core"})}),"\n",(0,r.jsx)(n.h4,{id:"example-70",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-current-cores\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"cpu-usage-history",children:"CPU Usage History"}),"\n",(0,r.jsx)(n.p,{children:"Recent CPU usage history, across all cores, and displayed by user and system"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://pixelflare.cc/alicia/dashy/cpu-history-chart"})}),"\n",(0,r.jsx)(n.h4,{id:"options-71",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit the number of results returned, rendering more data points will take longer to load. Defaults to ",(0,r.jsx)(n.code,{children:"100"})]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-71",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-cpu-history\n options:\n hostname: http://192.168.130.2:61208\n limit: 60\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"current-memory-usage",children:"Current Memory Usage"}),"\n",(0,r.jsx)(n.p,{children:"Real-time memory usage gauge, with more info visible on click"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/current-mem"})}),"\n",(0,r.jsx)(n.h4,{id:"example-72",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-current-mem\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"current-memory-usage-speedometer",children:"Current Memory Usage Speedometer"}),"\n",(0,r.jsx)(n.p,{children:"Speedometer styled version of the Current Memory Usage widget"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/current-mem-speedometer"})}),"\n",(0,r.jsx)(n.h4,{id:"example-73",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-mem-speedometer\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"memory-usage-history",children:"Memory Usage History"}),"\n",(0,r.jsx)(n.p,{children:"Recent memory usage chart"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://pixelflare.cc/alicia/dashy/mem-history-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"options-72",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit the number of results returned, rendering more data points will take longer to load. Defaults to ",(0,r.jsx)(n.code,{children:"100"})]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-74",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-mem-history\n options:\n hostname: http://localhost:61208\n limit: 80\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"disk-space",children:"Disk Space"}),"\n",(0,r.jsx)(n.p,{children:"List connected disks, showing free / used space and other info (file system, mount point and space available)"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/diskspace-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-75",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-disk-space\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"disk-io",children:"Disk IO"}),"\n",(0,r.jsx)(n.p,{children:"Shows real-time read and write speeds and operations per sec for each disk"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/disk-io-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-76",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-disk-io\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"system-load",children:"System Load"}),"\n",(0,r.jsx)(n.p,{children:"Shows the number of processes waiting in the run-queue, averaged across all cores. Displays for past 5, 10 and 15 minutes"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/system-load-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-77",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-system-load\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"uptime",children:"Uptime"}),"\n",(0,r.jsx)(n.p,{children:"Displays the system uptime fetched from Glances."}),"\n",(0,r.jsx)(n.h4,{id:"example-78",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-uptime\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"system-load-history",children:"System Load History"}),"\n",(0,r.jsxs)(n.p,{children:["Shows recent historical system load, calculated from the number of processes waiting in the run-queue, in 1, 5 and 15 minute intervals, and averaged across all cores. Optionally specify ",(0,r.jsx)(n.code,{children:"limit"})," to set number of results returned, defaults to ",(0,r.jsx)(n.code,{children:"500"}),", max ",(0,r.jsx)(n.code,{children:"100000"}),", but the higher the number the longer the load and render times will be."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://pixelflare.cc/alicia/dashy/system-load-history-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-79",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-load-history\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"network-interfaces",children:"Network Interfaces"}),"\n",(0,r.jsx)(n.p,{children:"Lists visible network interfaces, including real-time upload/ download stats"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/network-interfaces-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-80",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-network-interfaces\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"network-traffic",children:"Network Traffic"}),"\n",(0,r.jsxs)(n.p,{children:["Shows amount of data recently uploaded/ downloaded across all network interfaces. Optionally set the ",(0,r.jsx)(n.code,{children:"limit"})," option to specify number historical of data points to return"]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/network-traffic-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-81",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-network-traffic\n options:\n hostname: http://192.168.130.2:61208\n limit: 500\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"resource-usage-alerts",children:"Resource Usage Alerts"}),"\n",(0,r.jsx)(n.p,{children:"Lists recent high resource usage alerts (e.g. CPU, mem, IO, load, temp)"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://i.ibb.co/w01NX5R/gl-alerts.png"})}),"\n",(0,r.jsx)(n.h4,{id:"example-82",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-alerts\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"ip-address",children:"IP Address"}),"\n",(0,r.jsx)(n.p,{children:"Shows public and private IP address. Note that the ip plugin is not available on all instances of Glances."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/ip-addresses-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-83",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-ip-address\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"cpu-temp",children:"CPU Temp"}),"\n",(0,r.jsx)(n.p,{children:"Displays temperature data from system CPUs."}),"\n",(0,r.jsxs)(n.p,{children:["Note: This widget uses the ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo/glances/blob/develop/glances/plugins/glances_sensors.py",children:(0,r.jsx)(n.code,{children:"sensors"})})," plugin, which is disabled by default, and may cause ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo/glances/issues/1664#issuecomment-632063558",children:"performance issues"}),".\nYou'll need to enable the sensors plugin to use this widget, using: ",(0,r.jsx)(n.code,{children:"--enable-plugin sensors"})," when you start Glances."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/cpu-temp-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"options-73",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"units"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Use ",(0,r.jsx)(n.code,{children:"C"})," to display temperatures in Celsius or ",(0,r.jsx)(n.code,{children:"F"})," to use Fahrenheit. Defaults to ",(0,r.jsx)(n.code,{children:"C"}),"."]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-84",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-cpu-temp\n options:\n hostname: http://192.168.130.2:61208\n units: C\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"compact-metrics",children:"Compact Metrics"}),"\n",(0,r.jsx)(n.p,{children:"A multi-system overview widget that displays CPU, memory and disk usage for multiple Glances instances in a compact table. Click on a row to see detailed metrics for that system."}),"\n",(0,r.jsx)(n.h4,{id:"options-74",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"systems"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["An array of systems to monitor, each with ",(0,r.jsx)(n.code,{children:"header"})," (display name) and ",(0,r.jsx)(n.code,{children:"url"})," (Glances base URL)"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiVersion"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Glances API version. Defaults to ",(0,r.jsx)(n.code,{children:"4"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If Glances is password-protected, specify the username"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If Glances is password-protected, specify the password"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-85",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-compact-metrics\n options:\n systems:\n - header: Server 1\n url: http://192.168.1.10:61208\n - header: Server 2\n url: http://192.168.1.11:61208\n - header: NAS\n url: http://192.168.1.20:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"dynamic-widgets",children:"Dynamic Widgets"}),"\n",(0,r.jsx)(n.h3,{id:"iframe-widget",children:"Iframe Widget"}),"\n",(0,r.jsx)(n.p,{children:"Embed any webpage into your dashboard as a widget."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/iframe"})}),"\n",(0,r.jsx)(n.h4,{id:"options-75",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to the webpage to embed"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"frameHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If needed, specify height of iframe in ",(0,r.jsx)(n.code,{children:"px"}),". E.g. ",(0,r.jsx)(n.code,{children:"400"}),", defaults to auto"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-86",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: iframe\n options:\n url: https://fiatleak.com/\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"html-embedded-widget",children:"HTML Embedded Widget"}),"\n",(0,r.jsx)(n.p,{children:"Many websites and apps provide their own embeddable widgets. These can be used with Dashy using the Embed widget, which lets you dynamically embed and HTML, CSS or JavaScript contents."}),"\n",(0,r.jsxs)(n.p,{children:["\u26a0\ufe0f ",(0,r.jsx)(n.strong,{children:"NOTE:"})," Use with extreme caution. Embedding a script from an untrustworthy source may have serious unintended consequences."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/html-embed"})}),"\n",(0,r.jsx)(n.h4,{id:"options-76",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"html"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"HTML contents to render in the widget"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"script"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Raw JavaScript code to execute (caution)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"scriptSrc"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"A URL to JavaScript content (caution)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"css"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Any stylings for widget contents"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-87",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'- type: embed\n options:\n scriptSrc: https://cdn.speedcheck.org/basic/scbjs.min.js\n html: |\n
\n
\n \n Speedcheck\n \n
\n
\n'})}),"\n",(0,r.jsx)(n.p,{children:"Or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'- type: embed\n options:\n css: \'.coinmarketcap-currency-widget { color: var(--widget-text-color); }\'\n html: \'
\'\n scriptSrc: \'https://files.coinmarketcap.com/static/widget/currency.js\'\n'})}),"\n",(0,r.jsx)(n.p,{children:"You can also use this widget to display an image, wither locally or from a remote origin."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: embed\n options:\n html: ''\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"api-response",children:"API Response"}),"\n",(0,r.jsxs)(n.p,{children:["Display data from any HTTP/JSON API endpoint. Fetch a URL, then declaratively map response fields to labelled, formatted values.\nThis was very much inspired by, and is broadly compatible with ",(0,r.jsx)(n.a,{href:"https://gethomepage.dev/widgets/services/customapi/",children:"Homepage's customapi widget"})]}),"\n",(0,r.jsx)(n.h4,{id:"options-77",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The full URL of the API endpoint to fetch"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"refreshInterval"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["How often to refetch, in milliseconds. Defaults to ",(0,r.jsx)(n.code,{children:"10000"})," (10s). If omitted, the widget-level ",(0,r.jsx)(n.code,{children:"updateInterval"})," (in seconds) is used instead"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Username for HTTP basic auth"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Password for HTTP basic auth"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"method"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["HTTP method, e.g. ",(0,r.jsx)(n.code,{children:"GET"})," (default) or ",(0,r.jsx)(n.code,{children:"POST"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"headers"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"An object of custom request headers"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"requestBody"})})}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"string"})," or ",(0,r.jsx)(n.code,{children:"object"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Request body for non-GET methods. Prefer an object. A raw string is JSON-encoded again, so don't pre-stringify"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"display"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Layout for the fields: ",(0,r.jsx)(n.code,{children:"block"})," (default, label and value on one row) or ",(0,r.jsx)(n.code,{children:"list"})," (value stacked under the label)"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"mappings"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"The fields to display from the response (see below). If omitted, the raw response root is shown"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["Each item in ",(0,r.jsx)(n.code,{children:"mappings"})," accepts:"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"field"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Dot-path to the value, e.g. ",(0,r.jsx)(n.code,{children:"path.to.key"})," or ",(0,r.jsx)(n.code,{children:"items.0.name"}),". Omit to use the response root (useful with ",(0,r.jsx)(n.code,{children:"size"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"label"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Label shown beside the value"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"format"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["One of ",(0,r.jsx)(n.code,{children:"text"})," (default), ",(0,r.jsx)(n.code,{children:"number"}),", ",(0,r.jsx)(n.code,{children:"percent"}),", ",(0,r.jsx)(n.code,{children:"date"}),", ",(0,r.jsx)(n.code,{children:"relativeDate"})," or ",(0,r.jsx)(n.code,{children:"size"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"locale"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Locale for ",(0,r.jsx)(n.code,{children:"number"}),"/",(0,r.jsx)(n.code,{children:"percent"}),"/",(0,r.jsx)(n.code,{children:"date"}),"/",(0,r.jsx)(n.code,{children:"relativeDate"}),", e.g. ",(0,r.jsx)(n.code,{children:"nl"}),". Defaults to the browser locale"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"dateStyle"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["For ",(0,r.jsx)(n.code,{children:"date"})," format. One of ",(0,r.jsx)(n.code,{children:"full"}),", ",(0,r.jsx)(n.code,{children:"long"})," (default), ",(0,r.jsx)(n.code,{children:"medium"}),", ",(0,r.jsx)(n.code,{children:"short"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"timeStyle"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["For ",(0,r.jsx)(n.code,{children:"date"})," format. One of ",(0,r.jsx)(n.code,{children:"full"}),", ",(0,r.jsx)(n.code,{children:"long"}),", ",(0,r.jsx)(n.code,{children:"medium"}),", ",(0,r.jsx)(n.code,{children:"short"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"style"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["For ",(0,r.jsx)(n.code,{children:"relativeDate"})," format. One of ",(0,r.jsx)(n.code,{children:"long"})," (default), ",(0,r.jsx)(n.code,{children:"short"}),", ",(0,r.jsx)(n.code,{children:"narrow"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"numeric"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["For ",(0,r.jsx)(n.code,{children:"relativeDate"})," format. One of ",(0,r.jsx)(n.code,{children:"always"})," (default) or ",(0,r.jsx)(n.code,{children:"auto"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"additionalField"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["A secondary value shown next to the main one. Accepts ",(0,r.jsx)(n.code,{children:"field"}),", ",(0,r.jsx)(n.code,{children:"format"})," (and its options), plus ",(0,r.jsx)(n.code,{children:"color"})," - one of ",(0,r.jsx)(n.code,{children:"theme"}),", ",(0,r.jsx)(n.code,{children:"adaptive"})," (action colour from the value's sign: positive green, negative red), ",(0,r.jsx)(n.code,{children:"black"})," or ",(0,r.jsx)(n.code,{children:"white"})]})]})]})]}),"\n",(0,r.jsx)(n.p,{children:"Notes:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"percent"})})," treats the value as an already-computed percentage, so ",(0,r.jsx)(n.code,{children:"42"})," is shown as ",(0,r.jsx)(n.code,{children:"42%"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"size"})})," returns the number of items in an array (or keys in an object) \u2014 pair it with an omitted ",(0,r.jsx)(n.code,{children:"field"})," to count the response root"]}),"\n",(0,r.jsxs)(n.li,{children:["For APIs that don't send CORS headers (most self-hosted services), set the widget-level ",(0,r.jsx)(n.code,{children:"useProxy: true"})," to route the request through Dashy's server-side proxy"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"example-88",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: customapi\n options:\n url: https://api.github.com/repos/lissy93/dashy\n refreshInterval: 60000\n mappings:\n - field: stargazers_count\n label: Stars\n format: number\n - field: open_issues_count\n label: Open Issues\n format: number\n - field: pushed_at\n label: Last Push\n format: relativeDate\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-70",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Depends on target API (use ",(0,r.jsx)(n.code,{children:"useProxy: true"})," if blocked)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional (basic auth or custom headers)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"prometheus-data",children:"Prometheus Data"}),"\n",(0,r.jsx)(n.p,{children:"Display data from any service with a Prometheus exporter."}),"\n",(0,r.jsx)(n.p,{children:"// Coming soon..."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"data-feed",children:"Data Feed"}),"\n",(0,r.jsxs)(n.p,{children:["Show live data from an RSS-enabled service. The only required parameter is ",(0,r.jsx)(n.code,{children:"rssUrl"}),", which is the URL to the ATOM feed. See ",(0,r.jsx)(n.a,{href:"#rss-feed",children:"RSS Widget"})," for full list of available options."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"700",src:"https://pixelflare.cc/alicia/dashy/data-feed"})}),"\n",(0,r.jsx)(n.h4,{id:"example-89",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: rss-feed\n options:\n rssUrl: https://notes.aliciasykes.com/feed\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"usage--customizations",children:"Usage & Customizations"}),"\n",(0,r.jsx)(n.h3,{id:"widget-usage-guide",children:"Widget Usage Guide"}),"\n",(0,r.jsx)(n.p,{children:"Like items, widgets are placed under sections. You may have one or more widgets per section."}),"\n",(0,r.jsx)(n.p,{children:"In your YAML config file, this will look something like:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"sections:\n- name: Today\n icon: far fa-calendar-day\n widgets:\n - type: clock\n options:\n format: en-GB\n - type: weather\n options:\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n city: London\n units: metric\n"})}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:'In this example, there is a single section, named "Today", using a Calendar icon from Font-Awesome. It has 2 widgets, a clock and the current weather.'}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"continuous-updates",children:"Continuous Updates"}),"\n",(0,r.jsxs)(n.p,{children:["By default, a widget which displays dynamic data from an external source, will only fetch results on page load. If you would like to keep data updated at all times, you can enable ",(0,r.jsx)(n.strong,{children:"Continuous Updates"}),". This is done by setting a time value in the ",(0,r.jsx)(n.code,{children:"updateInterval"})," field."]}),"\n",(0,r.jsxs)(n.p,{children:["The value of ",(0,r.jsx)(n.code,{children:"updateInterval"})," is optional, and is specified and seconds. It must be more than ",(0,r.jsx)(n.code,{children:"10"})," and less than ",(0,r.jsx)(n.code,{children:"7200"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"For example, the following widget displaying stats from Pi-Hole will update ever 20 seconds."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"widgets:\n- type: pi-hole-stats\n updateInterval: 20\n options:\n hostname: http://192.168.130.2\n"})}),"\n",(0,r.jsx)(n.p,{children:"Note that if you have many widgets, and set them to continuously update frequently, you will notice a hit to performance. A widget that relies on data from an external API, will also consume your usage quota faster, if set to keep updating."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"proxying-requests",children:"Proxying Requests"}),"\n",(0,r.jsx)(n.p,{children:"If a widget fails to make a data request, and the console shows a CORS error, this means the server is blocking client-side requests."}),"\n",(0,r.jsxs)(n.p,{children:["Dashy has a built-in CORS proxy (",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/services/cors-proxy.js",children:(0,r.jsx)(n.code,{children:"services/cors-proxy.js"})}),"), which will be used automatically by some widgets, or can be forced to use by other by setting the ",(0,r.jsx)(n.code,{children:"useProxy"})," option."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"widgets:\n- type: pi-hole-stats\n useProxy: true\n options:\n hostname: http://pi-hole.local\n"})}),"\n",(0,r.jsx)(n.p,{children:"Alternatively, and more securely, you can set the auth headers on your service to accept requests from Dashy. For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: https://location-of-dashy/\nVary: Origin\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"handling-secrets",children:"Handling Secrets"}),"\n",(0,r.jsxs)(n.p,{children:["Some widgets require you to pass potentially sensitive info such as API keys. The ",(0,r.jsx)(n.code,{children:"conf.yml"})," is not ideal for this, as it's stored in plaintext. Instead, for secrets you should use environment variables."]}),"\n",(0,r.jsxs)(n.p,{children:["In your widget options, set the value to the name of an environment variable starting with ",(0,r.jsx)(n.code,{children:"DASHY_"})," (or ",(0,r.jsx)(n.code,{children:"VITE_APP_"})," / ",(0,r.jsx)(n.code,{children:"VUE_APP_"})," for backwards compatibility). The Dashy server will substitute it with the matching ",(0,r.jsx)(n.code,{children:"process.env"})," value when proxying the request."]}),"\n",(0,r.jsxs)(n.p,{children:["The widget must be set to route through the Dashy server with ",(0,r.jsx)(n.code,{children:"useProxy: true"}),". Without this, the placeholder is sent directly to the upstream API and will fail. Substitution covers the request URL, headers and body, so any auth pattern (Bearer, Basic, query param, POST body) works."]}),"\n",(0,r.jsxs)(n.p,{children:["For more information about setting and managing your environment variables, see ",(0,r.jsx)(n.a,{href:"/docs/management#passing-in-environmental-variables",children:"Management Docs --\x3e Environmental Variables"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: weather\n useProxy: true\n options:\n apiKey: DASHY_WEATHER_TOKEN\n city: London\n units: metric\n hideDetails: true\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Then set ",(0,r.jsx)(n.code,{children:"DASHY_WEATHER_TOKEN='xxx'"})," in your container or local environment, and restart Dashy. To rotate the value, just update the env var and restart. No rebuild required."]}),"\n",(0,r.jsxs)(n.admonition,{type:"note",children:[(0,r.jsxs)(n.p,{children:["Only env vars starting with ",(0,r.jsx)(n.code,{children:"DASHY_"}),", ",(0,r.jsx)(n.code,{children:"VITE_APP_"})," or ",(0,r.jsx)(n.code,{children:"VUE_APP_"})," are eligible for substitution. Other server-side env vars are never exposed."]}),(0,r.jsxs)(n.p,{children:["If you build Dashy from source yourself, ",(0,r.jsx)(n.code,{children:"VITE_APP_*"})," and ",(0,r.jsx)(n.code,{children:"DASHY_*"})," vars set at build time are also baked into the bundle by Vite, which works without ",(0,r.jsx)(n.code,{children:"useProxy: true"}),"."]})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"setting-timeout",children:"Setting Timeout"}),"\n",(0,r.jsxs)(n.p,{children:["If the endpoint you are requesting data from is slow to respond, you may see a timeout error in the console. This can easily be fixed by specifying the ",(0,r.jsx)(n.code,{children:"timeout"})," property on the offending widget. This should be an integer value, in milliseconds. By default timeout is ",(0,r.jsx)(n.code,{children:"2500"})," ms (2\xbd seconds)."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-current-cpu\n timeout: 8000\n options:\n hostname: https://glances.dns-device.local\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"adding-labels",children:"Adding Labels"}),"\n",(0,r.jsxs)(n.p,{children:["If you have multiple widgets of the same type in a single section, it may not be clear what each one is. To overcome this, you can add a custom label to any given widget, using the ",(0,r.jsx)(n.code,{children:"label"})," property."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- name: CPU Usage\n icon: fas fa-tachometer\n widgets:\n - type: gl-current-cpu\n label: Meida Server\n options:\n hostname: http://media-server.lan:61208\n - type: gl-current-cpu\n label: Firewall\n options:\n hostname: http://firewall.lan:61208\n - type: gl-current-cpu\n label: File Sync Server\n options:\n hostname: http://file-sync.lan:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"ignoring-errors",children:"Ignoring Errors"}),"\n",(0,r.jsx)(n.p,{children:"When there's an error fetching or displaying a widgets data, then it will be highlighted in yellow, and a message displayed on the UI."}),"\n",(0,r.jsxs)(n.p,{children:["In some instances, this is a false positive, and the widget is actually functioning correctly. If this is the case, you can disable the UI error message of a given widget by setting: ",(0,r.jsx)(n.code,{children:"ignoreErrors: true"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-disk-io\n ignoreErrors: true\n options:\n hostname: https://glances.dns-device.local\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"widget-styling",children:"Widget Styling"}),"\n",(0,r.jsx)(n.p,{children:"Like elsewhere in Dashy, all colours can be easily modified with CSS variables."}),"\n",(0,r.jsx)(n.p,{children:"Widgets use the following color variables, which can be overridden if desired:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"--widget-text-color"})," - Text color, defaults to ",(0,r.jsx)(n.code,{children:"--primary"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"--widget-background-color"})," - Background color, defaults to ",(0,r.jsx)(n.code,{children:"--background-darker"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"--widget-accent-color"})," - Accent color, defaults to ",(0,r.jsx)(n.code,{children:"--background"})]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["For more info on how to apply custom variables, see the ",(0,r.jsx)(n.a,{href:"/docs/theming#setting-custom-css-in-the-ui",children:"Theming Docs"})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"customizing-charts",children:"Customizing Charts"}),"\n",(0,r.jsxs)(n.p,{children:["For widgets that contain charts, you can set an array of colors under ",(0,r.jsx)(n.code,{children:"chartColors"}),".\nTo specify the chart height, set ",(0,r.jsx)(n.code,{children:"chartHeight"})," to an integer (in ",(0,r.jsx)(n.code,{children:"px"}),"), defaults to ",(0,r.jsx)(n.code,{children:"300"}),".\nFor example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-load-history\n options:\n hostname: http://192.168.130.2:61208\n chartColors: ['#9b5de5', '#f15bb5', '#00bbf9', '#00f5d4']\n chartHeight: 450\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"language-translations",children:"Language Translations"}),"\n",(0,r.jsx)(n.p,{children:"Since most of the content displayed within widgets is fetched from an external API, unless that API supports multiple languages, translating dynamic content is not possible."}),"\n",(0,r.jsx)(n.p,{children:"However, any hard-coded content is translatable, and all dates and times will display in your local format."}),"\n",(0,r.jsxs)(n.p,{children:["For more info about multi-language support, see the ",(0,r.jsx)(n.a,{href:"/docs/multi-language-support",children:"Internationalization Docs"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"widget-ui-options",children:"Widget UI Options"}),"\n",(0,r.jsx)(n.p,{children:"Widgets can be opened in full-page view, by clicking the Arrow icon (top-right). The URL in your address bar will also update, and visiting that web address directly will take you straight to that widget."}),"\n",(0,r.jsx)(n.p,{children:"You can reload the data of any widget, by clicking the Refresh Data icon (also in top-right). This will only affect the widget where the action was triggered from."}),"\n",(0,r.jsxs)(n.p,{children:["All ",(0,r.jsx)(n.a,{href:"/docs/configuring#section",children:"config options"})," that can be applied to sections, can also be applied to widget sections. For example, to make a widget section double the width, set ",(0,r.jsx)(n.code,{children:"displayData.cols: 2"})," within the parent section. You can collapse a widget (by clicking the section title), and collapse state will be saved locally."]}),"\n",(0,r.jsxs)(n.p,{children:["Widgets cannot currently be edited through the UI. This feature is in development, and will be released soon. In the meantime, you can either use the JSON config editor, or use ",(0,r.jsx)(n.a,{href:"https://github.com/coder/code-server",children:"VS Code Server"}),", or just SSH into your box and edit the conf.yml file directly."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"build-your-own-widget",children:"Build your own Widget"}),"\n",(0,r.jsx)(n.p,{children:"Widgets are built in a modular fashion, making it easy for anyone to create their own custom components."}),"\n",(0,r.jsxs)(n.p,{children:["For a full tutorial on creating your own widget, you can follow ",(0,r.jsx)(n.a,{href:"/docs/development-guides#building-a-widget",children:"this guide"}),", or take a look at ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/commit/3da76ce2999f57f76a97454c0276301e39957b8e",children:"here"})," for a code example."]}),"\n",(0,r.jsxs)(n.p,{children:["Alternatively, for displaying simple data, you could also just use the either the ",(0,r.jsx)(n.a,{href:"#iframe-widget",children:"iframe"}),", ",(0,r.jsx)(n.a,{href:"#html-embedded-widget",children:"embed"}),", ",(0,r.jsx)(n.a,{href:"#data-feed",children:"data feed"})," or ",(0,r.jsx)(n.a,{href:"#api-response",children:"API response"})," widgets."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"requesting-a-widget",children:"Requesting a Widget"}),"\n",(0,r.jsx)(n.p,{children:"Suggestions for widget ideas are welcome. But there is no guarantee that I will build your widget idea."}),"\n",(0,r.jsx)(n.p,{children:"Please only request widgets for services that:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Have a publicly accessible API"}),"\n",(0,r.jsx)(n.li,{children:"Are CORS and HTTPS enabled"}),"\n",(0,r.jsx)(n.li,{children:"Are free to use, or have a free plan"}),"\n",(0,r.jsx)(n.li,{children:"Allow for use in their Terms of Service"}),"\n",(0,r.jsx)(n.li,{children:"Would be useful for other users"}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["You can suggest a widget ",(0,r.jsx)(n.a,{href:"https://git.io/Jygo3",children:"here"}),", please star the repo before submitting a ticket. If you are a monthly GitHub sponsor, I will happily build out a custom widget for any service that meets the above criteria, usually within 2 weeks of initial request."]}),"\n",(0,r.jsxs)(n.p,{children:["For services that are not officially supported, it is likely still possible to display data using either the ",(0,r.jsx)(n.a,{href:"#iframe-widget",children:"iframe"}),", ",(0,r.jsx)(n.a,{href:"#html-embedded-widget",children:"embed"})," or ",(0,r.jsx)(n.a,{href:"#api-response",children:"API response"})," widgets. For more advanced features, like charts and action buttons, you could also build your own widget, using ",(0,r.jsx)(n.a,{href:"/docs/development-guides#building-a-widget",children:"this tutorial"}),", it's fairly straight forward, and you can use an ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/src/components/Widgets",children:"existing widget"})," (or ",(0,r.jsx)(n.a,{href:"https://git.io/JygKI",children:"this example"}),") as a template."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"troubleshooting-widget-errors",children:"Troubleshooting Widget Errors"}),"\n",(0,r.jsxs)(n.p,{children:["If an error occurs when fetching or rendering results, you will see a short message in the UI. If that message doesn't adequately explain the problem, then you can ",(0,r.jsx)(n.a,{href:"/docs/troubleshooting#how-to-open-browser-console",children:"open the browser console"})," to see more details."]}),"\n",(0,r.jsx)(n.p,{children:"Before proceeding, ensure that if the widget requires auth your API is correct, and for custom widgets, double check that the URL and protocol is correct."}),"\n",(0,r.jsx)(n.p,{children:"If you're able to, you can find more information about why the request may be failing in the Dev Tools under the Network tab, and you can ensure your endpoint is correct and working using a tool like Postman."}),"\n",(0,r.jsx)(n.h4,{id:"cors-errors",children:"CORS Errors"}),"\n",(0,r.jsxs)(n.p,{children:["The most common issue is a CORS error. This is a browser security mechanism which prevents the client-side app (Dashy) from from accessing resources on a remote origin, without that server's explicit permission (e.g. with headers like Access-Control-Allow-Origin). See the MDN Docs for more info: ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"Cross-Origin Resource Sharing"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"There are several ways to fix a CORS error:"}),"\n",(0,r.jsx)(n.h4,{id:"option-1---ensure-correct-protocol",children:"Option 1 - Ensure Correct Protocol"}),"\n",(0,r.jsx)(n.p,{children:"You will get a CORS error if you try and access a http service from a https source. So ensure that the URL you are requesting has the right protocol, and is correctly formatted."}),"\n",(0,r.jsx)(n.h4,{id:"option-2---set-headers",children:"Option 2 - Set Headers"}),"\n",(0,r.jsxs)(n.p,{children:["If you have control over the destination (e.g. for a self-hosted service), then you can simply apply the correct headers.\nAdd the ",(0,r.jsx)(n.code,{children:"Access-Control-Allow-Origin"})," header, with the value of either ",(0,r.jsx)(n.code,{children:"*"})," to allow requests from anywhere, or more securely, the host of where Dashy is served from. For example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: https://url-of-dashy.local\n"})}),"\n",(0,r.jsx)(n.p,{children:"or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: *\n"})}),"\n",(0,r.jsx)(n.h4,{id:"option-3---proxying-request",children:"Option 3 - Proxying Request"}),"\n",(0,r.jsxs)(n.p,{children:["You can route requests through Dashy's built-in CORS proxy. Instructions and more details can be found ",(0,r.jsx)(n.a,{href:"#proxying-requests",children:"here"}),". If you don't have control over the target origin, and you are running Dashy either through Docker, with the Node server or on Netlify, then this solution will work for you."]}),"\n",(0,r.jsxs)(n.p,{children:["Just add the ",(0,r.jsx)(n.code,{children:"useProxy: true"})," option to the failing widget."]}),"\n",(0,r.jsx)(n.h4,{id:"option-4---use-a-plugin",children:"Option 4 - Use a plugin"}),"\n",(0,r.jsxs)(n.p,{children:["For testing purposes, you can use an addon, which will disable the CORS checks. You can get the Allow-CORS extension for ",(0,r.jsx)(n.a,{href:"https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?hl=en-US",children:"Chrome"})," or ",(0,r.jsx)(n.a,{href:"https://addons.mozilla.org/en-US/firefox/addon/access-control-allow-origin/",children:"Firefox"}),", more details ",(0,r.jsx)(n.a,{href:"https://mybrowseraddon.com/access-control-allow-origin.html",children:"here"})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"raising-an-issue",children:"Raising an Issue"}),"\n",(0,r.jsxs)(n.p,{children:["If you need to submit a bug report for a failing widget, then please include the full console output (see ",(0,r.jsx)(n.a,{href:"/docs/troubleshooting#how-to-open-browser-console",children:"how"}),") as well as the relevant parts of your config file. Before sending the request, ensure you've read the docs. If you're new to GitHub, an haven't previously contributed to the project, then please fist star the repo to avoid your ticket being closed by the anti-spam bot."]})]})}function x(e={}){const{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(o,{...e})}):o(e)}},28453(e,n,s){s.d(n,{R:()=>l,x:()=>t});var i=s(96540);const r={},d=i.createContext(r);function l(e){const n=i.useContext(d);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),i.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/9be9f402.fb229c7e.js b/assets/js/9be9f402.fb229c7e.js new file mode 100644 index 00000000..176f5189 --- /dev/null +++ b/assets/js/9be9f402.fb229c7e.js @@ -0,0 +1 @@ +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[5552],{38803(e,n,s){s.r(n),s.d(n,{assets:()=>c,contentTitle:()=>t,default:()=>x,frontMatter:()=>l,metadata:()=>i,toc:()=>h});const i=JSON.parse('{"id":"widgets","title":"Widgets","description":"Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets available out-of-the-box as well as support for custom widgets to display stats from almost any service with an API.","source":"@site/docs/widgets.md","sourceDirName":".","slug":"/widgets","permalink":"/docs/widgets","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/widgets.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Icons","permalink":"/docs/icons"},"next":{"title":"Theming","permalink":"/docs/theming"}}');var r=s(74848),d=s(28453);const l={},t="Widgets",c={},h=[{value:"Contents",id:"contents",level:2},{value:"General Widgets",id:"general-widgets",level:2},{value:"Clock",id:"clock",level:3},{value:"Options",id:"options",level:4},{value:"Example",id:"example",level:4},{value:"Info",id:"info",level:4},{value:"Weather",id:"weather",level:3},{value:"Options",id:"options-1",level:4},{value:"Example",id:"example-1",level:4},{value:"Info",id:"info-1",level:4},{value:"Weather Forecast",id:"weather-forecast",level:3},{value:"Options",id:"options-2",level:4},{value:"Example",id:"example-2",level:4},{value:"Info",id:"info-2",level:4},{value:"RSS Feed",id:"rss-feed",level:3},{value:"Options",id:"options-3",level:4},{value:"Example",id:"example-3",level:4},{value:"Info",id:"info-3",level:4},{value:"Image",id:"image",level:3},{value:"Options",id:"options-4",level:4},{value:"Example",id:"example-4",level:4},{value:"Info",id:"info-4",level:4},{value:"Public IP",id:"public-ip",level:3},{value:"Options",id:"options-5",level:4},{value:"Example",id:"example-5",level:4},{value:"Supported providers",id:"supported-providers",level:4},{value:"Info",id:"info-5",level:4},{value:"IP Blacklist",id:"ip-blacklist",level:3},{value:"Options",id:"options-6",level:4},{value:"Example",id:"example-6",level:4},{value:"Info",id:"info-6",level:4},{value:"Domain Monitor",id:"domain-monitor",level:3},{value:"Options",id:"options-7",level:4},{value:"Example",id:"example-7",level:4},{value:"Info",id:"info-7",level:4},{value:"Crypto Watch List",id:"crypto-watch-list",level:3},{value:"Options",id:"options-8",level:4},{value:"Example",id:"example-8",level:4},{value:"Info",id:"info-8",level:4},{value:"Crypto Token Price History",id:"crypto-token-price-history",level:3},{value:"Options",id:"options-9",level:4},{value:"Example",id:"example-9",level:4},{value:"Info",id:"info-9",level:4},{value:"Wallet Balance",id:"wallet-balance",level:3},{value:"Options",id:"options-10",level:4},{value:"Example",id:"example-10",level:4},{value:"Info",id:"info-10",level:4},{value:"Code Stats",id:"code-stats",level:3},{value:"Options",id:"options-11",level:4},{value:"Example",id:"example-11",level:4},{value:"Info",id:"info-11",level:4},{value:"Mullvad Status",id:"mullvad-status",level:3},{value:"Options",id:"options-12",level:4},{value:"Example",id:"example-12",level:4},{value:"Info",id:"info-12",level:4},{value:"addy.io",id:"addyio",level:3},{value:"Options",id:"options-13",level:4},{value:"Example",id:"example-13",level:4},{value:"Info",id:"info-13",level:4},{value:"Vulnerability Feed",id:"vulnerability-feed",level:3},{value:"Options",id:"options-14",level:4},{value:"Example",id:"example-14",level:4},{value:"Info",id:"info-14",level:4},{value:"Exchange Rates",id:"exchange-rates",level:3},{value:"Options",id:"options-15",level:4},{value:"Example",id:"example-15",level:4},{value:"Info",id:"info-15",level:4},{value:"Public Holidays",id:"public-holidays",level:3},{value:"Options",id:"options-16",level:4},{value:"Example",id:"example-16",level:4},{value:"Info",id:"info-16",level:4},{value:"Covid-19 Status",id:"covid-19-status",level:3},{value:"Options",id:"options-17",level:4},{value:"Example",id:"example-17",level:4},{value:"Info",id:"info-17",level:4},{value:"Sports Scores",id:"sports-scores",level:3},{value:"Options",id:"options-18",level:4},{value:"Example",id:"example-18",level:4},{value:"Info",id:"info-18",level:4},{value:"News Headlines",id:"news-headlines",level:3},{value:"Options",id:"options-19",level:4},{value:"Example",id:"example-19",level:4},{value:"Info",id:"info-19",level:4},{value:"TFL Status",id:"tfl-status",level:3},{value:"Options",id:"options-20",level:4},{value:"Example",id:"example-20",level:4},{value:"Info",id:"info-20",level:4},{value:"Stock Price History",id:"stock-price-history",level:3},{value:"Options",id:"options-21",level:4},{value:"Example",id:"example-21",level:4},{value:"Info",id:"info-21",level:4},{value:"ETH Gas Prices",id:"eth-gas-prices",level:3},{value:"Options",id:"options-22",level:4},{value:"Example",id:"example-22",level:4},{value:"Info",id:"info-22",level:4},{value:"Joke",id:"joke",level:3},{value:"Options",id:"options-23",level:4},{value:"Example",id:"example-23",level:4},{value:"Info",id:"info-23",level:4},{value:"Chuck Norris quotes",id:"chuck-norris-quotes",level:3},{value:"Options",id:"options-24",level:4},{value:"Example",id:"example-24",level:4},{value:"Info",id:"info-24",level:4},{value:"XKCD Comics",id:"xkcd-comics",level:3},{value:"Options",id:"options-25",level:4},{value:"Example",id:"example-25",level:4},{value:"Info",id:"info-25",level:4},{value:"Flight Data",id:"flight-data",level:3},{value:"Options",id:"options-26",level:4},{value:"Example",id:"example-26",level:4},{value:"Info",id:"info-26",level:4},{value:"Astronomy Picture of the Day",id:"astronomy-picture-of-the-day",level:3},{value:"Options",id:"options-27",level:4},{value:"Example",id:"example-27",level:4},{value:"Info",id:"info-27",level:4},{value:"GitHub Trending",id:"github-trending",level:3},{value:"Options",id:"options-28",level:4},{value:"Example",id:"example-28",level:4},{value:"Info",id:"info-28",level:4},{value:"GitHub Profile Stats",id:"github-profile-stats",level:3},{value:"Options",id:"options-29",level:4},{value:"Example",id:"example-29",level:4},{value:"Info",id:"info-29",level:4},{value:"HealthChecks Status",id:"healthchecks-status",level:3},{value:"Options",id:"options-30",level:4},{value:"Example",id:"example-30",level:5},{value:"Info",id:"info-30",level:4},{value:"Hackernews Trending",id:"hackernews-trending",level:3},{value:"Options",id:"options-31",level:4},{value:"Example",id:"example-31",level:5},{value:"MVG Departure",id:"mvg-departure",level:3},{value:"Options",id:"options-32",level:4},{value:"Info",id:"info-31",level:4},{value:"MVG Connection",id:"mvg-connection",level:3},{value:"Options",id:"options-33",level:4},{value:"Info",id:"info-32",level:4},{value:"Custom List",id:"custom-list",level:3},{value:"Options",id:"options-34",level:4},{value:"Json Schema",id:"json-schema",level:4},{value:"Notes",id:"notes",level:4},{value:"Example",id:"example-32",level:4},{value:"Info",id:"info-33",level:4},{value:"Custom search",id:"custom-search",level:3},{value:"Options",id:"options-35",level:4},{value:"Notes",id:"notes-1",level:4},{value:"Example",id:"example-33",level:4},{value:"Info",id:"info-34",level:4},{value:"RescueTime Overview",id:"rescuetime-overview",level:3},{value:"Options",id:"options-36",level:4},{value:"Example",id:"example-34",level:4},{value:"Info",id:"info-35",level:4},{value:"Minecraft Server",id:"minecraft-server",level:3},{value:"Options",id:"options-37",level:4},{value:"Example",id:"example-35",level:4},{value:"Info",id:"info-36",level:4},{value:"Self-Hosted Services Widgets",id:"self-hosted-services-widgets",level:2},{value:"System Info",id:"system-info",level:3},{value:"Options",id:"options-38",level:4},{value:"Example",id:"example-36",level:4},{value:"Info",id:"info-37",level:4},{value:"CPU History (NetData)",id:"cpu-history-netdata",level:3},{value:"Options",id:"options-39",level:4},{value:"Example",id:"example-37",level:4},{value:"Info",id:"info-38",level:4},{value:"Memory History (NetData)",id:"memory-history-netdata",level:3},{value:"Options",id:"options-40",level:4},{value:"Example",id:"example-38",level:4},{value:"Info",id:"info-39",level:4},{value:"Load History (NetData)",id:"load-history-netdata",level:3},{value:"Options",id:"options-41",level:4},{value:"Example",id:"example-39",level:4},{value:"Info",id:"info-40",level:4},{value:"Pi-Hole Stats",id:"pi-hole-stats",level:3},{value:"Options",id:"options-42",level:4},{value:"Example",id:"example-40",level:4},{value:"Info",id:"info-41",level:4},{value:"Pi-Hole Stats v6",id:"pi-hole-stats-v6",level:3},{value:"Options",id:"options-43",level:4},{value:"Example",id:"example-41",level:4},{value:"Info",id:"info-42",level:4},{value:"Pi-Hole Queries",id:"pi-hole-queries",level:3},{value:"Options",id:"options-44",level:4},{value:"Example",id:"example-42",level:4},{value:"Info",id:"info-43",level:4},{value:"Pi-Hole Queries v6",id:"pi-hole-queries-v6",level:3},{value:"Options",id:"options-45",level:4},{value:"Example",id:"example-43",level:4},{value:"Info",id:"info-44",level:4},{value:"Pi-Hole Recent Traffic",id:"pi-hole-recent-traffic",level:3},{value:"Options",id:"options-46",level:4},{value:"Example",id:"example-44",level:4},{value:"Info",id:"info-45",level:4},{value:"Pi-Hole Recent Traffic v6",id:"pi-hole-recent-traffic-v6",level:3},{value:"Options",id:"options-47",level:4},{value:"Example",id:"example-45",level:4},{value:"Info",id:"info-46",level:4},{value:"Stat Ping Statuses",id:"stat-ping-statuses",level:3},{value:"Options",id:"options-48",level:4},{value:"Example",id:"example-46",level:4},{value:"Info",id:"info-47",level:4},{value:"Synology Download Station",id:"synology-download-station",level:3},{value:"Options",id:"options-49",level:4},{value:"Example",id:"example-47",level:4},{value:"Info",id:"info-48",level:4},{value:"AdGuard Home Block Stats",id:"adguard-home-block-stats",level:3},{value:"Options",id:"options-50",level:4},{value:"Example",id:"example-48",level:4},{value:"Info",id:"info-49",level:4},{value:"AdGuard Home Filters",id:"adguard-home-filters",level:3},{value:"Options",id:"options-51",level:4},{value:"Example",id:"example-49",level:4},{value:"Info",id:"info-50",level:4},{value:"AdGuard Home DNS Info",id:"adguard-home-dns-info",level:3},{value:"Options",id:"options-52",level:4},{value:"Example",id:"example-50",level:4},{value:"Info",id:"info-51",level:4},{value:"AdGuard Home Top Domains",id:"adguard-home-top-domains",level:3},{value:"Options",id:"options-53",level:4},{value:"Example",id:"example-51",level:4},{value:"Info",id:"info-52",level:4},{value:"Nextcloud User",id:"nextcloud-user",level:3},{value:"Options",id:"options-54",level:4},{value:"Example",id:"example-52",level:4},{value:"Info",id:"info-53",level:4},{value:"Nextcloud User Statuses",id:"nextcloud-user-statuses",level:3},{value:"Options",id:"options-55",level:4},{value:"Example",id:"example-53",level:4},{value:"Info",id:"info-54",level:4},{value:"Nextcloud Notifications",id:"nextcloud-notifications",level:3},{value:"Options",id:"options-56",level:4},{value:"Example",id:"example-54",level:4},{value:"Info",id:"info-55",level:4},{value:"Nextcloud System",id:"nextcloud-system",level:3},{value:"Options",id:"options-57",level:4},{value:"Example",id:"example-55",level:4},{value:"Info",id:"info-56",level:4},{value:"Nextcloud Stats",id:"nextcloud-stats",level:3},{value:"Options",id:"options-58",level:4},{value:"Example",id:"example-56",level:4},{value:"Info",id:"info-57",level:4},{value:"Nextcloud PHP OPcache Stats",id:"nextcloud-php-opcache-stats",level:3},{value:"Options",id:"options-59",level:4},{value:"Example",id:"example-57",level:4},{value:"Info",id:"info-58",level:4},{value:"ntfy stream",id:"ntfy-stream",level:3},{value:"Options",id:"options-60",level:4},{value:"Example",id:"example-58",level:4},{value:"Info",id:"info-59",level:4},{value:"Proxmox lists",id:"proxmox-lists",level:3},{value:"Options",id:"options-61",level:4},{value:"Example",id:"example-59",level:4},{value:"Info",id:"info-60",level:4},{value:"Troubleshooting",id:"troubleshooting",level:4},{value:"Sabnzbd",id:"sabnzbd",level:3},{value:"Options",id:"options-62",level:4},{value:"Example",id:"example-60",level:4},{value:"Info",id:"info-61",level:4},{value:"Gluetun VPN Info",id:"gluetun-vpn-info",level:3},{value:"Options",id:"options-63",level:4},{value:"Example",id:"example-61",level:4},{value:"Info",id:"info-62",level:4},{value:"Drone CI Builds",id:"drone-ci-builds",level:3},{value:"Options",id:"options-64",level:4},{value:"Example",id:"example-62",level:4},{value:"Info",id:"info-63",level:4},{value:"Filebrowser",id:"filebrowser",level:3},{value:"Options",id:"options-65",level:4},{value:"Example",id:"example-63",level:4},{value:"Widget Sections",id:"widget-sections",level:4},{value:"Info",id:"info-64",level:4},{value:"Linkding",id:"linkding",level:3},{value:"Options",id:"options-66",level:4},{value:"Example",id:"example-64",level:4},{value:"Info",id:"info-65",level:4},{value:"Uptime Kuma",id:"uptime-kuma",level:3},{value:"Options",id:"options-67",level:4},{value:"Example",id:"example-65",level:4},{value:"Info",id:"info-66",level:4},{value:"Uptime Kuma Status Page",id:"uptime-kuma-status-page",level:3},{value:"Options",id:"options-68",level:4},{value:"Example",id:"example-66",level:4},{value:"Info",id:"info-67",level:4},{value:"Tactical RMM",id:"tactical-rmm",level:3},{value:"Options",id:"options-69",level:4},{value:"Example",id:"example-67",level:4},{value:"Info",id:"info-68",level:4},{value:"System Resource Monitoring",id:"system-resource-monitoring",level:2},{value:"Glances",id:"glances",level:3},{value:"Options",id:"options-70",level:4},{value:"Info",id:"info-69",level:4},{value:"Screenshot",id:"screenshot",level:4},{value:"Current CPU Usage",id:"current-cpu-usage",level:3},{value:"Example",id:"example-68",level:4},{value:"Current CPU Usage Speedometer",id:"current-cpu-usage-speedometer",level:3},{value:"Example",id:"example-69",level:4},{value:"CPU Usage Per Core",id:"cpu-usage-per-core",level:3},{value:"Example",id:"example-70",level:4},{value:"CPU Usage History",id:"cpu-usage-history",level:3},{value:"Options",id:"options-71",level:4},{value:"Example",id:"example-71",level:4},{value:"Current Memory Usage",id:"current-memory-usage",level:3},{value:"Example",id:"example-72",level:4},{value:"Current Memory Usage Speedometer",id:"current-memory-usage-speedometer",level:3},{value:"Example",id:"example-73",level:4},{value:"Memory Usage History",id:"memory-usage-history",level:3},{value:"Options",id:"options-72",level:4},{value:"Example",id:"example-74",level:4},{value:"Disk Space",id:"disk-space",level:3},{value:"Example",id:"example-75",level:4},{value:"Disk IO",id:"disk-io",level:3},{value:"Example",id:"example-76",level:4},{value:"System Load",id:"system-load",level:3},{value:"Example",id:"example-77",level:4},{value:"Uptime",id:"uptime",level:3},{value:"Example",id:"example-78",level:4},{value:"System Load History",id:"system-load-history",level:3},{value:"Example",id:"example-79",level:4},{value:"Network Interfaces",id:"network-interfaces",level:3},{value:"Example",id:"example-80",level:4},{value:"Network Traffic",id:"network-traffic",level:3},{value:"Example",id:"example-81",level:4},{value:"Resource Usage Alerts",id:"resource-usage-alerts",level:3},{value:"Example",id:"example-82",level:4},{value:"IP Address",id:"ip-address",level:3},{value:"Example",id:"example-83",level:4},{value:"CPU Temp",id:"cpu-temp",level:3},{value:"Options",id:"options-73",level:4},{value:"Example",id:"example-84",level:4},{value:"GPU",id:"gpu",level:3},{value:"Options",id:"options-74",level:4},{value:"Example",id:"example-85",level:4},{value:"Compact Metrics",id:"compact-metrics",level:3},{value:"Options",id:"options-75",level:4},{value:"Example",id:"example-86",level:4},{value:"Dynamic Widgets",id:"dynamic-widgets",level:2},{value:"Iframe Widget",id:"iframe-widget",level:3},{value:"Options",id:"options-76",level:4},{value:"Example",id:"example-87",level:4},{value:"HTML Embedded Widget",id:"html-embedded-widget",level:3},{value:"Options",id:"options-77",level:4},{value:"Example",id:"example-88",level:4},{value:"API Response",id:"api-response",level:3},{value:"Options",id:"options-78",level:4},{value:"Example",id:"example-89",level:4},{value:"Info",id:"info-70",level:4},{value:"Prometheus Data",id:"prometheus-data",level:3},{value:"Data Feed",id:"data-feed",level:3},{value:"Example",id:"example-90",level:4},{value:"Usage & Customizations",id:"usage--customizations",level:2},{value:"Widget Usage Guide",id:"widget-usage-guide",level:3},{value:"Continuous Updates",id:"continuous-updates",level:3},{value:"Proxying Requests",id:"proxying-requests",level:3},{value:"Handling Secrets",id:"handling-secrets",level:3},{value:"Setting Timeout",id:"setting-timeout",level:3},{value:"Adding Labels",id:"adding-labels",level:3},{value:"Ignoring Errors",id:"ignoring-errors",level:3},{value:"Widget Styling",id:"widget-styling",level:3},{value:"Customizing Charts",id:"customizing-charts",level:3},{value:"Language Translations",id:"language-translations",level:3},{value:"Widget UI Options",id:"widget-ui-options",level:3},{value:"Build your own Widget",id:"build-your-own-widget",level:3},{value:"Requesting a Widget",id:"requesting-a-widget",level:3},{value:"Troubleshooting Widget Errors",id:"troubleshooting-widget-errors",level:3},{value:"CORS Errors",id:"cors-errors",level:4},{value:"Option 1 - Ensure Correct Protocol",id:"option-1---ensure-correct-protocol",level:4},{value:"Option 2 - Set Headers",id:"option-2---set-headers",level:4},{value:"Option 3 - Proxying Request",id:"option-3---proxying-request",level:4},{value:"Option 4 - Use a plugin",id:"option-4---use-a-plugin",level:4},{value:"Raising an Issue",id:"raising-an-issue",level:3}];function o(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",h5:"h5",header:"header",hr:"hr",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"widgets",children:"Widgets"})}),"\n",(0,r.jsx)(n.p,{children:"Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets available out-of-the-box as well as support for custom widgets to display stats from almost any service with an API."}),"\n",(0,r.jsx)(n.h2,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#general-widgets",children:"General Widgets"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#clock",children:"Clock"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#weather",children:"Weather"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#weather-forecast",children:"Weather Forecast"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#rss-feed",children:"RSS Feed"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#image",children:"Image"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#public-ip",children:"Public IP Address"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ip-blacklist",children:"IP Blacklist Checker"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#domain-monitor",children:"Domain Monitor"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#crypto-watch-list",children:"Crypto Watch List"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#crypto-token-price-history",children:"Crypto Price History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#wallet-balance",children:"Crypto Wallet Balance"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#code-stats",children:"Code Stats"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#mullvad-status",children:"Mullvad Status"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#addyio",children:"Email Aliases (addy.io)"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#vulnerability-feed",children:"Vulnerability Feed"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#exchange-rates",children:"Exchange Rates"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#public-holidays",children:"Public Holidays"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#covid-19-status",children:"Covid-19 Status"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sports-scores",children:"Sports Scores"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#news-headlines",children:"News Headlines"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#tfl-status",children:"TFL Status"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#stock-price-history",children:"Stock Price History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#eth-gas-prices",children:"ETH Gas Prices"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#joke",children:"Joke of the Day"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#chuck-norris-quotes",children:"Chuck Norris quotes"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#xkcd-comics",children:"XKCD Comics"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#flight-data",children:"Flight Data"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#astronomy-picture-of-the-day",children:"NASA APOD"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#github-trending",children:"GitHub Trending"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#github-profile-stats",children:"GitHub Profile Stats"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#healthchecks-status",children:"Healthchecks Status"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#hackernews-trending",children:"Hackernews Trending"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#mvg-departure",children:"Mvg Departure"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#mvg-connection",children:"Mvg Connection"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#custom-search",children:"Custom search"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#rescuetime-overview",children:"Rescuetime overview"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#minecraft-server",children:"Minecraft Server"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#self-hosted-services-widgets",children:"Self-Hosted Services Widgets"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#system-info",children:"System Info"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cpu-history-netdata",children:"CPU History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#memory-history-netdata",children:"Memory History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#load-history-netdata",children:"System Load History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-stats",children:"Pi-Hole Stats"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-stats-v6",children:"Pi-Hole Stats v6"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-queries",children:"Pi-Hole Queries"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-queries-v6",children:"Pi-Hole Queries v6"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-recent-traffic",children:"Pi-Hole Recent Traffic"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#pi-hole-recent-traffic-v6",children:"Pi-Hole Recent Traffic v6"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#stat-ping-statuses",children:"Stat Ping Statuses"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#synology-download-station",children:"Synology Download Station"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#adguard-home-block-stats",children:"AdGuard Home Block Stats"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#adguard-home-filters",children:"AdGuard Home Filters"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#adguard-home-dns-info",children:"AdGuard Home DNS Info"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#adguard-home-top-domains",children:"AdGuard Home Top Domains"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-user",children:"Nextcloud User"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-user-statuses",children:"Nextcloud User Statuses"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-notifications",children:"Nextcloud Notifications"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-system",children:"Nextcloud System"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-stats",children:"Nextcloud Stats"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#nextcloud-php-opcache-stats",children:"Nextcloud PHP OPcache"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ntfy-stream",children:"Ntfy stream"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#proxmox-lists",children:"Proxmox lists"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sabnzbd",children:"Sabnzbd"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#gluetun-vpn-info",children:"Gluetun VPN Info"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#drone-ci-builds",children:"Drone CI Build"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#filebrowser",children:"Filebrowser"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#linkding",children:"Linkding"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#uptime-kuma",children:"Uptime Kuma"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#uptime-kuma-status-page",children:"Uptime Kuma Status Page"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#tactical-rmm",children:"Tactical RMM"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#system-resource-monitoring",children:"System Resource Monitoring"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#current-cpu-usage",children:"CPU Usage Current"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cpu-usage-per-core",children:"CPU Usage Per Core"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cpu-usage-history",children:"CPU Usage History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#current-memory-usage",children:"Memory Usage Current"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#memory-usage-history",children:"Memory Usage History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#disk-space",children:"Disk Space"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#disk-io",children:"Disk IO"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#system-load",children:"System Load"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#system-load-history",children:"System Load History"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#network-interfaces",children:"Network Interfaces"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#network-traffic",children:"Network Traffic"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#resource-usage-alerts",children:"Resource Usage Alerts"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ip-address",children:"Public & Private IP"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cpu-temp",children:"CPU Temperature"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#gpu",children:"GPU"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#compact-metrics",children:"Compact Metrics"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#dynamic-widgets",children:"Dynamic Widgets"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#iframe-widget",children:"Iframe Widget"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#html-embedded-widget",children:"HTML Embed Widget"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#api-response",children:"API Response"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#prometheus-data",children:"Prometheus Data"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#data-feed",children:"Data Feed"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"#usage--customizations",children:"Usage & Customizations"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-usage-guide",children:"Widget Usage Guide"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#continuous-updates",children:"Continuous Updates"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#proxying-requests",children:"Proxying Requests"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#handling-secrets",children:"Handling Secrets"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#setting-timeout",children:"Setting Timeout"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#adding-labels",children:"Adding Labels"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ignoring-errors",children:"Ignoring Errors"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-styling",children:"Custom CSS Styling"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#customizing-charts",children:"Customizing Charts"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#language-translations",children:"Language Translations"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-ui-options",children:"Widget UI Options"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#build-your-own-widget",children:"Building a Widget"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#requesting-a-widget",children:"Requesting a Widget"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#troubleshooting-widget-errors",children:"Troubleshooting"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"general-widgets",children:"General Widgets"}),"\n",(0,r.jsx)(n.h3,{id:"clock",children:"Clock"}),"\n",(0,r.jsx)(n.p,{children:"A simple, live-updating time and date widget with time-zone support. All fields are optional."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/clock"})}),"\n",(0,r.jsx)(n.h4,{id:"options",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"timeZone"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The time zone to display date and time in.",(0,r.jsx)(n.br,{})," Specified as Region/City, for example: ",(0,r.jsx)(n.code,{children:"Australia/Melbourne"}),". See the ",(0,r.jsx)(n.a,{href:"https://timezonedb.com/time-zones",children:"Time Zone DB"})," for a full list of supported TZs. Defaults to the browser / device's local time"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"format"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["A country code for displaying the date and time in local format.",(0,r.jsx)(n.br,{}),"Specified as ",(0,r.jsx)(n.code,{children:"[ISO-3166]-[ISO-639]"}),", for example: ",(0,r.jsx)(n.code,{children:"en-AU"}),". See ",(0,r.jsx)(n.a,{href:"https://www.fincher.org/Utilities/CountryLanguageList.shtml",children:"here"})," for a full list of locales. Defaults to the browser / device's region"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"customCityName"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"By default the city from the time-zone is shown, but setting this value will override that text"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideDate"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", the date and city will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideSeconds"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", seconds will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"use12Hour"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", 12 hour time will be displayed. Defaults to the settings suggested by the current ",(0,r.jsx)(n.code,{children:"format"})," and ",(0,r.jsx)(n.code,{children:"timeZone"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: clock\n options:\n timeZone: Europe/London\n format: en-GB\n hideDate: false\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info",children:"Info"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"No external data requests."})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"weather",children:"Weather"}),"\n",(0,r.jsx)(n.p,{children:"A simple, live-updating local weather component, showing temperature, conditions and more info."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/weather"})}),"\n",(0,r.jsx)(n.h4,{id:"options-1",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your OpenWeatherMap API key. You can get one for free at ",(0,r.jsx)(n.a,{href:"https://openweathermap.org/",children:"openweathermap.org"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"city"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A city name to use for fetching weather. This can also be a state code or country code, following the ISO-3166 format"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cityId"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["An OpenWeatherMap numeric city ID, used to disambiguate cities that share a name. You can find the ID in the URL of the city's page on ",(0,r.jsx)(n.a,{href:"https://openweathermap.org/",children:"openweathermap.org"})," (e.g. ",(0,r.jsx)(n.code,{children:"2643743"})," for London, GB). If provided, this will override the ",(0,r.jsx)(n.code,{children:"city"})," option"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"units"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The units to use for displaying data, can be either ",(0,r.jsx)(n.code,{children:"metric"})," or ",(0,r.jsx)(n.code,{children:"imperial"}),". Defaults to ",(0,r.jsx)(n.code,{children:"metric"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideDetails"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", the additional details (wind, humidity, pressure, etc) will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lat"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["To show weather for a specific location, you can provide the latitude and longitude coordinates. If provided, this will override the ",(0,r.jsx)(n.code,{children:"city"})," and ",(0,r.jsx)(n.code,{children:"cityId"})," options"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lon"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["To show weather for a specific location, you can provide the latitude and longitude coordinates. If provided, this will override the ",(0,r.jsx)(n.code,{children:"city"})," and ",(0,r.jsx)(n.code,{children:"cityId"})," options"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-1",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: weather\n options:\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n city: London\n units: metric\n hideDetails: true\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-1",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://openweather.co.uk/privacy-policy",children:"OWM Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"weather-forecast",children:"Weather Forecast"}),"\n",(0,r.jsxs)(n.p,{children:["Displays the weather (temperature and conditions) for the next few days for a given location. Note that this requires either the free ",(0,r.jsx)(n.a,{href:"https://home.openweathermap.org/students",children:"OpenWeatherMap Student Plan"}),", or the Premium Plan."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/weather-forecast"})}),"\n",(0,r.jsx)(n.h4,{id:"options-2",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your OpenWeatherMap API key. You can get one at ",(0,r.jsx)(n.a,{href:"https://openweathermap.org/",children:"openweathermap.org"})," or for free via the ",(0,r.jsx)(n.a,{href:"https://home.openweathermap.org/students",children:"OWM Student Plan"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"city"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A city name to use for fetching weather. This can also be a state code or country code, following the ISO-3166 format"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cityId"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["An OpenWeatherMap numeric city ID, used to disambiguate cities that share a name. You can find the ID in the URL of the city's page on ",(0,r.jsx)(n.a,{href:"https://openweathermap.org/",children:"openweathermap.org"})," (e.g. ",(0,r.jsx)(n.code,{children:"2643743"})," for London, GB). If provided, this will override the ",(0,r.jsx)(n.code,{children:"city"})," option"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lat"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Latitude for a specific location. If provided alongside ",(0,r.jsx)(n.code,{children:"lon"}),", this will override the ",(0,r.jsx)(n.code,{children:"city"})," and ",(0,r.jsx)(n.code,{children:"cityId"})," options"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lon"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Longitude for a specific location. If provided alongside ",(0,r.jsx)(n.code,{children:"lat"}),", this will override the ",(0,r.jsx)(n.code,{children:"city"})," and ",(0,r.jsx)(n.code,{children:"cityId"})," options"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"numDays"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of days to display of forecast info to display. Defaults to ",(0,r.jsx)(n.code,{children:"4"}),", max ",(0,r.jsx)(n.code,{children:"16"})," days"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"units"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The units to use for displaying data, can be either ",(0,r.jsx)(n.code,{children:"metric"})," or ",(0,r.jsx)(n.code,{children:"imperial"}),". Defaults to ",(0,r.jsx)(n.code,{children:"metric"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideDetails"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", the additional details (wind, humidity, pressure, etc) will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-2",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: weather-forecast\n options:\n city: California\n numDays: 6\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n units: imperial\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-2",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udd34 Premium (free for personal use only)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://openweather.co.uk/privacy-policy",children:"OWM Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"rss-feed",children:"RSS Feed"}),"\n",(0,r.jsx)(n.p,{children:"Display news and updates from any RSS-enabled service."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/rss"})}),"\n",(0,r.jsx)(n.h4,{id:"options-3",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"rssUrl"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL location of your RSS feed"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["An API key for ",(0,r.jsx)(n.a,{href:"https://rss2json.com/",children:"rss2json"}),". It's free, and will allow you to make 10,000 requests per day, you can sign up ",(0,r.jsx)(n.a,{href:"https://rss2json.com/sign-up",children:"here"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"The number of posts to return. If you haven't specified an API key, this will be limited to 10"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"orderBy"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["How results should be sorted. Can be either ",(0,r.jsx)(n.code,{children:"pubDate"}),", ",(0,r.jsx)(n.code,{children:"author"})," or ",(0,r.jsx)(n.code,{children:"title"}),". Defaults to ",(0,r.jsx)(n.code,{children:"pubDate"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"orderDirection"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Order direction of feed items to return. Can be either ",(0,r.jsx)(n.code,{children:"asc"})," or ",(0,r.jsx)(n.code,{children:"desc"}),". Defaults to ",(0,r.jsx)(n.code,{children:"desc"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"parseLocally"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If true parse the rss feed locally instead of using the rss2json API."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-3",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: rss-feed\n options:\n rssUrl: https://www.schneier.com/blog/atom.xml\n apiKey: xxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-3",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free Plan (up to 10,000 requests / day)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://rss2json.com/privacy-policy",children:"Rss2Json Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"image",children:"Image"}),"\n",(0,r.jsx)(n.p,{children:"Displays an image."}),"\n",(0,r.jsxs)(n.p,{children:["This may be useful if you have a service (such as Grafana - ",(0,r.jsx)(n.a,{href:"https://mattionline.de/grafana-api-export-graph-as-png/",children:"see example"}),"), which periodically exports charts or other data as an image."]}),"\n",(0,r.jsxs)(n.p,{children:["You can also store images within Dashy's public directory (using a Docker volume), and reference them directly. E.g. ",(0,r.jsx)(n.code,{children:"-v ./path/to/my-homelab-logo.png:/app/public/logo.png"}),", then in the widget ",(0,r.jsx)(n.code,{children:"imagePath: /logo.png"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Similarly, any web service that serves up widgets as image can be used. E.g. you could show current star chart for a GitHub repo, with: ",(0,r.jsx)(n.code,{children:"imagePath: https://starchart.cc/Lissy93/dashy.svg"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["If you'd like to embed a live screenshot, of all or just part of a website, then this can be done using ",(0,r.jsx)(n.a,{href:"https://apiflash.com/",children:"API Flash"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Or what about showing a photo of the day? Try ",(0,r.jsx)(n.code,{children:"https://source.unsplash.com/random/400x300"})," or ",(0,r.jsx)(n.code,{children:"https://picsum.photos/400/300"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"300",src:"https://pixelflare.cc/alicia/dashy/image"})}),"\n",(0,r.jsx)(n.h4,{id:"options-4",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"imagePath"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The path (local or remote) of the image to display"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"imageWidth"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify a fixed width for rendered image. Accepts either integer value in ",(0,r.jsx)(n.code,{children:"px"}),", or any string value with units (e.g. ",(0,r.jsx)(n.code,{children:"420"}),", ",(0,r.jsx)(n.code,{children:"100px"}),", ",(0,r.jsx)(n.code,{children:"6.9rem"}),") (defaults to ",(0,r.jsx)(n.code,{children:"auto"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"imageHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify a fixed height for rendered image. Accepts either integer value in ",(0,r.jsx)(n.code,{children:"px"}),", or any string value with units (e.g. ",(0,r.jsx)(n.code,{children:"420"}),", ",(0,r.jsx)(n.code,{children:"100px"}),", ",(0,r.jsx)(n.code,{children:"6.9rem"}),") (defaults to ",(0,r.jsx)(n.code,{children:"auto"}),")"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-4",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: image\n options:\n imagePath: https://i.ibb.co/yhbt6CY/dashy.png\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-4",children:"Info"}),"\n",(0,r.jsx)(n.p,{children:"Unless image fetched from remote source, no external data request is made."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"public-ip",children:"Public IP"}),"\n",(0,r.jsx)(n.p,{children:"Displays your public IP address, ISP and approximate location."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/public-ip"})}),"\n",(0,r.jsx)(n.h4,{id:"options-5",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"provider"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["One of ",(0,r.jsx)(n.code,{children:"ipinfo"})," ",(0,r.jsx)(n.em,{children:"(default)"}),", ",(0,r.jsx)(n.code,{children:"freeipapi"}),", ",(0,r.jsx)(n.code,{children:"ipquery"}),", ",(0,r.jsx)(n.code,{children:"ip-api"}),", or ",(0,r.jsx)(n.code,{children:"ipgeolocation"}),". See the table below"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Required for ",(0,r.jsx)(n.code,{children:"ipgeolocation"}),". Optional for ",(0,r.jsx)(n.code,{children:"ipinfo"})," (a ",(0,r.jsx)(n.a,{href:"https://ipinfo.io/signup",children:"free token"})," raises the rate limit from ~1k/day to ~50k/month)"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideLocation"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set to ",(0,r.jsx)(n.code,{children:"true"})," to hide the flag, ISP name and city/region \u2014 only the IP address is shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-5",children:"Example"}),"\n",(0,r.jsx)(n.p,{children:"Default (no options needed)"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: public-ip\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Using ",(0,r.jsx)(n.code,{children:"ip-api"})," via the proxy (gets server IP, because of ",(0,r.jsx)(n.code,{children:"useProxy"}),")"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: public-ip\n useProxy: true\n options:\n provider: ip-api\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Using ",(0,r.jsx)(n.code,{children:"ipgeolocation"})," with a key:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: public-ip\n options:\n provider: ipgeolocation\n apiKey: xxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["Setting ",(0,r.jsx)(n.code,{children:"useProxy: true"})," makes the lookup happen from Dashy's server instead of your browser, so the upstream API returns the public IP of the machine running Dashy, whereas without ",(0,r.jsx)(n.code,{children:"useProxy"})," it will show your public IP."]})}),"\n",(0,r.jsx)(n.h4,{id:"supported-providers",children:"Supported providers"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Provider"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Key"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Proxy"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Notes"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"ipinfo"})," ",(0,r.jsx)(n.em,{children:"(default)"})]}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed / \ud83d\udfe0 Optional"}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsxs)(n.td,{children:["Service from ",(0,r.jsx)(n.a,{href:"https://ipinfo.io/",children:"ipinfo.io"}),". Keyless gives ~1k/day; a ",(0,r.jsx)(n.a,{href:"https://ipinfo.io/signup",children:"free token"})," raises this to ~50k/month. Sometimes blocked by adblockers."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"freeipapi"})}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsxs)(n.td,{children:["Keyless, 60 req/min via ",(0,r.jsx)(n.a,{href:"https://freeipapi.com/",children:"freeipapi.com"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"ipquery"})}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsxs)(n.td,{children:["Modern keyless API from ",(0,r.jsx)(n.a,{href:"https://ipquery.io/",children:"ipquery.io"}),". Includes VPN/Tor/datacenter risk flags in the raw response."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"ip-api"})}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsxs)(n.td,{children:["\ud83d\udd34 ",(0,r.jsx)(n.strong,{children:"Required"})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.a,{href:"https://ip-api.com/",children:"ip-api.com"})," is HTTP-only on the free tier, so the proxy is needed to avoid mixed-content errors."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"ipgeolocation"})}),(0,r.jsxs)(n.td,{children:["\ud83d\udd34 ",(0,r.jsx)(n.strong,{children:"Required"})]}),(0,r.jsx)(n.td,{children:"\ud83d\udfe2 Not needed"}),(0,r.jsxs)(n.td,{children:["Get a free key from ",(0,r.jsx)(n.a,{href:"https://ipgeolocation.io/signup.html",children:"ipgeolocation.io"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"info-5",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled (only ",(0,r.jsx)(n.code,{children:"provider: ip-api"})," needs the proxy)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional (only ",(0,r.jsx)(n.code,{children:"ipgeolocation"})," requires a key, and ",(0,r.jsx)(n.code,{children:"ipinfo"})," is optional)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": See the policy for whichever provider you choose: ",(0,r.jsx)(n.a,{href:"https://freeipapi.com/",children:"freeipapi.com"}),", ",(0,r.jsx)(n.a,{href:"https://ipinfo.io/privacy-policy",children:"ipinfo.io"}),", ",(0,r.jsx)(n.a,{href:"https://ipquery.io/",children:"ipquery.io"}),", ",(0,r.jsx)(n.a,{href:"https://ip-api.com/docs/legal",children:"ip-api.com"}),", ",(0,r.jsx)(n.a,{href:"https://ipgeolocation.io/privacy.html",children:"IPGeoLocation"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"ip-blacklist",children:"IP Blacklist"}),"\n",(0,r.jsxs)(n.p,{children:["Notice certain web pages aren't loading? This widget quickly shows which blacklists your IP address (or host, or email) appears on, using data from ",(0,r.jsx)(n.a,{href:"https://blacklistchecker.com/",children:"blacklistchecker.com"}),"."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/ip-blacklist"})}),"\n",(0,r.jsx)(n.h4,{id:"options-6",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"ipAddress"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"The IP to check. This can also be a domain/ host name or even an email address. If left blank, Dashy will use your current public IP address."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["You can get your free API key from ",(0,r.jsx)(n.a,{href:"https://blacklistchecker.com/keys",children:"blacklistchecker.com"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-6",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: blacklist-check\n options:\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n ipAddress: 1.1.1.1\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-6",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free Plan"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://blacklistchecker.com/privacy",children:"BlacklistChecker Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"domain-monitor",children:"Domain Monitor"}),"\n",(0,r.jsxs)(n.p,{children:["Keep an eye on the expiry dates of your domain names, using public whois records fetched from ",(0,r.jsx)(n.a,{href:"https://whoapi.com/",children:"whoapi.com"}),". Click the domain name to view additional info, like registrar, name servers and date last updated."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/domain-monitor"})}),"\n",(0,r.jsx)(n.h4,{id:"options-7",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"domain"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The domain to check"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["You can get your free API key from ",(0,r.jsx)(n.a,{href:"https://my.whoapi.com/user/signup",children:"my.whoapi.com"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showFullInfo"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If set to true, the toggle-full-info panel will be open by default"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-7",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: domain-monitor\n options:\n domain: example.com\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n - type: domain-monitor\n options:\n domain: example2.com\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-7",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free Plan (10,000 requests)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://whoapi.com/privacy-policy/",children:"WhoAPI Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"crypto-watch-list",children:"Crypto Watch List"}),"\n",(0,r.jsxs)(n.p,{children:["Keep track of price changes of your favorite crypto assets. Data is fetched from ",(0,r.jsx)(n.a,{href:"https://www.coingecko.com/",children:"CoinGecko"}),". All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/crypto-prices"})}),"\n",(0,r.jsx)(n.h4,{id:"options-8",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"assets"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["An array of cryptocurrencies, coins and tokens. See ",(0,r.jsx)(n.a,{href:"https://api.coingecko.com/api/v3/asset_platforms",children:"list of supported assets"}),". If none are specified, then the top coins by ",(0,r.jsx)(n.code,{children:"sortBy"})," (defaults to market cap) will be returned"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"currency"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The fiat currency to display price in, expressed as an ISO-4217 alpha code (see ",(0,r.jsx)(n.a,{href:"https://www.iban.com/currency-codes",children:"list of currencies"}),"). Defaults to ",(0,r.jsx)(n.code,{children:"USD"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"sortBy"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The method of sorting results. Can be ",(0,r.jsx)(n.code,{children:"marketCap"}),", ",(0,r.jsx)(n.code,{children:"volume"})," or ",(0,r.jsx)(n.code,{children:"alphabetical"}),". Defaults to ",(0,r.jsx)(n.code,{children:"marketCap"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Number of results to return, useful when no assets are specified. Defaults to either ",(0,r.jsx)(n.code,{children:"all"})," or ",(0,r.jsx)(n.code,{children:"100"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-8",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: crypto-watch-list\n options:\n limit: 10\n"})}),"\n",(0,r.jsx)(n.p,{children:"Or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: crypto-watch-list\n options:\n currency: GBP\n sortBy: marketCap\n assets:\n - bitcoin\n - ethereum\n - monero\n - cosmos\n - polkadot\n - dogecoin\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-8",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.coingecko.com/en/privacy",children:"CoinGecko Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"crypto-token-price-history",children:"Crypto Token Price History"}),"\n",(0,r.jsxs)(n.p,{children:["Shows recent price history for a given crypto asset, using price data fetched from ",(0,r.jsx)(n.a,{href:"https://www.coingecko.com/",children:"CoinGecko"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/crypto-price-history"})}),"\n",(0,r.jsx)(n.h4,{id:"options-9",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"asset"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Name of a crypto asset, coin or token to fetch price data for, see ",(0,r.jsx)(n.a,{href:"https://api.coingecko.com/api/v3/asset_platforms",children:"list of supported assets"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"currency"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The fiat currency to display results in, expressed as an ISO-4217 alpha code (see ",(0,r.jsx)(n.a,{href:"https://www.iban.com/currency-codes",children:"list of currencies"}),"). Defaults to ",(0,r.jsx)(n.code,{children:"USD"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"numDays"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of days of price history to render. Defaults to ",(0,r.jsx)(n.code,{children:"7"}),", min: ",(0,r.jsx)(n.code,{children:"1"}),", max: ",(0,r.jsx)(n.code,{children:"30"})," days"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColor"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Color of the chart value. Defaults to ",(0,r.jsx)(n.code,{children:"--widget-text-color"})," which inherits dashboard primary color"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The height of rendered chart in px. Defaults to ",(0,r.jsx)(n.code,{children:"300"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-9",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: crypto-price-chart\n options:\n asset: bitcoin\n currency: GBP\n numDays: 7\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-9",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.coingecko.com/en/privacy",children:"CoinGecko Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"wallet-balance",children:"Wallet Balance"}),"\n",(0,r.jsxs)(n.p,{children:["Keep track of your crypto balances and see recent transactions. Data is fetched from ",(0,r.jsx)(n.a,{href:"https://www.blockcypher.com/dev/",children:"BlockCypher"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/crypto-wallet"})}),"\n",(0,r.jsx)(n.h4,{id:"options-10",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"coin"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Symbol of coin or asset, e.g. ",(0,r.jsx)(n.code,{children:"btc"}),", ",(0,r.jsx)(n.code,{children:"eth"})," or ",(0,r.jsx)(n.code,{children:"doge"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"address"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Address to monitor. This is your wallet's ",(0,r.jsx)(n.strong,{children:"public"})," / receiving address"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"network"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["To use a different network, other than mainnet. Defaults to ",(0,r.jsx)(n.code,{children:"main"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit the number of transactions to display. Defaults to ",(0,r.jsx)(n.code,{children:"10"}),", set to large number to show all"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-10",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: wallet-balance\n options:\n coin: btc\n address: 3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-10",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.blockcypher.com/privacy.html",children:"BlockCypher Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"code-stats",children:"Code Stats"}),"\n",(0,r.jsxs)(n.p,{children:["Display your coding summary. ",(0,r.jsx)(n.a,{href:"https://codestats.net/",children:"Code::Stats"})," is a free and open source app that aggregates statistics about your programming activity. Dashy supports both the public instance, as well as self-hosted versions."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/code-stats"})}),"\n",(0,r.jsx)(n.h4,{id:"options-11",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Your CodeStats username"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If your self-hosting CodeStats, then supply the host name. By default it will use the public hosted instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"monthsToShow"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify the number of months to render in the historical data chart. Defaults to ",(0,r.jsx)(n.code,{children:"6"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideMeta"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide the meta section (username, level, all-time and recent XP)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideHistory"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide the historical calendar heat map"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideLanguages"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide the programming languages pie chart"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideMachines"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide the machines percentage chart"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-11",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: code-stats\n options:\n username: alicia\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-11",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted or Managed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://codestats.net/tos#privacy",children:"Code::Stats Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"mullvad-status",children:"Mullvad Status"}),"\n",(0,r.jsxs)(n.p,{children:["Shows your Mullvad VPN connection status, as well as server info. Fetched from ",(0,r.jsx)(n.a,{href:"https://mullvad.net/en/check/",children:"am.i.mullvad.net"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/mullvad"})}),"\n",(0,r.jsx)(n.h4,{id:"options-12",children:"Options"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"No Options."})}),"\n",(0,r.jsx)(n.h4,{id:"example-12",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: mullvad-status\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-12",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://mullvad.net/en/help/privacy-policy/",children:"Mullvad Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"addyio",children:"addy.io"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://addy.io/",children:"addy.io"})," is a free and open source mail forwarding service. Use it to protect your real email address, by using a different alias for each of your online accounts, and have all emails land in your normal inbox(es). Supports custom domains, email replies, PGP-encryption, multiple recipients and more"]}),"\n",(0,r.jsx)(n.p,{children:"This widget display email addresses / aliases from addy.io. Click an email address to copy to clipboard, or use the toggle switch to enable/ disable it. Shows usage stats (bandwidth, used aliases etc), as well as total messages received, blocked and sent. Works with both self-hosted and managed instances of addy.io."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/addy"})}),"\n",(0,r.jsx)(n.h4,{id:"options-13",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your addy.io API Key / Personal Access Token. You can generate this under ",(0,r.jsx)(n.a,{href:"https://app.addy.io/settings/api",children:"API Settings"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If your self-hosting addy.io, then supply the host name. By default it will use the public hosted instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiVersion"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If you're using an API version that is not version ",(0,r.jsx)(n.code,{children:"v1"}),", then specify it here"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit the number of emails shown per page. Defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"sortBy"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify the sort order for email addresses. Defaults to ",(0,r.jsx)(n.code,{children:"updated_at"}),". Can be either: ",(0,r.jsx)(n.code,{children:"local_part"}),", ",(0,r.jsx)(n.code,{children:"domain"}),", ",(0,r.jsx)(n.code,{children:"email"}),", ",(0,r.jsx)(n.code,{children:"emails_forwarded"}),", ",(0,r.jsx)(n.code,{children:"emails_blocked"}),", ",(0,r.jsx)(n.code,{children:"emails_replied"}),", ",(0,r.jsx)(n.code,{children:"emails_sent"}),", ",(0,r.jsx)(n.code,{children:"created_at"}),", ",(0,r.jsx)(n.code,{children:"updated_at"})," or ",(0,r.jsx)(n.code,{children:"deleted_at"}),". Precede with a ",(0,r.jsx)(n.code,{children:"-"})," character to reverse order."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"searchTerm"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"A search term to filter results by, will search the email, description and domain"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"disableControls"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Prevent any changes being made to account through the widget. User will not be able to enable or disable aliases through UI when this option is set"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideMeta"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Don't show account meta info (forward/ block count, quota usage etc)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideAliases"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Don't show email address / alias list. Will only show account meta info"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-13",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:' - type: addy\n options:\n apiKey: "xxxxxxxxxxxxxxxxxxxxxxxx\\\n xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\\\n xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"\n limit: 5\n sortBy: created_at\n disableControls: true\n'})}),"\n",(0,r.jsx)(n.h4,{id:"info-13",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free for Self-Hosted / Free Plan available on managed instance or $1/month for premium"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted or Managed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://addy.io/privacy/",children:"addy.io Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"vulnerability-feed",children:"Vulnerability Feed"}),"\n",(0,r.jsxs)(n.p,{children:["Keep track of recent security advisories and vulnerabilities, with optional filtering by score, exploits, vendor and product. Using data from ",(0,r.jsx)(n.a,{href:"https://nvd.nist.gov/developers/vulnerabilities",children:"NIST Vulnerability API"}),". All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/cve"})}),"\n",(0,r.jsx)(n.h4,{id:"options-14",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cveTag"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["This parameter returns only the CVE records that include the provided cveTag. Options are ",(0,r.jsx)(n.strong,{children:"disputed"}),", ",(0,r.jsx)(n.strong,{children:"unsupported-when-assigned"})," or ",(0,r.jsx)(n.strong,{children:"exclusively-hosted-service"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of results to fetch. Can be between ",(0,r.jsx)(n.code,{children:"5"})," and ",(0,r.jsx)(n.code,{children:"30"}),", defaults to ",(0,r.jsx)(n.code,{children:"5"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cvssV2Severity"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["This parameter returns only the CVEs that match the provided CVSSv2 qualitative severity rating. Options are ",(0,r.jsx)(n.strong,{children:"LOW"}),", ",(0,r.jsx)(n.strong,{children:"MEDIUM"}),", ",(0,r.jsx)(n.strong,{children:"HIGH"})," or ",(0,r.jsx)(n.strong,{children:"CRITICAL"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cvssV3Severity"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["This parameter returns only the CVEs that match the provided CVSSv3 qualitative severity rating. Options are ",(0,r.jsx)(n.strong,{children:"LOW"}),", ",(0,r.jsx)(n.strong,{children:"MEDIUM"}),", ",(0,r.jsx)(n.strong,{children:"HIGH"})," or ",(0,r.jsx)(n.strong,{children:"CRITICAL"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cvssV4Severity"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["This parameter returns only the CVEs that match the provided CVSSv4 qualitative severity rating. Options are ",(0,r.jsx)(n.strong,{children:"LOW"}),", ",(0,r.jsx)(n.strong,{children:"MEDIUM"}),", ",(0,r.jsx)(n.strong,{children:"HIGH"})," or ",(0,r.jsx)(n.strong,{children:"CRITICAL"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"keywordSearch"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"This parameter returns only the CVEs where a word or phrase is found in the current description"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Without a key you're limited to 5 requests every 30 seconds. You can get a free API key from ",(0,r.jsx)(n.a,{href:"https://nvd.nist.gov/developers/request-an-api-key",children:"here"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-14",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: cve-vulnerabilities\n"})}),"\n",(0,r.jsx)(n.p,{children:"or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: cve-vulnerabilities\n options:\n cveTag: disputed\n cvssV2Severity: CRITICAL\n limit: 30\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-14",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required (free apiKey recommended for multiple widgets)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.cvedetails.com/privacy.php",children:"CVE Details Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"exchange-rates",children:"Exchange Rates"}),"\n",(0,r.jsx)(n.p,{children:"Display current FX rates in your native currency. Hover over a row to view more info, or click to show rates in that currency."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/currencies"})}),"\n",(0,r.jsx)(n.h4,{id:"options-15",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"inputCurrency"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The base currency to show results in. Specified as a 3-letter ISO-4217 code, see ",(0,r.jsx)(n.a,{href:"https://www.exchangerate-api.com/docs/supported-currencies",children:"here"})," for the full list of supported currencies, and their symbols"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"outputCurrencies"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["List or currencies to show results for. Specified as a 3-letter ISO-4217 code, see ",(0,r.jsx)(n.a,{href:"https://www.exchangerate-api.com/docs/supported-currencies",children:"here"})," for the full list of supported currencies, and their symbols"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["API key for ",(0,r.jsx)(n.a,{href:"https://www.exchangerate-api.com/",children:"exchangerate-api.com"}),", usually a 24-digit alpha-numeric string. You can sign up for a free account ",(0,r.jsx)(n.a,{href:"https://app.exchangerate-api.com/sign-up",children:"here"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-15",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: exchange-rates\n options:\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxx\n inputCurrency: GBP\n outputCurrencies:\n - USD\n - JPY\n - HKD\n - KPW\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-15",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan (up to 100,000 requests/ month)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.exchangerate-api.com/terms",children:"ExchangeRateAPI Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"public-holidays",children:"Public Holidays"}),"\n",(0,r.jsxs)(n.p,{children:["Counting down to the next day off work? This widget displays upcoming public holidays for your country. Data is fetched from ",(0,r.jsx)(n.a,{href:"http://kayaposoft.com/enrico/",children:"Enrico"})]}),"\n",(0,r.jsxs)(n.p,{children:["Note, config for this widget is case-sensitive (see ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1268",children:"#1268"}),")"]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/holidays"})}),"\n",(0,r.jsx)(n.h4,{id:"options-16",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"country"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The country to fetch holiday data for, specified as a country code, e.g. ",(0,r.jsx)(n.code,{children:"GB"})," or ",(0,r.jsx)(n.code,{children:"US"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"state"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["restrict a country to a specific state defined by ",(0,r.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/ISO_3166-2",children:"ISO_3166-2"}),", e.g. ",(0,r.jsx)(n.code,{children:"LND"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"holidayType"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The type of holidays to fetch. Can be: ",(0,r.jsx)(n.code,{children:"all"}),", ",(0,r.jsx)(n.code,{children:"public_holiday"}),", ",(0,r.jsx)(n.code,{children:"observance"}),", ",(0,r.jsx)(n.code,{children:"school_holiday"}),", ",(0,r.jsx)(n.code,{children:"other_day"})," or ",(0,r.jsx)(n.code,{children:"extra_working_day"}),". Defaults to ",(0,r.jsx)(n.code,{children:"public_holiday"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"monthsToShow"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of months in advance to show. Min: ",(0,r.jsx)(n.code,{children:"1"}),", max: ",(0,r.jsx)(n.code,{children:"24"}),". Defaults to ",(0,r.jsx)(n.code,{children:"12"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lang"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The language in which the events should be. Usually local languages and english are available. Default to first available in the country. e.g. ",(0,r.jsx)(n.code,{children:"en"})," or ",(0,r.jsx)(n.code,{children:"fr"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-16",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: public-holidays\n options:\n country: GB\n state: LND\n holidayType: all\n monthsToShow: 12\n lang: en\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-16",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/jurajmajer/enrico",children:"jurajmajer/enrico"}),") or Managed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"covid-19-status",children:"Covid-19 Status"}),"\n",(0,r.jsxs)(n.p,{children:["Keep track of the current COVID-19 status. Optionally also show cases by country, and a time-series chart. Uses live data from various sources, computed by ",(0,r.jsx)(n.a,{href:"https://disease.sh/",children:"disease.sh"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/covid"})}),"\n",(0,r.jsx)(n.h4,{id:"options-17",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showChart"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Also display a time-series chart showing number of recent cases"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showCountries"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Also display a list of cases per country"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"numDays"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Specify number of days worth of history to render on the chart"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"countries"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string[]"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["An array of countries to display, specified by their ",(0,r.jsx)(n.a,{href:"https://www.iso.org/obp/ui",children:"ISO-3 codes"}),". Leave blank to show all, sorted by most cases. ",(0,r.jsx)(n.code,{children:"showCountries"})," must be set to ",(0,r.jsx)(n.code,{children:"true"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If showing all countries, set a limit for number of results to return. Defaults to ",(0,r.jsx)(n.code,{children:"10"}),", no maximum"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-17",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: covid-stats\n"})}),"\n",(0,r.jsx)(n.p,{children:"Or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: covid-stats\n options:\n showChart: true\n showCountries: true\n countries:\n - GBR\n - USA\n - IND\n - RUS\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-17",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/disease-sh/api",children:"disease-sh/api"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Conditions"}),": ",(0,r.jsx)(n.a,{href:"https://github.com/disease-sh/api/blob/master/TERMS",children:"Terms of Use"})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"sports-scores",children:"Sports Scores"}),"\n",(0,r.jsxs)(n.p,{children:["Show recent scores and upcoming matches from your favorite sports team. Data is fetched from ",(0,r.jsx)(n.a,{href:"https://www.thesportsdb.com/",children:"TheSportsDB.com"}),". From the UI, you can click any other team to view their scores and upcoming games, or click a league name to see all teams."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/sports"})}),"\n",(0,r.jsx)(n.h4,{id:"options-18",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"teamId"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The ID of a team to fetch scores from. You can search for your team on the ",(0,r.jsx)(n.a,{href:"https://www.thesportsdb.com/teams_main.php",children:"Teams Page"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"leagueId"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Alternatively, provide a league ID to fetch all games from. You can find the ID on the ",(0,r.jsx)(n.a,{href:"https://www.thesportsdb.com/Sport/Leagues",children:"Leagues Page"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"pastOrFuture"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set to ",(0,r.jsx)(n.code,{children:"past"})," to show scores for recent games, or ",(0,r.jsx)(n.code,{children:"future"})," to show upcoming games. Defaults to ",(0,r.jsx)(n.code,{children:"past"}),". You can change this within the UI"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Optionally specify your API key, which you can sign up for at ",(0,r.jsx)(n.a,{href:"https://www.thesportsdb.com/",children:"TheSportsDB.com"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["To limit output to a certain number of matches, defaults to ",(0,r.jsx)(n.code,{children:"15"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideImage"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set to ",(0,r.jsx)(n.code,{children:"true"})," to not render the team / match banner image, defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-18",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: sports-scores\n options:\n teamId: 133636\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-18",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan (up to 30 requests / minute, limited endpoints)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"news-headlines",children:"News Headlines"}),"\n",(0,r.jsxs)(n.p,{children:["Displays the latest news, click to read full article. Date is fetched from various news sources using ",(0,r.jsx)(n.a,{href:"https://currentsapi.services/en",children:"Currents API"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://pixelflare.cc/alicia/dashy/news"})}),"\n",(0,r.jsx)(n.h4,{id:"options-19",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your API key for CurrentsAPI. This is free, and you can ",(0,r.jsx)(n.a,{href:"https://currentsapi.services/en/register",children:"get one here"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"country"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Fetch news only from a certain country or region. Specified as a country code, e.g. ",(0,r.jsx)(n.code,{children:"GB"})," or ",(0,r.jsx)(n.code,{children:"US"}),". See ",(0,r.jsx)(n.a,{href:"https://api.currentsapi.services/v1/available/regions",children:"here"})," for a list of supported regions"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"category"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Only return news from within a given category, e.g. ",(0,r.jsx)(n.code,{children:"sports"}),", ",(0,r.jsx)(n.code,{children:"programming"}),", ",(0,r.jsx)(n.code,{children:"world"}),", ",(0,r.jsx)(n.code,{children:"science"}),". The ",(0,r.jsx)(n.a,{href:"https://api.currentsapi.services/v1/available/categories",children:"following categories"})," are supported"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lang"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify the language for returned articles as a 2-digit ISO code (limited article support). The ",(0,r.jsx)(n.a,{href:"https://api.currentsapi.services/v1/available/languages",children:"following languages"})," are supported, defaults to ",(0,r.jsx)(n.code,{children:"en"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"count"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit the number of results. Can be between ",(0,r.jsx)(n.code,{children:"1"})," and ",(0,r.jsx)(n.code,{children:"200"}),", defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"keywords"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Only return articles that contain an exact match within their title or description"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideImages"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", then article image thumbnails will not be displayed"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-19",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: news-headlines\n options:\n apiKey: xxxxxxx\n category: world\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-19",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan (up to 600 requests / day)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://currentsapi.services/privacy",children:"CurrentsAPI Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"tfl-status",children:"TFL Status"}),"\n",(0,r.jsx)(n.p,{children:"Shows real-time tube status of the London Underground. All fields are optional."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/tfl"})}),"\n",(0,r.jsx)(n.h4,{id:"options-20",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showAll"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["By default, details for lines with a Good Service are not visible, but you can click More Details to see all. Setting this option to ",(0,r.jsx)(n.code,{children:"true"})," will show all lines on initial page load"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"sortAlphabetically"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["By default lines are sorted by current status, set this option to ",(0,r.jsx)(n.code,{children:"true"})," to instead sort them alphabetically"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"linesToShow"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"By default all lines are shown. If you're only interested in the status of a few lines, then pass in an array of lines to show, specified by name"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-20",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: tfl-status\n"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: tfl-status\n options:\n showAll: true\n sortAlphabetically: true\n linesToShow:\n - District\n - Jubilee\n - Central\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-20",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://tfl.gov.uk/corporate/privacy-and-cookies/",children:"TFL Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"stock-price-history",children:"Stock Price History"}),"\n",(0,r.jsx)(n.p,{children:"Shows recent price history for a given publicly-traded stock or share"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/stocks"})}),"\n",(0,r.jsx)(n.h4,{id:"options-21",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["API key for ",(0,r.jsx)(n.a,{href:"https://www.alphavantage.co/",children:"Alpha Vantage"}),", you can get a free API key ",(0,r.jsx)(n.a,{href:"https://www.alphavantage.co/support/#api-key",children:"here"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"stock"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The stock symbol for the asset to fetch data for"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"priceTime"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The time to fetch price for. Can be ",(0,r.jsx)(n.code,{children:"high"}),", ",(0,r.jsx)(n.code,{children:"low"}),", ",(0,r.jsx)(n.code,{children:"open"})," or ",(0,r.jsx)(n.code,{children:"close"}),". Defaults to ",(0,r.jsx)(n.code,{children:"high"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColor"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Color of the chart value. Defaults to ",(0,r.jsx)(n.code,{children:"--widget-text-color"})," which inherits dashboard primary color"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The height of rendered chart in px. Defaults to ",(0,r.jsx)(n.code,{children:"300"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-21",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: stock-price-chart\n options:\n stock: NET\n apiKey: PGUWSWD6CZTXMT8N\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-21",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan (up to 500 requests/day)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.alphavantage.co/privacy/",children:"AlphaVantage Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"eth-gas-prices",children:"ETH Gas Prices"}),"\n",(0,r.jsxs)(n.p,{children:["Renders the current Gas cost of transactions on the Ethereum network (in both GWEI and USD), along with recent historical prices. Useful for spotting a good time to transact. Uses data from ",(0,r.jsx)(n.a,{href:"https://ethgas.watch/",children:"ethgas.watch"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/eth-gas"})}),"\n",(0,r.jsx)(n.h4,{id:"options-22",children:"Options"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"No config options."})}),"\n",(0,r.jsx)(n.h4,{id:"example-22",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: eth-gas-prices\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-22",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/wslyvh/ethgaswatch",children:"wslyvh/ethgaswatch"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"joke",children:"Joke"}),"\n",(0,r.jsxs)(n.p,{children:["Renders a programming or generic joke. Data is fetched from the ",(0,r.jsx)(n.a,{href:"https://github.com/Sv443/JokeAPI",children:"JokesAPI"})," by @Sv443. All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/joke"})}),"\n",(0,r.jsx)(n.h4,{id:"options-23",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"category"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set the category of jokes to return. Use a string to specify a single category, or an array to pass in multiple options. Available options are: ",(0,r.jsx)(n.code,{children:"all"}),", ",(0,r.jsx)(n.code,{children:"programming"}),", ",(0,r.jsx)(n.code,{children:"pun"}),", ",(0,r.jsx)(n.code,{children:"dark"}),", ",(0,r.jsx)(n.code,{children:"spooky"}),", ",(0,r.jsx)(n.code,{children:"christmas"})," and ",(0,r.jsx)(n.code,{children:"misc"}),". An up-to-date list of supported categories can be found ",(0,r.jsx)(n.a,{href:"https://v2.jokeapi.dev/categories",children:"here"}),". Defaults to ",(0,r.jsx)(n.code,{children:"all"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"safeMode"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set to ",(0,r.jsx)(n.code,{children:"true"}),", to prevent the fetching of any NSFW jokes. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"language"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify the language for returned jokes. The following languages are supported: ",(0,r.jsx)(n.code,{children:"en"}),", ",(0,r.jsx)(n.code,{children:"cs"}),", ",(0,r.jsx)(n.code,{children:"de"}),", ",(0,r.jsx)(n.code,{children:"es"}),", ",(0,r.jsx)(n.code,{children:"fr"})," and ",(0,r.jsx)(n.code,{children:"pt"}),", and an up-to-date list of supported languages can be found ",(0,r.jsx)(n.a,{href:"https://v2.jokeapi.dev/languages",children:"here"}),". By default, your system language will be used, if it's supported, otherwise English"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-23",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: joke\n options:\n safeMode: true\n language: en\n category: Programming\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-23",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/Sv443/JokeAPI",children:"Sv443/JokeAPI"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://sv443.net/privacypolicy/en",children:"SV443's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"chuck-norris-quotes",children:"Chuck Norris quotes"}),"\n",(0,r.jsxs)(n.p,{children:["Renders a Chuck Norris quote. Data is fetched from the ",(0,r.jsx)(n.a,{href:"https://api.chucknorris.io/",children:"ChuckNorrisAPI"})," by @matchilling. All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://tbd"})}),"\n",(0,r.jsx)(n.h4,{id:"options-24",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"categories"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Set the category of jokes to return. Use a string to specify a single category, or an array to pass in multiple options. Available options are: ",(0,r.jsx)(n.code,{children:"animal"}),",",(0,r.jsx)(n.code,{children:"career"}),",",(0,r.jsx)(n.code,{children:"celebrity"}),",",(0,r.jsx)(n.code,{children:"dev"}),",",(0,r.jsx)(n.code,{children:"explicit"}),",",(0,r.jsx)(n.code,{children:"fashion"}),",",(0,r.jsx)(n.code,{children:"food"}),",",(0,r.jsx)(n.code,{children:"history"}),",",(0,r.jsx)(n.code,{children:"money"}),",",(0,r.jsx)(n.code,{children:"movie"}),",",(0,r.jsx)(n.code,{children:"music"}),",",(0,r.jsx)(n.code,{children:"political"}),",",(0,r.jsx)(n.code,{children:"religion"}),",",(0,r.jsx)(n.code,{children:"science"}),",",(0,r.jsx)(n.code,{children:"sport"})," and ",(0,r.jsx)(n.code,{children:"travel"}),". An up-to-date list of supported categories can be found ",(0,r.jsx)(n.a,{href:"https://api.chucknorris.io/jokes/categories",children:"here"}),". Defaults to not explicitely set and therefore any of the categories can come up."]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-24",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: chucknorris\n options:\n categories: history,sport\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-24",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://api.chucknorris.io/privacy",children:"matchilling's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"xkcd-comics",children:"XKCD Comics"}),"\n",(0,r.jsxs)(n.p,{children:["Have a laugh with the daily comic from ",(0,r.jsx)(n.a,{href:"https://xkcd.com/",children:"XKCD"}),". A classic webcomic website covering everything from Linux, math, romance, science and language. All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/xkcd"})}),"\n",(0,r.jsx)(n.h4,{id:"options-25",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"comic"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string / number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Choose which comic to display. Set to either ",(0,r.jsx)(n.code,{children:"random"}),", ",(0,r.jsx)(n.code,{children:"latest"})," or the series number of a specific comic, like ",(0,r.jsx)(n.code,{children:"627"}),". Defaults to ",(0,r.jsx)(n.code,{children:"latest"})]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-25",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: xkcd-comic\n options:\n comic: latest\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-25",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"flight-data",children:"Flight Data"}),"\n",(0,r.jsxs)(n.p,{children:["Displays airport departure and arrival flights, using data from ",(0,r.jsx)(n.a,{href:"https://www.aerodatabox.com/",children:"AeroDataBox"}),". Useful if you live near an airport and often wonder where the flight overhead is going to. Hover over a row for more flight data."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/flights"})}),"\n",(0,r.jsx)(n.h4,{id:"options-26",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"airport"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The airport to show flight data from. Should be specified as a 4-character ICAO-code, a full list of which can be found ",(0,r.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/ICAO_airport_code",children:"here"})," (example: ",(0,r.jsx)(n.code,{children:"KBJC"})," or ",(0,r.jsx)(n.code,{children:"EGKK"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["A valid ",(0,r.jsx)(n.a,{href:"https://rapidapi.com/",children:"RapidAPI"})," Key, with ",(0,r.jsx)(n.a,{href:"https://rapidapi.com/aerodatabox/api/aerodatabox/",children:"AeroDataBox"})," enabled (check in your ",(0,r.jsx)(n.a,{href:"https://rapidapi.com/developer/billing/subscriptions-and-usage",children:"Subscription Dashboard"}),"). This API is free to sign up for and use"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"For busy airports, you may wish to limit the number of results visible"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"direction"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["By default, both departure and arrival flights will be fetched, if you would like to only show flights in one direction, set this to wither ",(0,r.jsx)(n.code,{children:"departure"})," or ",(0,r.jsx)(n.code,{children:"arrival"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-26",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: flight-data\n options:\n airport: EGLC\n apiKey: XXXXX\n limit: 12\n direction: all\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-26",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Free plan (up to 150 requests / month)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance Only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.aerodatabox.com/#h.p_cxtiyzwf_wqd",children:"AeroDataBox"})," and ",(0,r.jsx)(n.a,{href:"https://rapidapi.com/privacy/",children:"RapidAPI Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"astronomy-picture-of-the-day",children:"Astronomy Picture of the Day"}),"\n",(0,r.jsxs)(n.p,{children:["Show the NASA Astronomy Picture of the Day. Data is fetched from ",(0,r.jsx)(n.a,{href:"https://apod.nasa.gov/apod/",children:"APOD"})," using ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/go-apod",children:"@Lissy93/go-apod"})," / hosted at ",(0,r.jsx)(n.a,{href:"https://apod.as93.net/",children:"apod.as93.net"}),"."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://storage.googleapis.com/as93-screenshots/dashy/apod.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-27",children:"Options"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"No config options."})}),"\n",(0,r.jsx)(n.h4,{id:"example-27",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: apod\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-27",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/lissy93/go-apod",children:"@Lissy93/go-apod"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.nasa.gov/about/highlights/HP_Privacy.html",children:"NASA's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"github-trending",children:"GitHub Trending"}),"\n",(0,r.jsxs)(n.p,{children:["Displays currently trending projects on GitHub. Optionally specify a language and time-frame. Data is fetched from ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/gh-trending-no-cors",children:"Lissy93/gh-trending-no-cors"})," using the GitHub API. All fields are optional."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://pixelflare.cc/alicia/dashy/github-trending"})}),"\n",(0,r.jsx)(n.h4,{id:"options-28",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"lang"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["A programming language to fetch trending repos from that category. E.g. ",(0,r.jsx)(n.code,{children:"javascript"})," or ",(0,r.jsx)(n.code,{children:"go"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"since"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The timeframe to use when calculating trends. Can be either ",(0,r.jsx)(n.code,{children:"daily"}),", ",(0,r.jsx)(n.code,{children:"weekly"})," or ",(0,r.jsx)(n.code,{children:"monthly"}),". Defaults to ",(0,r.jsx)(n.code,{children:"daily"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Optionally limit the number of results. Max ",(0,r.jsx)(n.code,{children:"25"}),", default is ",(0,r.jsx)(n.code,{children:"10"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-28",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: github-trending-repos\n options:\n limit: 8\n since: weekly\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-28",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/gh-trending-no-cors",children:"Lissy93/gh-trending-no-cors"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://docs.github.com/en/github/site-policy/github-privacy-statement",children:"GitHub's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"github-profile-stats",children:"GitHub Profile Stats"}),"\n",(0,r.jsxs)(n.p,{children:["Display stats from your GitHub profile, using embedded cards from ",(0,r.jsx)(n.a,{href:"https://github.com/anuraghazra/github-readme-stats",children:"anuraghazra/github-readme-stats"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://pixelflare.cc/alicia/dashy/github-stats"})}),"\n",(0,r.jsx)(n.h4,{id:"options-29",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The GitHub username to fetch info for. E.g. ",(0,r.jsx)(n.code,{children:"lissy93"}),". (Not required if ",(0,r.jsx)(n.code,{children:"hideProfileCard"})," and ",(0,r.jsx)(n.code,{children:"hideLanguagesCard"})," are both set to ",(0,r.jsx)(n.code,{children:"true"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideProfileCard"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", the users profile card will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideLanguagesCard"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", the users top languages card will not be shown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"repos"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If you'd like to also display stats for some GitHub repositories, then add an array or repo names here. Specified as ",(0,r.jsx)(n.code,{children:"[username]/[repo-name]"}),", e.g. ",(0,r.jsx)(n.code,{children:"lissy93/dashy"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-29",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: github-profile-stats\n options:\n username: Lissy93\n hideLanguagesCard: true\n repos:\n - lissy93/dashy\n - lissy93/personal-security-checklist\n - lissy93/twitter-sentiment-visualisation\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-29",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/anuraghazra/github-readme-stats",children:"anuraghazra/github-readme-stats"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://docs.github.com/en/github/site-policy/github-privacy-statement",children:"GitHub's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"healthchecks-status",children:"HealthChecks Status"}),"\n",(0,r.jsx)(n.p,{children:"Display cron job monitor status of one or more HealthChecks project(s). Works with healthchecks.io and your selfhosted instance."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/cron-monitor"})}),"\n",(0,r.jsx)(n.h4,{id:"options-30",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Optional"}),(0,r.jsxs)(n.td,{children:["The base url of your instance, default is ",(0,r.jsx)(n.code,{children:"https://healthchecks.io"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"string"})," (or ",(0,r.jsx)(n.code,{children:"string[]"})," for multiple)"]}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"API key(s) for project(s) to monitor. Generate a separate key per project (read-only is fine), under Project --\x3e Settings --\x3e API Access"})]})]})]}),"\n",(0,r.jsx)(n.h5,{id:"example-30",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: health-checks\n options:\n host: https://healthcheck.your-domain.de\n apiKey: \n - abcdefg...\n - zxywvu...\n"})}),"\n",(0,r.jsx)(n.p,{children:"Or, a single project:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: health-checks\n options:\n host: https://healthchecks.io\n apiKey: hcw_xxxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-30",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free / Paid / Self-hosted"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Managed Instance or Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/healthchecks/healthchecks",children:"healthchecks/healthchecks"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://healthchecks.io/privacy/",children:"Healthchecks.io Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"hackernews-trending",children:"Hackernews Trending"}),"\n",(0,r.jsx)(n.p,{children:"Display new and trending Posts from Hackernews"}),"\n",(0,r.jsx)(n.h4,{id:"options-31",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"stories"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["HN Stories to display defaults to ",(0,r.jsx)(n.code,{children:"topstories"}),". Options are: ",(0,r.jsx)(n.code,{children:"beststories"}),", ",(0,r.jsx)(n.code,{children:"topstories"})," or ",(0,r.jsx)(n.code,{children:"newstories"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"int"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"The size of the list of Posts to show."})]})]})]}),"\n",(0,r.jsx)(n.h5,{id:"example-31",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: hackernews-trending\n options:\n stories: newstories\n limit: 10\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"mvg-departure",children:"MVG Departure"}),"\n",(0,r.jsx)(n.p,{children:"Display departure time of a MVG (M\xfcnchner Verkehrs Gesellschaft) station."}),"\n",(0,r.jsxs)(n.p,{children:["From ",(0,r.jsx)(n.a,{href:"https://www.mvg.de/impressum.html",children:"https://www.mvg.de/impressum.html"}),":"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:"[...] Die Verarbeitung unserer Inhalte oder Daten durch Dritte erfordert unsere ausdr\xfcckliche Zustimmung. F\xfcr private, nicht-kommerzielle Zwecke, wird eine gem\xe4\xdfigte Nutzung ohne unsere ausdr\xfcckliche Zustimmung geduldet. Jegliche Form von Data-Mining stellt keine gem\xe4\xdfigte Nutzung dar.[...]"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"In other words: Private, noncomercial, moderate use of the API is tolerated. They don\u2019t consider data mining as moderate use. (This is not a legal advice)"}),"\n",(0,r.jsx)(n.h4,{id:"options-32",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"location"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The name of the location (exact) or the location id, startin with ",(0,r.jsx)(n.code,{children:"de:09162:"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"integer"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Limit number of entries, defaults to 10."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"A custom title to be displayed."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"header"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"bool"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Shall the title be shown?"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.line"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for given line(s)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.product"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for specific product (TRAM, UBAHN, SBAHN, BUS)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.destination"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for specific destination(s)"})]})]})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: mvg\n options:\n location: Marienplatz\n limit: 5\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-31",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free / Private use only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": ",(0,r.jsx)(n.a,{href:"https://mvg.de",children:"MVG"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.mvg.de/datenschutz-mvg.html",children:"MVG Datenschutz"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"mvg-connection",children:"MVG Connection"}),"\n",(0,r.jsx)(n.p,{children:"Display the next connection for two addresses/coordinates, stations or POI within Munich using MVG MVG (M\xfcnchner Verkehrs Gesellschaft)."}),"\n",(0,r.jsxs)(n.p,{children:["From ",(0,r.jsx)(n.a,{href:"https://www.mvg.de/impressum.html",children:"https://www.mvg.de/impressum.html"}),":"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:"[...] Die Verarbeitung unserer Inhalte oder Daten durch Dritte erfordert unsere ausdr\xfcckliche Zustimmung. F\xfcr private, nicht-kommerzielle Zwecke, wird eine gem\xe4\xdfigte Nutzung ohne unsere ausdr\xfcckliche Zustimmung geduldet. Jegliche Form von Data-Mining stellt keine gem\xe4\xdfigte Nutzung dar.[...]"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"In other words: Private, noncomercial, moderate use of the API is tolerated. They don\u2019t consider data mining as moderate use. (This is not a legal advice)"}),"\n",(0,r.jsx)(n.h4,{id:"options-33",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"origin"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Origin of the connection."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"destination"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Destination of the connection."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"A custom title to be displayed."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"header"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"bool"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Shall the title be shown?"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.line"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for given line(s)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.product"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for specific product (TRAM, UBAHN, SBAHN, BUS)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"filters.destination"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string/object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter results for specific destination(s)"})]})]})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: mvg-connection\n options:\n from: Marienplatz\n from: Dachauer Stra\xdfe 123\n header: true\n filters:\n product: [UBAHN]\n line: [U1,U2,U4,U5]\n\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-32",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free / Private use only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": ",(0,r.jsx)(n.a,{href:"https://mvg.de",children:"MVG"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.mvg.de/datenschutz-mvg.html",children:"MVG Datenschutz"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"custom-list",children:"Custom List"}),"\n",(0,r.jsx)(n.p,{children:"Renders custom schema-compliant JOSN in a list."}),"\n",(0,r.jsx)(n.h4,{id:"options-34",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"text"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A string containing the url of a json file."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"text"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"A title for the widget. Can be helpful if stacking multiple lists in the same section."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"daysForNew"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:"Optional"}),(0,r.jsx)(n.td,{children:"Used to highlight new items."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"json-schema",children:"Json Schema"}),"\n",(0,r.jsx)(n.p,{children:"The input file should comply with the following schema:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n "$schema": "http://json-schema.org/draft-04/schema#",\n "type": "array",\n "items": [\n {\n "type": "object",\n "properties": {\n "link": {\n "type": "object",\n "properties": {\n "text": {\n "type": "string"\n },\n "url": {\n "type": "string"\n },\n "title": {\n "type": "string"\n }\n },\n "required": [\n "text",\n "url",\n "title"\n ]\n },\n "value": {\n "type": "object",\n "properties": {\n "text": {\n "type": "string"\n },\n "title": {\n "type": "string"\n }\n },\n "required": [\n "text",\n "title"\n ]\n },\n "date": {\n "type": "string"\n }\n },\n "required": [\n "link",\n "value",\n "date"\n ]\n }\n ]\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"Example: This json data was generated by a data worflow that gets the new releases of a few projects from GitHub. The system used to build the data workflow is n8n."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'[\n {\n "link": {\n "text": "jellyfin/jellyfin",\n "url": "https://github.com/jellyfin/jellyfin/releases/tag/v10.10.7",\n "title": ""\n },\n "value": {\n "text": "v10.10.7",\n "title": "2025-04-05"\n },\n "date": "2025-04-05T19:14:59Z"\n },\n {\n "link": {\n "text": "jellyfin/jellyfin-web",\n "url": "https://github.com/jellyfin/jellyfin-web/releases/tag/v10.10.7",\n "title": ""\n },\n "value": {\n "text": "v10.10.7",\n "title": "2025-04-05"\n },\n "date": "2025-04-05T19:15:00Z"\n },\n {\n "link": {\n "text": "lissy93/dashy",\n "url": "https://github.com/Lissy93/dashy/releases/tag/3.1.1",\n "title": ""\n },\n "value": {\n "text": "3.1.1",\n "title": "2024-05-30"\n },\n "date": "2024-05-30T17:20:53Z"\n },\n {\n "link": {\n "text": "VSCodium/vscodium",\n "url": "https://github.com/VSCodium/vscodium/releases/tag/1.102.14746",\n "title": ""\n },\n "value": {\n "text": "1.102.14746",\n "title": "2025-07-16"\n },\n "date": "2025-07-16T18:27:49Z"\n }\n]\n'})}),"\n",(0,r.jsx)(n.h4,{id:"notes",children:"Notes"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"This widget is designed to render data generated by another system that complies with the schema. The example JSON data above was generated using a n8n workflow, and other ETL or workflow systems can generate similar results."}),"\n",(0,r.jsx)(n.li,{children:"To avoid requests to a different system in each refresh, you can save the input files locally in the user-data folder inside your Dashy installation."}),"\n",(0,r.jsxs)(n.li,{children:["To use json files from a different domain, remember to add ",(0,r.jsx)(n.code,{children:"useProxy: true"})," to the widget configuration. I have not tested this use case because I save all my input data locally on the Dashy server. Please open a ticket if you have an issue in this use case."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"example-32",children:"Example"}),"\n",(0,r.jsxs)(n.p,{children:["This widget renders a json file that from a ",(0,r.jsx)(n.code,{children:"json-data"})," directory inside the ",(0,r.jsx)(n.code,{children:"user-data"})," directory on the Dashy server."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: custom-list\n options:\n url: /json-data/github-releases.json\n title: 'Github Releases'\n daysForNew: 5\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-33",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Not needed for files hosted inside the ",(0,r.jsx)(n.code,{children:"user-data"})," directory. Use ",(0,r.jsx)(n.code,{children:"useProxy: true"})," to bypass CORS restrictions when using data from a different server."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": user defined"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": depends on the user defined host."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"custom-search",children:"Custom search"}),"\n",(0,r.jsx)(n.p,{children:"Allows web search using multiple user-defined search engines and other websites."}),"\n",(0,r.jsx)(n.h4,{id:"options-35",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"engines"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:"required"}),(0,r.jsxs)(n.td,{children:["An array of search engine objects. Each search engine object should have two required properties: ",(0,r.jsx)(n.strong,{children:"title"})," and ",(0,r.jsx)(n.strong,{children:"url"}),". See the example below."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"placeholder"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"Placeholder text in the search box."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"openingMethod"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsxs)(n.td,{children:["Open search in one of ",(0,r.jsx)(n.code,{children:"newtab"}),", ",(0,r.jsx)(n.code,{children:"sametab"})," or ",(0,r.jsx)(n.code,{children:"workspace"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"notes-1",children:"Notes"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["The first search engine in the engines array will be treated as the default search engine, and used when the user presses ",(0,r.jsx)(n.code,{children:"Enter"})," in the search box."]}),"\n",(0,r.jsx)(n.li,{children:"Popup blockers can interfere with opening a new search window."}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"example-33",children:"Example"}),"\n",(0,r.jsx)(n.p,{children:"This widget allows searching multiple search engines from dashy."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: 'custom-search'\n options:\n placeholder: Search for something using the buttons below\n engines:\n - title: SearXNG\n url: https://searx.lan/?q=\n - title: Quant\n url: https://www.qwant.com/?q=\n - title: Bing Web\n url: http://www.bing.com/search?q=\n - title: Bing Images\n url: http://www.bing.com/images/search?q=\n - title: Bing Maps\n url: http://www.bing.com/maps/search?q=\n - title: Yandex\n url: https://www.yandex.com/search/?text=\n - title: Passmark\n url: https://www.passmark.com/search/zoomsearch.php?zoom_query=\n - title: IMDB\n url: http://www.imdb.com/find?q=\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-34",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Not needed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": user defined"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": depends on the user defined search engines."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"rescuetime-overview",children:"RescueTime Overview"}),"\n",(0,r.jsx)(n.p,{children:"Show an overview of how you have spent your time for the current day."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/rescue-time"})}),"\n",(0,r.jsx)(n.h4,{id:"options-36",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"required"}),(0,r.jsx)(n.td,{children:"The API-Key generated in the RescueTime UI."})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-34",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: rescue-time\n useProxy: true\n options:\n apiKey: abcdefghijkl_mnop\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-35",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe0 Depends on user subscription"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": ",(0,r.jsx)(n.a,{href:"https://www.rescuetime.com",children:"RescueTime"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.rescuetime.com/privacy",children:"RescueTime Privacy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"minecraft-server",children:"Minecraft Server"}),"\n",(0,r.jsx)(n.p,{children:"Show minecraft server status"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://i.ibb.co/hcmd4Wf/minecraft-widget.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-37",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Display title for server uses server address if not set."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"server"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Server hostname or ip(:port) will use srv records."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"bedrock"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If server is a bedrock edition server. (default false)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showMods"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Display mod list if available"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showPlayers"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Display player list if available"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showPlugins"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Display plugin list if available"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-35",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: minecraft-status\n options:\n title: Venity Network\n server: play.venitymc.com\n bedrock: true\n showMods: true\n showPlayers: true\n showPlugins: true\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-36",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": ",(0,r.jsx)(n.a,{href:"https://mcsrvstat.us/",children:"Minecraft Server Status"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://mcsrvstat.us/faq",children:"Minecraft Server Status FAQ"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"self-hosted-services-widgets",children:"Self-Hosted Services Widgets"}),"\n",(0,r.jsx)(n.h3,{id:"system-info",children:"System Info"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.mvg.de/datenschutz-mvg.html",children:"MVG Datenschutz"})]}),"\nDisplays info about the server which Dashy is hosted on. Includes user + host, operating system, uptime and basic memory & load data."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/system-info"})}),"\n",(0,r.jsx)(n.h4,{id:"options-38",children:"Options"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"No config options."})}),"\n",(0,r.jsx)(n.h4,{id:"example-36",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: system-info\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-37",children:"Info"}),"\n",(0,r.jsx)(n.p,{children:"No external data requests made"}),"\n",(0,r.jsxs)(n.p,{children:["Note that this widget is not available if you are running Dashy in a container or VM. Instead you can use the ",(0,r.jsx)(n.a,{href:"#system-resource-monitoring",children:"System Monitoring"})," widgets to display stats from the host system instead."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"cpu-history-netdata",children:"CPU History (NetData)"}),"\n",(0,r.jsx)(n.p,{children:"Pull recent CPU usage history from NetData."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/cpu-history"})}),"\n",(0,r.jsx)(n.h4,{id:"options-39",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your NetData instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The height of rendered chart in px. Defaults to ",(0,r.jsx)(n.code,{children:"300"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColor"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColors"})})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"string"})," / ",(0,r.jsx)(n.code,{children:"array"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Color of the chart value(s) as hex codes. ",(0,r.jsx)(n.code,{children:"chartColor"})," is a single value (defaults to ",(0,r.jsx)(n.code,{children:"--widget-text-color"}),"), whereas ",(0,r.jsx)(n.code,{children:"chartColors"})," is an array of colors"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-37",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nd-cpu-history\n options:\n host: http://192.168.1.1:19999\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-38",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/netdata/netdata",children:"GitHub - NetData"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.netdata.cloud/data-privacy/",children:"NetData Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"memory-history-netdata",children:"Memory History (NetData)"}),"\n",(0,r.jsx)(n.p,{children:"Pull recent system RAM usage from NetData, and show as a breakdown of different categories."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://i.ibb.co/2dsSWnk/nd-memory-history.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-40",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your NetData instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The height of rendered chart in px. Defaults to ",(0,r.jsx)(n.code,{children:"300"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColor"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColors"})})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"string"})," / ",(0,r.jsx)(n.code,{children:"array"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Color of the chart value(s) as hex codes. ",(0,r.jsx)(n.code,{children:"chartColor"})," is a single value (defaults to ",(0,r.jsx)(n.code,{children:"--widget-text-color"}),"), whereas ",(0,r.jsx)(n.code,{children:"chartColors"})," is an array of colors"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-38",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nd-ram-history\n options:\n host: http://192.168.1.1:19999\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-39",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/netdata/netdata",children:"GitHub - NetData"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.netdata.cloud/data-privacy/",children:"NetData Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"load-history-netdata",children:"Load History (NetData)"}),"\n",(0,r.jsx)(n.p,{children:"Pull recent load usage in 1, 5 and 15 minute intervals, from NetData."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/load-history"})}),"\n",(0,r.jsx)(n.h4,{id:"options-41",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your NetData instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The height of rendered chart in px. Defaults to ",(0,r.jsx)(n.code,{children:"300"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColor"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"chartColors"})})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"string"})," / ",(0,r.jsx)(n.code,{children:"array"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Color of the chart value(s) as hex codes. ",(0,r.jsx)(n.code,{children:"chartColor"})," is a single value (defaults to ",(0,r.jsx)(n.code,{children:"--widget-text-color"}),"), whereas ",(0,r.jsx)(n.code,{children:"chartColors"})," is an array of colors"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-39",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nd-load-history\n options:\n host: http://192.168.1.1:19999\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-40",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/netdata/netdata",children:"GitHub - NetData"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.netdata.cloud/data-privacy/",children:"NetData Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-stats",children:"Pi-Hole Stats"}),"\n",(0,r.jsxs)(n.p,{children:["Displays the number of queries blocked by ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"}),"."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/pi-hole"})}),"\n",(0,r.jsx)(n.h4,{id:"options-42",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideStatus"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideChart"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideInfo"})})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide any of the three parts of the widget"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password. It is ",(0,r.jsx)(n.strong,{children:"NOT"})," your pi-hole admin interface or server password. It can be found in ",(0,r.jsx)(n.code,{children:"/etc/pihole/setupVars.conf"}),", and is a 64-character located on the line that starts with ",(0,r.jsx)(n.code,{children:"WEBPASSWORD"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-40",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-stats\n options:\n hostname: http://192.168.130.1\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["To avoid storing secrets in plaintext, both ",(0,r.jsx)(n.code,{children:"hostname"})," and ",(0,r.jsx)(n.code,{children:"apiKey"})," can be supplied via environment variables. See ",(0,r.jsx)(n.a,{href:"#handling-secrets",children:"Handling Secrets"}),"."]})}),"\n",(0,r.jsx)(n.h4,{id:"info-41",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-Hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-stats-v6",children:"Pi-Hole Stats v6"}),"\n",(0,r.jsxs)(n.p,{children:["Displays the number of queries blocked by ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"}),". Use this version of the widget if you have a v6+ Pi-Hole instance."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://i.ibb.co/zftCLJN/pi-hole-stats.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-43",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideStatus"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideChart"})})," / ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideInfo"})})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Optionally hide any of the three parts of the widget"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password or application password. It ",(0,r.jsx)(n.strong,{children:"IS"})," your Pi-Hole admin interface password ",(0,r.jsx)(n.strong,{children:"UNLESS"})," you have 2FA turned on (in contrast to the old widget). If you have 2FA turned on you will need to create an application password. Refer to Pi-Hole documentation for how to create an application password."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-41",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-stats-v6\n options:\n hostname: http://192.168.130.1\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["To avoid storing secrets in plaintext, both ",(0,r.jsx)(n.code,{children:"hostname"})," and ",(0,r.jsx)(n.code,{children:"apiKey"})," can be supplied via environment variables. See ",(0,r.jsx)(n.a,{href:"#handling-secrets",children:"Handling Secrets"}),"."]})}),"\n",(0,r.jsx)(n.h4,{id:"info-42",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-Hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-queries",children:"Pi-Hole Queries"}),"\n",(0,r.jsxs)(n.p,{children:["Shows top queries that were blocked and allowed by ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"}),"."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/pi-hole-queries"})}),"\n",(0,r.jsx)(n.h4,{id:"options-44",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password. It is ",(0,r.jsx)(n.strong,{children:"NOT"})," your pi-hole admin interface or server password. It can be found in ",(0,r.jsx)(n.code,{children:"/etc/pihole/setupVars.conf"}),", and is a 64-character located on the line that starts with ",(0,r.jsx)(n.code,{children:"WEBPASSWORD"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"count"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of queries to display. Defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-42",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-top-queries\n options:\n hostname: https://pi-hole.local\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-43",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-queries-v6",children:"Pi-Hole Queries v6"}),"\n",(0,r.jsxs)(n.p,{children:["Shows top queries that were blocked and allowed by ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"}),". Use this version of the widget if you have a v6+ Pi-Hole instance."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://i.ibb.co/pXR0bdQ/pi-hole-queries.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-45",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password or application password. It ",(0,r.jsx)(n.strong,{children:"IS"})," your Pi-Hole admin interface password ",(0,r.jsx)(n.strong,{children:"UNLESS"})," you have 2FA turned on (in contrast to the old widget). If you have 2FA turned on you will need to create an application password. Refer to Pi-Hole documentation for how to create an application password."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"count"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The number of queries to display. Defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-43",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-top-queries-v6\n options:\n hostname: https://pi-hole.local\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-44",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-recent-traffic",children:"Pi-Hole Recent Traffic"}),"\n",(0,r.jsxs)(n.p,{children:["Shows number of recent traffic, using allowed and blocked queries from ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://pixelflare.cc/alicia/dashy/pi-hole-trafic"})}),"\n",(0,r.jsx)(n.h4,{id:"options-46",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password. It is ",(0,r.jsx)(n.strong,{children:"NOT"})," your pi-hole admin interface or server password. It can be found in ",(0,r.jsx)(n.code,{children:"/etc/pihole/setupVars.conf"}),", and is a 64-character located on the line that starts with ",(0,r.jsx)(n.code,{children:"WEBPASSWORD"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-44",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-traffic\n options:\n hostname: https://pi-hole.local\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-45",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"pi-hole-recent-traffic-v6",children:"Pi-Hole Recent Traffic v6"}),"\n",(0,r.jsxs)(n.p,{children:["Shows number of recent traffic, using allowed and blocked queries from ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/",children:"Pi-Hole"}),". Use this version of the widget if you have a v6+ Pi-Hole instance."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://i.ibb.co/7kdxxwx/pi-hole-recent-queries.png"})}),"\n",(0,r.jsx)(n.h4,{id:"options-47",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Pi-Hole instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Your Pi-Hole web password or application password. It ",(0,r.jsx)(n.strong,{children:"IS"})," your Pi-Hole admin interface password ",(0,r.jsx)(n.strong,{children:"UNLESS"})," you have 2FA turned on (in contrast to the old widget). If you have 2FA turned on you will need to create an application password. Refer to Pi-Hole documentation for how to create an application password."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-45",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: pi-hole-traffic-v6\n options:\n hostname: https://pi-hole.local\n apiKey: xxxxxxxxxxxxxxxxxxxxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-46",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udd34 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/pi-hole/pi-hole",children:"GitHub - Pi-hole"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://pi-hole.net/privacy/",children:"Pi-Hole Privacy Guide"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"stat-ping-statuses",children:"Stat Ping Statuses"}),"\n",(0,r.jsxs)(n.p,{children:["Displays the current and recent uptime of your running services, via a self-hosted instance of ",(0,r.jsx)(n.a,{href:"https://github.com/statping/statping",children:"StatPing"})]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"300",src:"https://pixelflare.cc/alicia/dashy/statping"})}),"\n",(0,r.jsx)(n.h4,{id:"options-48",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your StatPing instance, without a trailing slash"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"groupId"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:"Optional"}),(0,r.jsxs)(n.td,{children:["If provided, only Services in the given group are displayed. Defaults to ",(0,r.jsx)(n.code,{children:"0"})," in which case all services are displayed."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showChart"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:"Optional"}),(0,r.jsxs)(n.td,{children:["If provided and ",(0,r.jsx)(n.code,{children:"false"})," then charts are not displayed. Defaults to ",(0,r.jsx)(n.code,{children:"true"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showInfo"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:"Optional"}),(0,r.jsxs)(n.td,{children:["If provided and ",(0,r.jsx)(n.code,{children:"false"})," then information summaries are not displayed. Defaults to ",(0,r.jsx)(n.code,{children:"true"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-46",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: stat-ping\n options:\n hostname: http://192.168.130.1:8080\n"})}),"\n",(0,r.jsx)(n.p,{children:"or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: stat-ping\n options:\n hostname: http://192.168.130.1:8080\n groupId: 3\n showChart: false\n showInfo: false\n"})}),"\n",(0,r.jsxs)(n.p,{children:["You can use multiple StatPing widgets with different ",(0,r.jsx)(n.code,{children:"groupId"}),"s."]}),"\n",(0,r.jsx)(n.p,{children:"Note, the Group Id is not directly visible in StatPing UI, you can inspect the group select HTML element or the API response to find out."}),"\n",(0,r.jsx)(n.h4,{id:"info-47",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/statping/statping",children:"GitHub - StatPing"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://docs.statping.com/",children:"StatPing Docs"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"synology-download-station",children:"Synology Download Station"}),"\n",(0,r.jsx)(n.p,{children:"Displays the current downloads/torrents tasks of your Synology NAS"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://pixelflare.cc/alicia/dashy/synology-downloads"})}),"\n",(0,r.jsx)(n.h4,{id:"options-49",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your Synology NAS, without a trailing slash"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The username of a user on your synology NAS. You will see only this user download station tasks if he is not part of the administrator group. Currently don't support OTP protected accounts."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The password of the account specified above."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-47",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: synology-download\n options:\n hostname: http://192.168.1.1:8080\n username: dashy\n password: totally-secure-password\n\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-48",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://www.synology.com/en-us",children:"Synology"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.synology.com/en-us/company/legal/privacy",children:"Synology Privacy Statement"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"adguard-home-block-stats",children:"AdGuard Home Block Stats"}),"\n",(0,r.jsxs)(n.p,{children:["Fetches data from your ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"})," instance, and\ndisplays total number of allowed and blocked queries, plus a pie chart showing breakdown by block type."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/adguard-stats"})}),"\n",(0,r.jsx)(n.h4,{id:"options-50",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your AdGuard Home instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your username here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your password here"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-48",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: adguard-stats\n useProxy: true\n options:\n hostname: http://127.0.0.1\n username: admin\n password: test\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-49",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/privacy.html",children:"AdGuard Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"adguard-home-filters",children:"AdGuard Home Filters"}),"\n",(0,r.jsxs)(n.p,{children:["Fetches data from your ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"})," instance, to display the current status of each of your filter lists. Includes filter name, last updated, number of items, and a link to the list."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/adguard-filters"})}),"\n",(0,r.jsx)(n.h4,{id:"options-51",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your AdGuard Home instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your username here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your password here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showOnOffStatusOnly"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),", will only show aggregated AdGuard filter status (on/off), instead of a list of filters"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-49",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: adguard-filter-status\n useProxy: true\n options:\n hostname: http://127.0.0.1\n username: admin\n password: test\n showOnOffStatusOnly: false\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-50",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/privacy.html",children:"AdGuard Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"adguard-home-dns-info",children:"AdGuard Home DNS Info"}),"\n",(0,r.jsxs)(n.p,{children:["Fetches data from your ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"})," instance, and displays the current status (Enabled / Disabled) of AdGuard DNS. Click show more to view detailed info, including upstream DNS provider, active ports, and the status of DNSSEC, EDNS CS, PTR and IPv6."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/adguard-dns"})}),"\n",(0,r.jsx)(n.h4,{id:"options-52",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your AdGuard Home instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your username here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your password here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showFullInfo"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If set to ",(0,r.jsx)(n.code,{children:"true"}),', the full DNS info will be shown by default, without having to click "Show Info"']})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-50",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: adguard-dns-info\n useProxy: true\n options:\n hostname: http://127.0.0.1\n username: admin\n password: test\n showFullInfo: false\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-51",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/privacy.html",children:"AdGuard Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"adguard-home-top-domains",children:"AdGuard Home Top Domains"}),"\n",(0,r.jsxs)(n.p,{children:["Fetches data from your ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"})," instance, and displays a list of the most queried, and most blocked domains."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"600",src:"https://pixelflare.cc/alicia/dashy/adguard-domains"})}),"\n",(0,r.jsx)(n.h4,{id:"options-53",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to your AdGuard Home instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your username here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If you've got auth enabled on AdGuard, provide your password here"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify the number of results to show, between ",(0,r.jsx)(n.code,{children:"1"})," and ",(0,r.jsx)(n.code,{children:"100"}),", defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideBlockedDomains"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Don't show the blocked domains list (queried domains only)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideQueriedDomains"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Don't show the queried domains list (blocked domains only)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-51",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: adguard-top-domains\n useProxy: true\n options:\n hostname: http://127.0.0.1\n username: admin\n password: test\n limit: 10\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-52",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/adguard-home/overview.html",children:"AdGuard Home"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://adguard.com/en/privacy.html",children:"AdGuard Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-user",children:"Nextcloud User"}),"\n",(0,r.jsxs)(n.p,{children:["Nextcloud is a ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/install/#instructions-server",children:"self hosted"})," productivity platform, it can also be used free of charge with ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/sign-up/",children:"hundreds of existing hosting providers"})," that offer a free Nextcloud account."]}),"\n",(0,r.jsx)(n.p,{children:"Displays branding information of a Nextcloud server (logo, url, slogan) and some user details (name, login name, last login, disk space or quota). Use with regular or admin user."}),"\n",(0,r.jsx)(n.p,{children:"Shows quota usage when quota is enabled for the user or disk usage when not enabled."}),"\n",(0,r.jsx)(n.p,{children:"Known issues: the User API incorrectly reports available disk space as total for admin users when quota is not enabled (which usually is the case for admins)."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/nextcloud-user",alt:"nextcloud-user"})}),"\n",(0,r.jsx)(n.h4,{id:"options-54",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud username"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-52",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-user\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-53",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-user-statuses",children:"Nextcloud User Statuses"}),"\n",(0,r.jsx)(n.p,{children:"Show user statuses for selected users."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/nextcloud-user-status",alt:"nextcloud-userstatus"})}),"\n",(0,r.jsx)(n.h4,{id:"options-55",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud username"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"users"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["Nextcloud User IDs to show statuses for, list size between ",(0,r.jsx)(n.code,{children:"1"})," and ",(0,r.jsx)(n.code,{children:"100"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showEmpty"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Show statuses without a message, defaults to ",(0,r.jsx)(n.code,{children:"true"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-53",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-user-status\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n users: ['bob', 'alice']\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-54",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-notifications",children:"Nextcloud Notifications"}),"\n",(0,r.jsx)(n.p,{children:"Displays your notifications and allows deleting them."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/next-cloud-notifications",alt:"nextcloud-notifications"})}),"\n",(0,r.jsx)(n.h4,{id:"options-56",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud username"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number|string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit displayed notifications either by count, e.g. ",(0,r.jsx)(n.code,{children:"5"})," to show the 5 most recent, or by age, e.g. ",(0,r.jsx)(n.code,{children:"1d"})," to only show notifications not older than a day. Accepted suffixes for age limit are ",(0,r.jsx)(n.code,{children:"m"}),", ",(0,r.jsx)(n.code,{children:"h"})," and ",(0,r.jsx)(n.code,{children:"d"}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-54",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-notifications\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n limit: 6h\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-55",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-system",children:"Nextcloud System"}),"\n",(0,r.jsx)(n.p,{children:"Visualises overall memory utilisation and CPU load averages, shows server versions."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/next-cloud-sysyem",alt:"nextcloud-system"})}),"\n",(0,r.jsx)(n.h4,{id:"options-57",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Must be a Nextcloud admin user"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-55",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-system\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-56",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-stats",children:"Nextcloud Stats"}),"\n",(0,r.jsx)(n.p,{children:"Shows key usage statistics about your Nextcloud server."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/nextcloud-stats",alt:"nextcloud-stats"})}),"\n",(0,r.jsx)(n.h4,{id:"options-58",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Must be a Nextcloud admin user"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-56",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-stats\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-57",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"nextcloud-php-opcache-stats",children:"Nextcloud PHP OPcache Stats"}),"\n",(0,r.jsx)(n.p,{children:"Shows statistics about PHP OPcache performance on your Nextcloud server."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/nextcloud-php",alt:"nextcloud-phpopcache"})}),"\n",(0,r.jsx)(n.h4,{id:"options-59",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Nextcloud server"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Must be a Nextcloud admin user"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Nextcloud app-password (create one in Settings -> Security)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-57",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: nextcloud-php-opcache\n useProxy: true\n options:\n hostname: https://nextcloud.example.com\n username: alice\n password: xxxxx-xxxxx-xxxxx-xxxxx\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-58",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://nextcloud.com",children:"Nextcloud"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://nextcloud.com/privacy",children:"Nextcloud Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"ntfy-stream",children:"ntfy stream"}),"\n",(0,r.jsxs)(n.p,{children:["Subscribes to topics on a ",(0,r.jsx)(n.strong,{children:"private"})," ",(0,r.jsx)(n.a,{href:"https://docs.ntfy.sh/",children:"ntfy"})," server, and shows ",(0,r.jsx)(n.strong,{children:"new messages"})," as they arrive."]}),"\n",(0,r.jsx)(n.h4,{id:"options-60",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"server_url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The server url"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"topic"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A topic, or a comma separated list of topics"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"auth"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsxs)(n.td,{children:["An ",(0,r.jsx)(n.a,{href:"https://docs.ntfy.sh/subscribe/api/#authentication",children:"auth string"}),". \u26a0\ufe0f This is sent as query parameter."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"A title for the widget."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-58",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: ntfy-stream\n useProxy: false\n options:\n title: NTFY stream\n server_url: https://myntfy.server.tld\n topic: alert,warning,mytopic\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-59",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": Disabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": ",(0,r.jsx)(n.a,{href:"https://docs.ntfy.sh/install/",children:"Self-hosted"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsx)(n.a,{href:"https://docs.ntfy.sh/privacy/",children:"ntfy Privacy Policy"})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"proxmox-lists",children:"Proxmox lists"}),"\n",(0,r.jsx)(n.p,{children:"Shows lists of nodes, containers, and VMs in a Proxmox virtual environment cluster, with a status indicator."}),"\n",(0,r.jsx)(n.h4,{id:"options-61",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"cluster_url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The URL of the proxmox cluster server. No trailing ",(0,r.jsx)(n.code,{children:"/"}),". for example: ",(0,r.jsx)(n.code,{children:"https://proxmox.lan:8006"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"user_name"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["A Proxmox API Username, for example ",(0,r.jsx)(n.code,{children:"root@pam"})," or ",(0,r.jsx)(n.code,{children:"dashy@pve"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"token_name"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A Proxmox API token name. You can get a token in the API section of the cluster management interface."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"token_uuid"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The value of the token entered above. This is normally a UUID."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"node"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"A Proxmox node name. If empty or not supplied, a list of nodes will be shown."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"node_data"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsxs)(n.td,{children:["This is required if a node is selected, Currently this accepts two values, either ",(0,r.jsx)(n.code,{children:"lxc"})," or ",(0,r.jsx)(n.code,{children:"qemu"})," but the widget can be improved to get other types of data from the Proxmox API."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"A widget title."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"title_as_link"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsxs)(n.td,{children:["When this is set to anything other than 0 or false, the title will be linked to the value entered in the ",(0,r.jsx)(n.code,{children:"cluster_url"})," option."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"footer"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"A widget footer."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"footer_as_link"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsxs)(n.td,{children:["When this is set to anything other than 0 or false, the title will be linked to the value entered in the ",(0,r.jsx)(n.code,{children:"cluster_url"})," option."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hide_templates"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:"optional"}),(0,r.jsx)(n.td,{children:"When this is set to anything other than 0 or false, templates will be filtered out of the result list."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-59",children:"Example"}),"\n",(0,r.jsx)(n.p,{children:"This will show the list of nodes."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: proxmox-lists\n useProxy: true \n options:\n cluster_url: https://proxmox.lan:8006\n user_name: root@pam\n token_name: dashy\n token_uuid: bfb152df-abcd-abcd-abcd-ccb95a472d01\n"})}),"\n",(0,r.jsx)(n.p,{children:"This will show the list of VMs, with a title and a linked fotter, hiding VM templates."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: proxmox-lists\n useProxy: true \n options:\n cluster_url: https://proxmox.lan:8006\n user_name: root@pam\n token_name: dashy\n token_uuid: bfb152df-abcd-abcd-abcd-ccb95a472d01\n node: proxmox\n node_data: qemu\n title: Proxmox VMs\n title_as_link: false\n footer: Proxmox\n footer_as_link: true\n hide_templates: 1\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-60",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://proxmox.com/en/proxmox-ve",children:"Proxmox Virtual Environment"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://proxmox.com/en/privacy-policy",children:"Proxmox's Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"404 Error in development mode"}),": The error might disappear in production mode ",(0,r.jsx)(n.code,{children:"yarn start"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"500 Error in production mode"}),": Try adding the certificate authority (CA) certificate of your Proxmox host to Node.js.\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Download the Proxmox CA certificate to your Dashy host."}),"\n",(0,r.jsxs)(n.li,{children:["Export environment variable ",(0,r.jsx)(n.code,{children:"NODE_EXTRA_CA_CERTS"})," and set its value to the path of the downloaded CA certificate. Example: ",(0,r.jsx)(n.code,{children:"export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/devlab_ca.pem"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"sabnzbd",children:"Sabnzbd"}),"\n",(0,r.jsx)(n.p,{children:"Shows queue information regarding your self hosted Sabnzbd server."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"450",src:"https://pixelflare.cc/alicia/dashy/sabnzbd",alt:"Sabnzbd"})}),"\n",(0,r.jsx)(n.h4,{id:"options-62",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"sabnzbdUrl"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The URL of the Sabnzbd server. No trailing ",(0,r.jsx)(n.code,{children:"/"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["API key for Sabnzbd access. Located under ",(0,r.jsx)(n.code,{children:"Config"})," -> ",(0,r.jsx)(n.code,{children:"General"})," -> ",(0,r.jsx)(n.code,{children:"Security"})," -> ",(0,r.jsx)(n.code,{children:"API Key"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideDetails"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Hides extra server queue details."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideQueue"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Hides the queue list in an expandable dropdown."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-60",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:" - type: sabnzbd\n options:\n sabnzbdUrl: 'https://sabnzbd.example.com'\n apiKey: XXXXXXXXXXXXXXXXXX\n hideDetails: false\n hideQueue: false\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-61",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://sabnzbd.org/",children:"Sabnzbd"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://forums.sabnzbd.org/ucp.php?mode=privacy",children:"Sabnzbd Privacy Policy"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"gluetun-vpn-info",children:"Gluetun VPN Info"}),"\n",(0,r.jsx)(n.p,{children:"Display info from the Gluetun VPN container public IP API. This can show the IP and location data for the exit VPN node."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://pixelflare.cc/alicia/dashy/guletn-vpn"})}),"\n",(0,r.jsx)(n.h4,{id:"options-63",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"visibleFields"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["A comma separated list of the fields you want visible in the widget. You can have any number of the following : ",(0,r.jsx)(n.code,{children:"public_ip"}),", ",(0,r.jsx)(n.code,{children:"region"}),", ",(0,r.jsx)(n.code,{children:"country"}),", ",(0,r.jsx)(n.code,{children:"city"}),", ",(0,r.jsx)(n.code,{children:"location"}),", ",(0,r.jsx)(n.code,{children:"organisation"}),", ",(0,r.jsx)(n.code,{children:"postal_code"}),", ",(0,r.jsx)(n.code,{children:"timezone"}),". Defaults to just ",(0,r.jsx)(n.code,{children:"public_ip"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The url to the gluetun HTTP control server. E.g. ",(0,r.jsx)(n.code,{children:"http://gluetun:8000"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-61",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gluetun-status\n useProxy: true\n options:\n hostname: http://server-or-conatiner-hostname:8000\n visibleFields: public_ip,region,country,city,location,organisation,postal_code,timezone\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-62",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/qdm12/gluetun",children:"Gluetun"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/qdm12/gluetun/wiki",children:"Gluetun Wiki"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"drone-ci-builds",children:"Drone CI Builds"}),"\n",(0,r.jsxs)(n.p,{children:["Display the last builds from a ",(0,r.jsx)(n.a,{href:"https://www.drone.ci",children:"Drone CI"})," instance. A self-hosted CI system that uses docker."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"380",src:"https://pixelflare.cc/alicia/dashy/drone-ci"})}),"\n",(0,r.jsx)(n.h4,{id:"options-64",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The hostname of the Drone CI instance."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The API key (https://[your-drone-instance]/account)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"integer"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Limit the amounts of listed builds."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"repo"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Show only builds of the specified repo"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-62",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: drone-ci\n updateInterval: 30\n options:\n host: https://drone.somedomain.com\n apiKey: my-very-secret-api-key\n limit: 10\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-63",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://www.drone.io",children:"Drone"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://www.drone.io",children:"Drone"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"filebrowser",children:"Filebrowser"}),"\n",(0,r.jsxs)(n.p,{children:["Displays storage statistics and file listings from a ",(0,r.jsx)(n.a,{href:"https://github.com/gtsteffaniak/filebrowser",children:"Filebrowser Quantum"})," instance. Shows directory size, file/folder counts, favorite files, and recently modified files with quick-access links."]}),"\n",(0,r.jsx)(n.h4,{id:"options-65",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of your Filebrowser instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"A long-lived API key (create in Settings \u2192 API Keys)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"source"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"The source/scope name to browse. Defaults to the first available source"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"path"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["The directory path to display. Defaults to ",(0,r.jsx)(n.code,{children:"/"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"favorites"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"List of filenames to show as quick-access favorites"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showRecent"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Number of recently modified files to display. Defaults to ",(0,r.jsx)(n.code,{children:"5"}),", set to ",(0,r.jsx)(n.code,{children:"0"})," to disable"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Maximum number of files to display per section. Defaults to ",(0,r.jsx)(n.code,{children:"10"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideStats"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If ",(0,r.jsx)(n.code,{children:"true"}),", hides the storage statistics section"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideFavorites"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If ",(0,r.jsx)(n.code,{children:"true"}),", hides the favorites section"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hideRecent"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If ",(0,r.jsx)(n.code,{children:"true"}),", hides the recent files section"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"showDetailedStats"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If ",(0,r.jsx)(n.code,{children:"true"}),", shows additional statistics including last modified date, largest file, hidden file count, total items, and file type breakdown. Defaults to ",(0,r.jsx)(n.code,{children:"false"})]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-63",children:"Example"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Basic usage:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: filebrowser\n useProxy: true\n options:\n hostname: http://filebrowser.local:8080\n apiKey: DASHY_FILEBROWSER_KEY\n source: Documents\n path: /\n showRecent: 5\n favorites:\n - important-notes.txt\n - config.yaml\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"With detailed statistics:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: filebrowser\n useProxy: true\n options:\n hostname: http://filebrowser.local:8080\n apiKey: DASHY_FILEBROWSER_KEY\n source: Downloads\n showDetailedStats: true\n showRecent: 10\n limit: 15\n"})}),"\n",(0,r.jsx)(n.h4,{id:"widget-sections",children:"Widget Sections"}),"\n",(0,r.jsx)(n.p,{children:"The widget displays up to four sections:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Storage Stats"})," - Directory name, total size, file and folder counts"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Detailed Stats"})," (optional) - Last modified date, largest file, hidden file count, total items, and file type breakdown with badges"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Favorites"})," - Quick-access links to user-specified files"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Recent Files"})," - Most recently modified files sorted by date"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"info-64",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/gtsteffaniak/filebrowser",children:"Filebrowser Quantum"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsx)(n.em,{children:"Self-Hosted"})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"linkding",children:"Linkding"}),"\n",(0,r.jsx)(n.p,{children:"Linkding is a self-hosted bookmarking service, which has a clean interface and is simple to set up. This lists the links, filterable by tags."}),"\n",(0,r.jsx)(n.h4,{id:"options-66",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The hostname of the Drone CI instance."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The API key (",(0,r.jsx)(n.a,{href:"https://your-linkding-instance/settings/integrations",children:"https://your-linkding-instance/settings/integrations"}),")."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"tags"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"list of string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Filter the links by tag."})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-64",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: linkding\n updateInterval: 30\n options:\n host: https://lingding.somedomain.com\n apiKey: my-very-secret-api-key\n tags: \n - rpg\n - markdown\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-65",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/sissbruecker/linkding",children:"Linkding"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/sissbruecker/linkding",children:"Linkding"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"uptime-kuma",children:"Uptime Kuma"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})," is an easy-to-use self-hosted monitoring tool."]}),"\n",(0,r.jsx)(n.h4,{id:"options-67",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Uptime Kuma instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiKey"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The API key (see ",(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma/wiki/API-Keys",children:"https://github.com/louislam/uptime-kuma/wiki/API-Keys"}),")."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-65",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: uptime-kuma\n useProxy: true\n options:\n apiKey: uk2_99H0Yd3I2pPNIRfn0TqBFu4g5q85R1Mh75yZzw6H\n url: http://192.168.1.106:3691/metrics\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-66",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})," )"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"uptime-kuma-status-page",children:"Uptime Kuma Status Page"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})," is an easy-to-use self-hosted monitoring tool."]}),"\n",(0,r.jsx)(n.h4,{id:"options-68",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"host"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL of the Uptime Kuma instance"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"slug"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The slug of the status page"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"monitorNames"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"strins"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Names of monitored services (in the same order as on the kuma uptime status page)"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-66",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'- type: uptime-kuma-status-page\n options:\n host: http://localhost:3001\n slug: another-beautiful-status-page\n monitorNames:\n - "Name1"\n - "Name2"\n'})}),"\n",(0,r.jsx)(n.h4,{id:"info-67",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Not Needed"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})," )"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/louislam/uptime-kuma",children:"Uptime Kuma"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"tactical-rmm",children:"Tactical RMM"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/amidaware/tacticalrmm",children:"Tactical RMM"})," is a self-hosted remote monitoring & management tool."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsxs)(n.a,{href:"https://github.com/user-attachments/assets/152a7205-e5de-401f-bad8-19063ddfaf3c",children:["\n ",(0,r.jsx)(n.img,{src:"https://github.com/user-attachments/assets/5921d46f-d84c-494d-8aaf-6b20cc592640",alt:"Capture",border:"0"})]})}),"\n",(0,r.jsx)(n.h4,{id:"options-69",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The status endpoint URL (",(0,r.jsx)(n.a,{href:"https://api.example.com/core/v2/status/",children:"https://api.example.com/core/v2/status/"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"token"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["The MON_TOKEN (see ",(0,r.jsx)(n.a,{href:"https://docs.tacticalrmm.com/tipsntricks/#monitor-your-trmm-instance-via-the-built-in-monitoring-endpoint",children:"https://docs.tacticalrmm.com/tipsntricks/#monitor-your-trmm-instance-via-the-built-in-monitoring-endpoint"}),")."]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-67",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: trmm\n useProxy: true\n options:\n token: PkPVKMzbmXgeQDlJWb0WXYvsIk3JvZyadURud2cSTdMia6hUbQ\n url: https://api.example.com/core/v2/status/\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-68",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Proxied"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe2 Required"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/amidaware/tacticalrmm",children:"Tactical RMM"})," )"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": ",(0,r.jsxs)(n.em,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/amidaware/tacticalrmm",children:"Tactical RMM"})]})]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"system-resource-monitoring",children:"System Resource Monitoring"}),"\n",(0,r.jsx)(n.h3,{id:"glances",children:"Glances"}),"\n",(0,r.jsxs)(n.p,{children:["The easiest method for displaying system info and resource usage in Dashy is with ",(0,r.jsx)(n.a,{href:"https://nicolargo.github.io/glances/",children:"Glances"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Glances is a cross-platform monitoring tool developed by ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo",children:"@nicolargo"}),". It's similar to top/htop but with a ",(0,r.jsx)(n.a,{href:"https://glances.readthedocs.io/en/latest/api.html",children:"Rest API"})," and many ",(0,r.jsx)(n.a,{href:"https://glances.readthedocs.io/en/latest/gw/index.html",children:"data exporters"})," available. Under the hood, it uses ",(0,r.jsx)(n.a,{href:"https://github.com/giampaolo/psutil",children:"psutil"})," for retrieving system info."]}),"\n",(0,r.jsxs)(n.p,{children:["If you don't already have it installed, either follow the ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo/glances/blob/master/README.rst",children:"Installation Guide"})," for your system, or setup ",(0,r.jsx)(n.a,{href:"https://glances.readthedocs.io/en/latest/docker.html",children:"with Docker"}),", or use the one-line install script: ",(0,r.jsx)(n.code,{children:"curl -L https://bit.ly/glances | /bin/bash"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["If you are using Docker to run glances make sure to add the enviroment variable ",(0,r.jsx)(n.code,{children:"-e TZ = {YourTimeZone}"}),". You can get a list of valid timezones by running ",(0,r.jsx)(n.code,{children:"timedatectl list-timezones"})," on any linux system. This is needed so the graphs show the currect time."]}),"\n",(0,r.jsx)(n.p,{children:"Here an example for Docker"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:" docker run -d \\\n --name glances \\\n --restart unless-stopped \\\n -v /var/run/docker.sock:/var/run/docker.sock:ro \\\n -p 61208:61208 \\\n --pid host \\\n --privileged \\\n -e GLANCES_OPT=-w \\\n -e PUID=1000 \\\n -e PGID=1000 \\\n -e TZ=Europe/Zurich \\\n nicolargo/glances:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Glances can be launched with the ",(0,r.jsx)(n.code,{children:"glances"})," command. You'll need to run it in web server mode, using the ",(0,r.jsx)(n.code,{children:"-w"})," option for the API to be reachable. If you don't plan on using the Web UI, then you can disable it using ",(0,r.jsx)(n.code,{children:"--disable-webui"}),". See the ",(0,r.jsx)(n.a,{href:"https://glances.readthedocs.io/en/latest/cmds.html",children:"command reference docs"})," for more info."]}),"\n",(0,r.jsxs)(n.p,{children:["If Glances is running on a Windows system it is recommended to add the following arguments ",(0,r.jsx)(n.code,{children:"--disable-plugin all --enable-plugin cpu,mem,diskio,ip,network,containers,quicklook,load,fs,alert -w"})," This is due to Glances not being that stable on windows, so disabling all plugins that aren't used by Dashy widgets can save on ressources."]}),"\n",(0,r.jsx)(n.h4,{id:"options-70",children:"Options"}),"\n",(0,r.jsxs)(n.p,{children:["All Glance's based widgets require a ",(0,r.jsx)(n.code,{children:"hostname"}),". All other parameters are optional."]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"hostname"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL or IP + port to your Glances instance (without a trailing slash)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If you have setup basic auth on Glances, specify username here (defaults to ",(0,r.jsx)(n.code,{children:"glances"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If you have setup basic auth on Glances, specify password here. ",(0,r.jsx)(n.strong,{children:"Note"}),": since this password is in plaintext, it is important not to reuse it anywhere else"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiVersion"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Specify an API version, defaults to V ",(0,r.jsx)(n.code,{children:"3"}),". Note that support for older versions is limited"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"For widgets that show a time-series chart, optionally limit the number of data points returned. A higher number will show more historical results, but will take longer to load. A value between 300 - 800 is usually optimal"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["Note that if auth is configured, requests must be proxied with ",(0,r.jsx)(n.code,{children:"useProxy: true"})]}),"\n",(0,r.jsx)(n.h4,{id:"info-69",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe2 Enabled"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Host"}),": Self-Hosted (see ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo/glances",children:"GitHub - Nicolargo/Glances"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Privacy"}),": \u26ab No Policy Available"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"screenshot",children:"Screenshot"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.a,{href:"https://ibb.co/pR6dMZT",children:(0,r.jsx)(n.img,{src:"https://pixelflare.cc/alicia/dashy/monitor-board",alt:"example-screenshot"})})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"current-cpu-usage",children:"Current CPU Usage"}),"\n",(0,r.jsx)(n.p,{children:"Live-updating current CPU usage, as a combined average across all cores"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/current-cpu"})}),"\n",(0,r.jsx)(n.h4,{id:"example-68",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-current-cpu\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"current-cpu-usage-speedometer",children:"Current CPU Usage Speedometer"}),"\n",(0,r.jsx)(n.p,{children:"Speedometer styled version of the Current CPU Usage widget"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/current-cpu-speedometer"})}),"\n",(0,r.jsx)(n.h4,{id:"example-69",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-cpu-speedometer\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"cpu-usage-per-core",children:"CPU Usage Per Core"}),"\n",(0,r.jsx)(n.p,{children:"Live-updating CPU usage breakdown per core"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/cpu-per-core"})}),"\n",(0,r.jsx)(n.h4,{id:"example-70",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-current-cores\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"cpu-usage-history",children:"CPU Usage History"}),"\n",(0,r.jsx)(n.p,{children:"Recent CPU usage history, across all cores, and displayed by user and system"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://pixelflare.cc/alicia/dashy/cpu-history-chart"})}),"\n",(0,r.jsx)(n.h4,{id:"options-71",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit the number of results returned, rendering more data points will take longer to load. Defaults to ",(0,r.jsx)(n.code,{children:"100"})]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-71",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-cpu-history\n options:\n hostname: http://192.168.130.2:61208\n limit: 60\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"current-memory-usage",children:"Current Memory Usage"}),"\n",(0,r.jsx)(n.p,{children:"Real-time memory usage gauge, with more info visible on click"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/current-mem"})}),"\n",(0,r.jsx)(n.h4,{id:"example-72",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-current-mem\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"current-memory-usage-speedometer",children:"Current Memory Usage Speedometer"}),"\n",(0,r.jsx)(n.p,{children:"Speedometer styled version of the Current Memory Usage widget"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/current-mem-speedometer"})}),"\n",(0,r.jsx)(n.h4,{id:"example-73",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-mem-speedometer\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"memory-usage-history",children:"Memory Usage History"}),"\n",(0,r.jsx)(n.p,{children:"Recent memory usage chart"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://pixelflare.cc/alicia/dashy/mem-history-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"options-72",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"limit"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Limit the number of results returned, rendering more data points will take longer to load. Defaults to ",(0,r.jsx)(n.code,{children:"100"})]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-74",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-mem-history\n options:\n hostname: http://localhost:61208\n limit: 80\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"disk-space",children:"Disk Space"}),"\n",(0,r.jsx)(n.p,{children:"List connected disks, showing free / used space and other info (file system, mount point and space available)"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/diskspace-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-75",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-disk-space\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"disk-io",children:"Disk IO"}),"\n",(0,r.jsx)(n.p,{children:"Shows real-time read and write speeds and operations per sec for each disk"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/disk-io-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-76",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-disk-io\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"system-load",children:"System Load"}),"\n",(0,r.jsx)(n.p,{children:"Shows the number of processes waiting in the run-queue, averaged across all cores. Displays for past 5, 10 and 15 minutes"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/system-load-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-77",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-system-load\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"uptime",children:"Uptime"}),"\n",(0,r.jsx)(n.p,{children:"Displays the system uptime fetched from Glances."}),"\n",(0,r.jsx)(n.h4,{id:"example-78",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-uptime\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"system-load-history",children:"System Load History"}),"\n",(0,r.jsxs)(n.p,{children:["Shows recent historical system load, calculated from the number of processes waiting in the run-queue, in 1, 5 and 15 minute intervals, and averaged across all cores. Optionally specify ",(0,r.jsx)(n.code,{children:"limit"})," to set number of results returned, defaults to ",(0,r.jsx)(n.code,{children:"500"}),", max ",(0,r.jsx)(n.code,{children:"100000"}),", but the higher the number the longer the load and render times will be."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"500",src:"https://pixelflare.cc/alicia/dashy/system-load-history-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-79",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-load-history\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"network-interfaces",children:"Network Interfaces"}),"\n",(0,r.jsx)(n.p,{children:"Lists visible network interfaces, including real-time upload/ download stats"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/network-interfaces-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-80",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-network-interfaces\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"network-traffic",children:"Network Traffic"}),"\n",(0,r.jsxs)(n.p,{children:["Shows amount of data recently uploaded/ downloaded across all network interfaces. Optionally set the ",(0,r.jsx)(n.code,{children:"limit"})," option to specify number historical of data points to return"]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/network-traffic-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-81",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-network-traffic\n options:\n hostname: http://192.168.130.2:61208\n limit: 500\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"resource-usage-alerts",children:"Resource Usage Alerts"}),"\n",(0,r.jsx)(n.p,{children:"Lists recent high resource usage alerts (e.g. CPU, mem, IO, load, temp)"}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://i.ibb.co/w01NX5R/gl-alerts.png"})}),"\n",(0,r.jsx)(n.h4,{id:"example-82",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-alerts\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"ip-address",children:"IP Address"}),"\n",(0,r.jsx)(n.p,{children:"Shows public and private IP address. Note that the ip plugin is not available on all instances of Glances."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/ip-addresses-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"example-83",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-ip-address\n options:\n hostname: http://192.168.130.2:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"cpu-temp",children:"CPU Temp"}),"\n",(0,r.jsx)(n.p,{children:"Displays temperature data from system CPUs."}),"\n",(0,r.jsxs)(n.p,{children:["Note: This widget uses the ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo/glances/blob/develop/glances/plugins/glances_sensors.py",children:(0,r.jsx)(n.code,{children:"sensors"})})," plugin, which is disabled by default, and may cause ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo/glances/issues/1664#issuecomment-632063558",children:"performance issues"}),".\nYou'll need to enable the sensors plugin to use this widget, using: ",(0,r.jsx)(n.code,{children:"--enable-plugin sensors"})," when you start Glances."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/cpu-temp-glances"})}),"\n",(0,r.jsx)(n.h4,{id:"options-73",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"units"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Use ",(0,r.jsx)(n.code,{children:"C"})," to display temperatures in Celsius or ",(0,r.jsx)(n.code,{children:"F"})," to use Fahrenheit. Defaults to ",(0,r.jsx)(n.code,{children:"C"}),"."]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-84",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-cpu-temp\n options:\n hostname: http://192.168.130.2:61208\n units: C\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"gpu",children:"GPU"}),"\n",(0,r.jsx)(n.p,{children:"Displays usage, memory and temperature for each NVIDIA GPU, plus fan speed where available."}),"\n",(0,r.jsxs)(n.p,{children:["Note: This widget uses the ",(0,r.jsx)(n.a,{href:"https://github.com/nicolargo/glances/tree/develop/glances/plugins/gpu",children:(0,r.jsx)(n.code,{children:"gpu"})})," plugin, which requires the ",(0,r.jsx)(n.code,{children:"py3nvml"})," library and is limited to NVIDIA chipsets."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/glances-gpu"})}),"\n",(0,r.jsx)(n.h4,{id:"options-74",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsx)(n.tbody,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"units"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Use ",(0,r.jsx)(n.code,{children:"C"})," to display temperatures in Celsius or ",(0,r.jsx)(n.code,{children:"F"})," to use Fahrenheit. Defaults to ",(0,r.jsx)(n.code,{children:"C"}),"."]})]})})]}),"\n",(0,r.jsx)(n.h4,{id:"example-85",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-gpu\n options:\n hostname: http://192.168.130.2:61208\n units: C\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"compact-metrics",children:"Compact Metrics"}),"\n",(0,r.jsx)(n.p,{children:"A multi-system overview widget that displays CPU, memory and disk usage for multiple Glances instances in a compact table. Click on a row to see detailed metrics for that system."}),"\n",(0,r.jsx)(n.h4,{id:"options-75",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"systems"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsxs)(n.td,{children:["An array of systems to monitor, each with ",(0,r.jsx)(n.code,{children:"header"})," (display name) and ",(0,r.jsx)(n.code,{children:"url"})," (Glances base URL)"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"apiVersion"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Glances API version. Defaults to ",(0,r.jsx)(n.code,{children:"4"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If Glances is password-protected, specify the username"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"If Glances is password-protected, specify the password"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-86",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-compact-metrics\n options:\n systems:\n - header: Server 1\n url: http://192.168.1.10:61208\n - header: Server 2\n url: http://192.168.1.11:61208\n - header: NAS\n url: http://192.168.1.20:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"dynamic-widgets",children:"Dynamic Widgets"}),"\n",(0,r.jsx)(n.h3,{id:"iframe-widget",children:"Iframe Widget"}),"\n",(0,r.jsx)(n.p,{children:"Embed any webpage into your dashboard as a widget."}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/iframe"})}),"\n",(0,r.jsx)(n.h4,{id:"options-76",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The URL to the webpage to embed"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"frameHeight"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["If needed, specify height of iframe in ",(0,r.jsx)(n.code,{children:"px"}),". E.g. ",(0,r.jsx)(n.code,{children:"400"}),", defaults to auto"]})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-87",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: iframe\n options:\n url: https://fiatleak.com/\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"html-embedded-widget",children:"HTML Embedded Widget"}),"\n",(0,r.jsx)(n.p,{children:"Many websites and apps provide their own embeddable widgets. These can be used with Dashy using the Embed widget, which lets you dynamically embed and HTML, CSS or JavaScript contents."}),"\n",(0,r.jsxs)(n.p,{children:["\u26a0\ufe0f ",(0,r.jsx)(n.strong,{children:"NOTE:"})," Use with extreme caution. Embedding a script from an untrustworthy source may have serious unintended consequences."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"400",src:"https://pixelflare.cc/alicia/dashy/html-embed"})}),"\n",(0,r.jsx)(n.h4,{id:"options-77",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"html"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"HTML contents to render in the widget"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"script"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Raw JavaScript code to execute (caution)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"scriptSrc"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"A URL to JavaScript content (caution)"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"css"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Any stylings for widget contents"})]})]})]}),"\n",(0,r.jsx)(n.h4,{id:"example-88",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'- type: embed\n options:\n scriptSrc: https://cdn.speedcheck.org/basic/scbjs.min.js\n html: |\n
\n
\n \n Speedcheck\n \n
\n
\n'})}),"\n",(0,r.jsx)(n.p,{children:"Or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'- type: embed\n options:\n css: \'.coinmarketcap-currency-widget { color: var(--widget-text-color); }\'\n html: \'
\'\n scriptSrc: \'https://files.coinmarketcap.com/static/widget/currency.js\'\n'})}),"\n",(0,r.jsx)(n.p,{children:"You can also use this widget to display an image, wither locally or from a remote origin."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: embed\n options:\n html: ''\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"api-response",children:"API Response"}),"\n",(0,r.jsxs)(n.p,{children:["Display data from any HTTP/JSON API endpoint. Fetch a URL, then declaratively map response fields to labelled, formatted values.\nThis was very much inspired by, and is broadly compatible with ",(0,r.jsx)(n.a,{href:"https://gethomepage.dev/widgets/services/customapi/",children:"Homepage's customapi widget"})]}),"\n",(0,r.jsx)(n.h4,{id:"options-78",children:"Options"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"url"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"The full URL of the API endpoint to fetch"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"refreshInterval"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["How often to refetch, in milliseconds. Defaults to ",(0,r.jsx)(n.code,{children:"10000"})," (10s). If omitted, the widget-level ",(0,r.jsx)(n.code,{children:"updateInterval"})," (in seconds) is used instead"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"username"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Username for HTTP basic auth"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"password"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Password for HTTP basic auth"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"method"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["HTTP method, e.g. ",(0,r.jsx)(n.code,{children:"GET"})," (default) or ",(0,r.jsx)(n.code,{children:"POST"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"headers"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"An object of custom request headers"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"requestBody"})})}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"string"})," or ",(0,r.jsx)(n.code,{children:"object"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Request body for non-GET methods. Prefer an object. A raw string is JSON-encoded again, so don't pre-stringify"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"display"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Layout for the fields: ",(0,r.jsx)(n.code,{children:"block"})," (default, label and value on one row) or ",(0,r.jsx)(n.code,{children:"list"})," (value stacked under the label)"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"mappings"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"array"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"The fields to display from the response (see below). If omitted, the raw response root is shown"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["Each item in ",(0,r.jsx)(n.code,{children:"mappings"})," accepts:"]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Field"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Type"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Required"})}),(0,r.jsx)(n.th,{children:(0,r.jsx)(n.strong,{children:"Description"})})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"field"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Dot-path to the value, e.g. ",(0,r.jsx)(n.code,{children:"path.to.key"})," or ",(0,r.jsx)(n.code,{children:"items.0.name"}),". Omit to use the response root (useful with ",(0,r.jsx)(n.code,{children:"size"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"label"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsx)(n.td,{children:"Label shown beside the value"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"format"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["One of ",(0,r.jsx)(n.code,{children:"text"})," (default), ",(0,r.jsx)(n.code,{children:"number"}),", ",(0,r.jsx)(n.code,{children:"percent"}),", ",(0,r.jsx)(n.code,{children:"date"}),", ",(0,r.jsx)(n.code,{children:"relativeDate"})," or ",(0,r.jsx)(n.code,{children:"size"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"locale"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["Locale for ",(0,r.jsx)(n.code,{children:"number"}),"/",(0,r.jsx)(n.code,{children:"percent"}),"/",(0,r.jsx)(n.code,{children:"date"}),"/",(0,r.jsx)(n.code,{children:"relativeDate"}),", e.g. ",(0,r.jsx)(n.code,{children:"nl"}),". Defaults to the browser locale"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"dateStyle"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["For ",(0,r.jsx)(n.code,{children:"date"})," format. One of ",(0,r.jsx)(n.code,{children:"full"}),", ",(0,r.jsx)(n.code,{children:"long"})," (default), ",(0,r.jsx)(n.code,{children:"medium"}),", ",(0,r.jsx)(n.code,{children:"short"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"timeStyle"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["For ",(0,r.jsx)(n.code,{children:"date"})," format. One of ",(0,r.jsx)(n.code,{children:"full"}),", ",(0,r.jsx)(n.code,{children:"long"}),", ",(0,r.jsx)(n.code,{children:"medium"}),", ",(0,r.jsx)(n.code,{children:"short"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"style"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["For ",(0,r.jsx)(n.code,{children:"relativeDate"})," format. One of ",(0,r.jsx)(n.code,{children:"long"})," (default), ",(0,r.jsx)(n.code,{children:"short"}),", ",(0,r.jsx)(n.code,{children:"narrow"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"numeric"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["For ",(0,r.jsx)(n.code,{children:"relativeDate"})," format. One of ",(0,r.jsx)(n.code,{children:"always"})," (default) or ",(0,r.jsx)(n.code,{children:"auto"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"additionalField"})})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"object"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.em,{children:"Optional"})}),(0,r.jsxs)(n.td,{children:["A secondary value shown next to the main one. Accepts ",(0,r.jsx)(n.code,{children:"field"}),", ",(0,r.jsx)(n.code,{children:"format"})," (and its options), plus ",(0,r.jsx)(n.code,{children:"color"})," - one of ",(0,r.jsx)(n.code,{children:"theme"}),", ",(0,r.jsx)(n.code,{children:"adaptive"})," (action colour from the value's sign: positive green, negative red), ",(0,r.jsx)(n.code,{children:"black"})," or ",(0,r.jsx)(n.code,{children:"white"})]})]})]})]}),"\n",(0,r.jsx)(n.p,{children:"Notes:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"percent"})})," treats the value as an already-computed percentage, so ",(0,r.jsx)(n.code,{children:"42"})," is shown as ",(0,r.jsx)(n.code,{children:"42%"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"size"})})," returns the number of items in an array (or keys in an object) \u2014 pair it with an omitted ",(0,r.jsx)(n.code,{children:"field"})," to count the response root"]}),"\n",(0,r.jsxs)(n.li,{children:["For APIs that don't send CORS headers (most self-hosted services), set the widget-level ",(0,r.jsx)(n.code,{children:"useProxy: true"})," to route the request through Dashy's server-side proxy"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"example-89",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: customapi\n options:\n url: https://api.github.com/repos/lissy93/dashy\n refreshInterval: 60000\n mappings:\n - field: stargazers_count\n label: Stars\n format: number\n - field: open_issues_count\n label: Open Issues\n format: number\n - field: pushed_at\n label: Last Push\n format: relativeDate\n"})}),"\n",(0,r.jsx)(n.h4,{id:"info-70",children:"Info"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CORS"}),": \ud83d\udfe0 Depends on target API (use ",(0,r.jsx)(n.code,{children:"useProxy: true"})," if blocked)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Auth"}),": \ud83d\udfe0 Optional (basic auth or custom headers)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Price"}),": \ud83d\udfe2 Free"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"prometheus-data",children:"Prometheus Data"}),"\n",(0,r.jsx)(n.p,{children:"Display data from any service with a Prometheus exporter."}),"\n",(0,r.jsx)(n.p,{children:"// Coming soon..."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"data-feed",children:"Data Feed"}),"\n",(0,r.jsxs)(n.p,{children:["Show live data from an RSS-enabled service. The only required parameter is ",(0,r.jsx)(n.code,{children:"rssUrl"}),", which is the URL to the ATOM feed. See ",(0,r.jsx)(n.a,{href:"#rss-feed",children:"RSS Widget"})," for full list of available options."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{width:"700",src:"https://pixelflare.cc/alicia/dashy/data-feed"})}),"\n",(0,r.jsx)(n.h4,{id:"example-90",children:"Example"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: rss-feed\n options:\n rssUrl: https://notes.aliciasykes.com/feed\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"usage--customizations",children:"Usage & Customizations"}),"\n",(0,r.jsx)(n.h3,{id:"widget-usage-guide",children:"Widget Usage Guide"}),"\n",(0,r.jsx)(n.p,{children:"Like items, widgets are placed under sections. You may have one or more widgets per section."}),"\n",(0,r.jsx)(n.p,{children:"In your YAML config file, this will look something like:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"sections:\n- name: Today\n icon: far fa-calendar-day\n widgets:\n - type: clock\n options:\n format: en-GB\n - type: weather\n options:\n apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n city: London\n units: metric\n"})}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:'In this example, there is a single section, named "Today", using a Calendar icon from Font-Awesome. It has 2 widgets, a clock and the current weather.'}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"continuous-updates",children:"Continuous Updates"}),"\n",(0,r.jsxs)(n.p,{children:["By default, a widget which displays dynamic data from an external source, will only fetch results on page load. If you would like to keep data updated at all times, you can enable ",(0,r.jsx)(n.strong,{children:"Continuous Updates"}),". This is done by setting a time value in the ",(0,r.jsx)(n.code,{children:"updateInterval"})," field."]}),"\n",(0,r.jsxs)(n.p,{children:["The value of ",(0,r.jsx)(n.code,{children:"updateInterval"})," is optional, and is specified and seconds. It must be more than ",(0,r.jsx)(n.code,{children:"10"})," and less than ",(0,r.jsx)(n.code,{children:"7200"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"For example, the following widget displaying stats from Pi-Hole will update ever 20 seconds."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"widgets:\n- type: pi-hole-stats\n updateInterval: 20\n options:\n hostname: http://192.168.130.2\n"})}),"\n",(0,r.jsx)(n.p,{children:"Note that if you have many widgets, and set them to continuously update frequently, you will notice a hit to performance. A widget that relies on data from an external API, will also consume your usage quota faster, if set to keep updating."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"proxying-requests",children:"Proxying Requests"}),"\n",(0,r.jsx)(n.p,{children:"If a widget fails to make a data request, and the console shows a CORS error, this means the server is blocking client-side requests."}),"\n",(0,r.jsxs)(n.p,{children:["Dashy has a built-in CORS proxy (",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/services/cors-proxy.js",children:(0,r.jsx)(n.code,{children:"services/cors-proxy.js"})}),"), which will be used automatically by some widgets, or can be forced to use by other by setting the ",(0,r.jsx)(n.code,{children:"useProxy"})," option."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"widgets:\n- type: pi-hole-stats\n useProxy: true\n options:\n hostname: http://pi-hole.local\n"})}),"\n",(0,r.jsx)(n.p,{children:"Alternatively, and more securely, you can set the auth headers on your service to accept requests from Dashy. For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: https://location-of-dashy/\nVary: Origin\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"handling-secrets",children:"Handling Secrets"}),"\n",(0,r.jsxs)(n.p,{children:["Some widgets require you to pass potentially sensitive info such as API keys. The ",(0,r.jsx)(n.code,{children:"conf.yml"})," is not ideal for this, as it's stored in plaintext. Instead, for secrets you should use environment variables."]}),"\n",(0,r.jsxs)(n.p,{children:["In your widget options, set the value to the name of an environment variable starting with ",(0,r.jsx)(n.code,{children:"DASHY_"})," (or ",(0,r.jsx)(n.code,{children:"VITE_APP_"})," / ",(0,r.jsx)(n.code,{children:"VUE_APP_"})," for backwards compatibility). The Dashy server will substitute it with the matching ",(0,r.jsx)(n.code,{children:"process.env"})," value when proxying the request."]}),"\n",(0,r.jsxs)(n.p,{children:["The widget must be set to route through the Dashy server with ",(0,r.jsx)(n.code,{children:"useProxy: true"}),". Without this, the placeholder is sent directly to the upstream API and will fail. Substitution covers the request URL, headers and body, so any auth pattern (Bearer, Basic, query param, POST body) works."]}),"\n",(0,r.jsxs)(n.p,{children:["For more information about setting and managing your environment variables, see ",(0,r.jsx)(n.a,{href:"/docs/management#passing-in-environmental-variables",children:"Management Docs --\x3e Environmental Variables"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: weather\n useProxy: true\n options:\n apiKey: DASHY_WEATHER_TOKEN\n city: London\n units: metric\n hideDetails: true\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Then set ",(0,r.jsx)(n.code,{children:"DASHY_WEATHER_TOKEN='xxx'"})," in your container or local environment, and restart Dashy. To rotate the value, just update the env var and restart. No rebuild required."]}),"\n",(0,r.jsxs)(n.admonition,{type:"note",children:[(0,r.jsxs)(n.p,{children:["Only env vars starting with ",(0,r.jsx)(n.code,{children:"DASHY_"}),", ",(0,r.jsx)(n.code,{children:"VITE_APP_"})," or ",(0,r.jsx)(n.code,{children:"VUE_APP_"})," are eligible for substitution. Other server-side env vars are never exposed."]}),(0,r.jsxs)(n.p,{children:["If you build Dashy from source yourself, ",(0,r.jsx)(n.code,{children:"VITE_APP_*"})," and ",(0,r.jsx)(n.code,{children:"DASHY_*"})," vars set at build time are also baked into the bundle by Vite, which works without ",(0,r.jsx)(n.code,{children:"useProxy: true"}),"."]})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"setting-timeout",children:"Setting Timeout"}),"\n",(0,r.jsxs)(n.p,{children:["If the endpoint you are requesting data from is slow to respond, you may see a timeout error in the console. This can easily be fixed by specifying the ",(0,r.jsx)(n.code,{children:"timeout"})," property on the offending widget. This should be an integer value, in milliseconds. By default timeout is ",(0,r.jsx)(n.code,{children:"2500"})," ms (2\xbd seconds)."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-current-cpu\n timeout: 8000\n options:\n hostname: https://glances.dns-device.local\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"adding-labels",children:"Adding Labels"}),"\n",(0,r.jsxs)(n.p,{children:["If you have multiple widgets of the same type in a single section, it may not be clear what each one is. To overcome this, you can add a custom label to any given widget, using the ",(0,r.jsx)(n.code,{children:"label"})," property."]}),"\n",(0,r.jsx)(n.p,{children:"For example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- name: CPU Usage\n icon: fas fa-tachometer\n widgets:\n - type: gl-current-cpu\n label: Meida Server\n options:\n hostname: http://media-server.lan:61208\n - type: gl-current-cpu\n label: Firewall\n options:\n hostname: http://firewall.lan:61208\n - type: gl-current-cpu\n label: File Sync Server\n options:\n hostname: http://file-sync.lan:61208\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"ignoring-errors",children:"Ignoring Errors"}),"\n",(0,r.jsx)(n.p,{children:"When there's an error fetching or displaying a widgets data, then it will be highlighted in yellow, and a message displayed on the UI."}),"\n",(0,r.jsxs)(n.p,{children:["In some instances, this is a false positive, and the widget is actually functioning correctly. If this is the case, you can disable the UI error message of a given widget by setting: ",(0,r.jsx)(n.code,{children:"ignoreErrors: true"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-disk-io\n ignoreErrors: true\n options:\n hostname: https://glances.dns-device.local\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"widget-styling",children:"Widget Styling"}),"\n",(0,r.jsx)(n.p,{children:"Like elsewhere in Dashy, all colours can be easily modified with CSS variables."}),"\n",(0,r.jsx)(n.p,{children:"Widgets use the following color variables, which can be overridden if desired:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"--widget-text-color"})," - Text color, defaults to ",(0,r.jsx)(n.code,{children:"--primary"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"--widget-background-color"})," - Background color, defaults to ",(0,r.jsx)(n.code,{children:"--background-darker"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"--widget-accent-color"})," - Accent color, defaults to ",(0,r.jsx)(n.code,{children:"--background"})]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["For more info on how to apply custom variables, see the ",(0,r.jsx)(n.a,{href:"/docs/theming#setting-custom-css-in-the-ui",children:"Theming Docs"})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"customizing-charts",children:"Customizing Charts"}),"\n",(0,r.jsxs)(n.p,{children:["For widgets that contain charts, you can set an array of colors under ",(0,r.jsx)(n.code,{children:"chartColors"}),".\nTo specify the chart height, set ",(0,r.jsx)(n.code,{children:"chartHeight"})," to an integer (in ",(0,r.jsx)(n.code,{children:"px"}),"), defaults to ",(0,r.jsx)(n.code,{children:"300"}),".\nFor example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"- type: gl-load-history\n options:\n hostname: http://192.168.130.2:61208\n chartColors: ['#9b5de5', '#f15bb5', '#00bbf9', '#00f5d4']\n chartHeight: 450\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"language-translations",children:"Language Translations"}),"\n",(0,r.jsx)(n.p,{children:"Since most of the content displayed within widgets is fetched from an external API, unless that API supports multiple languages, translating dynamic content is not possible."}),"\n",(0,r.jsx)(n.p,{children:"However, any hard-coded content is translatable, and all dates and times will display in your local format."}),"\n",(0,r.jsxs)(n.p,{children:["For more info about multi-language support, see the ",(0,r.jsx)(n.a,{href:"/docs/multi-language-support",children:"Internationalization Docs"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"widget-ui-options",children:"Widget UI Options"}),"\n",(0,r.jsx)(n.p,{children:"Widgets can be opened in full-page view, by clicking the Arrow icon (top-right). The URL in your address bar will also update, and visiting that web address directly will take you straight to that widget."}),"\n",(0,r.jsx)(n.p,{children:"You can reload the data of any widget, by clicking the Refresh Data icon (also in top-right). This will only affect the widget where the action was triggered from."}),"\n",(0,r.jsxs)(n.p,{children:["All ",(0,r.jsx)(n.a,{href:"/docs/configuring#section",children:"config options"})," that can be applied to sections, can also be applied to widget sections. For example, to make a widget section double the width, set ",(0,r.jsx)(n.code,{children:"displayData.cols: 2"})," within the parent section. You can collapse a widget (by clicking the section title), and collapse state will be saved locally."]}),"\n",(0,r.jsxs)(n.p,{children:["Widgets cannot currently be edited through the UI. This feature is in development, and will be released soon. In the meantime, you can either use the JSON config editor, or use ",(0,r.jsx)(n.a,{href:"https://github.com/coder/code-server",children:"VS Code Server"}),", or just SSH into your box and edit the conf.yml file directly."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"build-your-own-widget",children:"Build your own Widget"}),"\n",(0,r.jsx)(n.p,{children:"Widgets are built in a modular fashion, making it easy for anyone to create their own custom components."}),"\n",(0,r.jsxs)(n.p,{children:["For a full tutorial on creating your own widget, you can follow ",(0,r.jsx)(n.a,{href:"/docs/development-guides#building-a-widget",children:"this guide"}),", or take a look at ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/commit/3da76ce2999f57f76a97454c0276301e39957b8e",children:"here"})," for a code example."]}),"\n",(0,r.jsxs)(n.p,{children:["Alternatively, for displaying simple data, you could also just use the either the ",(0,r.jsx)(n.a,{href:"#iframe-widget",children:"iframe"}),", ",(0,r.jsx)(n.a,{href:"#html-embedded-widget",children:"embed"}),", ",(0,r.jsx)(n.a,{href:"#data-feed",children:"data feed"})," or ",(0,r.jsx)(n.a,{href:"#api-response",children:"API response"})," widgets."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"requesting-a-widget",children:"Requesting a Widget"}),"\n",(0,r.jsx)(n.p,{children:"Suggestions for widget ideas are welcome. But there is no guarantee that I will build your widget idea."}),"\n",(0,r.jsx)(n.p,{children:"Please only request widgets for services that:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Have a publicly accessible API"}),"\n",(0,r.jsx)(n.li,{children:"Are CORS and HTTPS enabled"}),"\n",(0,r.jsx)(n.li,{children:"Are free to use, or have a free plan"}),"\n",(0,r.jsx)(n.li,{children:"Allow for use in their Terms of Service"}),"\n",(0,r.jsx)(n.li,{children:"Would be useful for other users"}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["You can suggest a widget ",(0,r.jsx)(n.a,{href:"https://git.io/Jygo3",children:"here"}),", please star the repo before submitting a ticket. If you are a monthly GitHub sponsor, I will happily build out a custom widget for any service that meets the above criteria, usually within 2 weeks of initial request."]}),"\n",(0,r.jsxs)(n.p,{children:["For services that are not officially supported, it is likely still possible to display data using either the ",(0,r.jsx)(n.a,{href:"#iframe-widget",children:"iframe"}),", ",(0,r.jsx)(n.a,{href:"#html-embedded-widget",children:"embed"})," or ",(0,r.jsx)(n.a,{href:"#api-response",children:"API response"})," widgets. For more advanced features, like charts and action buttons, you could also build your own widget, using ",(0,r.jsx)(n.a,{href:"/docs/development-guides#building-a-widget",children:"this tutorial"}),", it's fairly straight forward, and you can use an ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/src/components/Widgets",children:"existing widget"})," (or ",(0,r.jsx)(n.a,{href:"https://git.io/JygKI",children:"this example"}),") as a template."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"troubleshooting-widget-errors",children:"Troubleshooting Widget Errors"}),"\n",(0,r.jsxs)(n.p,{children:["If an error occurs when fetching or rendering results, you will see a short message in the UI. If that message doesn't adequately explain the problem, then you can ",(0,r.jsx)(n.a,{href:"/docs/troubleshooting#how-to-open-browser-console",children:"open the browser console"})," to see more details."]}),"\n",(0,r.jsx)(n.p,{children:"Before proceeding, ensure that if the widget requires auth your API is correct, and for custom widgets, double check that the URL and protocol is correct."}),"\n",(0,r.jsx)(n.p,{children:"If you're able to, you can find more information about why the request may be failing in the Dev Tools under the Network tab, and you can ensure your endpoint is correct and working using a tool like Postman."}),"\n",(0,r.jsx)(n.h4,{id:"cors-errors",children:"CORS Errors"}),"\n",(0,r.jsxs)(n.p,{children:["The most common issue is a CORS error. This is a browser security mechanism which prevents the client-side app (Dashy) from from accessing resources on a remote origin, without that server's explicit permission (e.g. with headers like Access-Control-Allow-Origin). See the MDN Docs for more info: ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"Cross-Origin Resource Sharing"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"There are several ways to fix a CORS error:"}),"\n",(0,r.jsx)(n.h4,{id:"option-1---ensure-correct-protocol",children:"Option 1 - Ensure Correct Protocol"}),"\n",(0,r.jsx)(n.p,{children:"You will get a CORS error if you try and access a http service from a https source. So ensure that the URL you are requesting has the right protocol, and is correctly formatted."}),"\n",(0,r.jsx)(n.h4,{id:"option-2---set-headers",children:"Option 2 - Set Headers"}),"\n",(0,r.jsxs)(n.p,{children:["If you have control over the destination (e.g. for a self-hosted service), then you can simply apply the correct headers.\nAdd the ",(0,r.jsx)(n.code,{children:"Access-Control-Allow-Origin"})," header, with the value of either ",(0,r.jsx)(n.code,{children:"*"})," to allow requests from anywhere, or more securely, the host of where Dashy is served from. For example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: https://url-of-dashy.local\n"})}),"\n",(0,r.jsx)(n.p,{children:"or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: *\n"})}),"\n",(0,r.jsx)(n.h4,{id:"option-3---proxying-request",children:"Option 3 - Proxying Request"}),"\n",(0,r.jsxs)(n.p,{children:["You can route requests through Dashy's built-in CORS proxy. Instructions and more details can be found ",(0,r.jsx)(n.a,{href:"#proxying-requests",children:"here"}),". If you don't have control over the target origin, and you are running Dashy either through Docker, with the Node server or on Netlify, then this solution will work for you."]}),"\n",(0,r.jsxs)(n.p,{children:["Just add the ",(0,r.jsx)(n.code,{children:"useProxy: true"})," option to the failing widget."]}),"\n",(0,r.jsx)(n.h4,{id:"option-4---use-a-plugin",children:"Option 4 - Use a plugin"}),"\n",(0,r.jsxs)(n.p,{children:["For testing purposes, you can use an addon, which will disable the CORS checks. You can get the Allow-CORS extension for ",(0,r.jsx)(n.a,{href:"https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?hl=en-US",children:"Chrome"})," or ",(0,r.jsx)(n.a,{href:"https://addons.mozilla.org/en-US/firefox/addon/access-control-allow-origin/",children:"Firefox"}),", more details ",(0,r.jsx)(n.a,{href:"https://mybrowseraddon.com/access-control-allow-origin.html",children:"here"})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"raising-an-issue",children:"Raising an Issue"}),"\n",(0,r.jsxs)(n.p,{children:["If you need to submit a bug report for a failing widget, then please include the full console output (see ",(0,r.jsx)(n.a,{href:"/docs/troubleshooting#how-to-open-browser-console",children:"how"}),") as well as the relevant parts of your config file. Before sending the request, ensure you've read the docs. If you're new to GitHub, an haven't previously contributed to the project, then please fist star the repo to avoid your ticket being closed by the anti-spam bot."]})]})}function x(e={}){const{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(o,{...e})}):o(e)}},28453(e,n,s){s.d(n,{R:()=>l,x:()=>t});var i=s(96540);const r={},d=i.createContext(r);function l(e){const n=i.useContext(d);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),i.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/9d9f8394.bf258cb4.js b/assets/js/9d9f8394.443c5822.js similarity index 99% rename from assets/js/9d9f8394.bf258cb4.js rename to assets/js/9d9f8394.443c5822.js index e60056b1..65982c23 100644 --- a/assets/js/9d9f8394.bf258cb4.js +++ b/assets/js/9d9f8394.443c5822.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[9013],{77309(e,n,s){s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>a,default:()=>c,frontMatter:()=>t,metadata:()=>i,toc:()=>d});const i=JSON.parse('{"id":"troubleshooting","title":"Troubleshooting","description":"**This document contains common problems and their solutions.**","source":"@site/docs/troubleshooting.md","sourceDirName":".","slug":"/troubleshooting","permalink":"/docs/troubleshooting","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/troubleshooting.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"App Management","permalink":"/docs/management"},"next":{"title":"Icons","permalink":"/docs/icons"}}');var r=s(74848),o=s(28453);const t={},a="Troubleshooting",l={},d=[{value:"Contents",id:"contents",level:2},{value:"Config not saving",id:"config-not-saving",level:2},{value:"Permission denied or read-only filesystem (EACCES, EROFS)",id:"permission-denied-or-read-only-filesystem-eacces-erofs",level:3},{value:"Kubernetes ConfigMap mount is read-only",id:"kubernetes-configmap-mount-is-read-only",level:3},{value:"SELinux or AppArmor blocks the write",id:"selinux-or-apparmor-blocks-the-write",level:3},{value:"Backup step fails so save aborts",id:"backup-step-fails-so-save-aborts",level:3},{value:"Save button is missing or returns 403 Forbidden",id:"save-button-is-missing-or-returns-403-forbidden",level:3},{value:"Save unavailable on Vercel, Netlify or other static hosts",id:"save-unavailable-on-vercel-netlify-or-other-static-hosts",level:3},{value:"/config-manager/save returns 404 or HTML",id:"config-managersave-returns-404-or-html",level:3},{value:""Invalid filename" when saving a sub-page",id:"invalid-filename-when-saving-a-sub-page",level:3},{value:""Cannot save to an external URL"",id:"cannot-save-to-an-external-url",level:3},{value:"Saved successfully but the UI shows the old config",id:"saved-successfully-but-the-ui-shows-the-old-config",level:3},{value:"Container crashes or restart loop after saving (3.1.0 and 3.1.1 only)",id:"container-crashes-or-restart-loop-after-saving-310-and-311-only",level:3},{value:"Intentionally read-only mode",id:"intentionally-read-only-mode",level:3},{value:"Refused to Connect in Modal or Workspace View",id:"refused-to-connect-in-modal-or-workspace-view",level:2},{value:"NGINX",id:"nginx",level:3},{value:"Caddy",id:"caddy",level:3},{value:"Apache",id:"apache",level:3},{value:"LightHttpd",id:"lighthttpd",level:3},{value:"404 / Routing issues",id:"404--routing-issues",level:2},{value:"404 On Static Hosting",id:"404-on-static-hosting",level:3},{value:"404 after Launch from Mobile Home Screen",id:"404-after-launch-from-mobile-home-screen",level:3},{value:"404 On Multi-Page Apps",id:"404-on-multi-page-apps",level:3},{value:"Dashy hosted at a sub-path (e.g. example.com/dashy)",id:"dashy-hosted-at-a-sub-path-eg-examplecomdashy",level:3},{value:"Sub-pages",id:"sub-pages",level:2},{value:"Sub-page shows "Unable to find config for ..."",id:"sub-page-shows-unable-to-find-config-for-",level:3},{value:"Old bookmark from before an upgrade",id:"old-bookmark-from-before-an-upgrade",level:4},{value:"The page was renamed or removed",id:"the-page-was-renamed-or-removed",level:4},{value:"The path points at an unreachable file",id:"the-path-points-at-an-unreachable-file",level:4},{value:"Page name literally "Main"",id:"page-name-literally-main",level:4},{value:"Service worker is serving a stale app",id:"service-worker-is-serving-a-stale-app",level:4},{value:"Sub-page missing from nav, or won't open when clicked",id:"sub-page-missing-from-nav-or-wont-open-when-clicked",level:3},{value:"Sub-page ignores its theme, layout or appConfig",id:"sub-page-ignores-its-theme-layout-or-appconfig",level:3},{value:"Sub-config files return 404",id:"sub-config-files-return-404",level:3},{value:"Remote Config Not Loading",id:"remote-config-not-loading",level:3},{value:"Build & memory errors",id:"build--memory-errors",level:2},{value:"Yarn Error",id:"yarn-error",level:3},{value:"yarn build fails inside the container",id:"yarn-build-fails-inside-the-container",level:3},{value:"High CPU or RAM Usage on Startup",id:"high-cpu-or-ram-usage-on-startup",level:3},{value:"Ineffective mark-compacts near heap limit Allocation failed",id:"ineffective-mark-compacts-near-heap-limit-allocation-failed",level:3},{value:"Command failed with signal "SIGKILL"",id:"command-failed-with-signal-sigkill",level:3},{value:"Node Sass does not yet support your current environment",id:"node-sass-does-not-yet-support-your-current-environment",level:3},{value:"Unreachable Code Error",id:"unreachable-code-error",level:3},{value:"Error: Cannot find module './_baseValues'",id:"error-cannot-find-module-_basevalues",level:3},{value:"Auth & OIDC",id:"auth--oidc",level:2},{value:"Auth Validation Error: "should be object"",id:"auth-validation-error-should-be-object",level:3},{value:"Keycloak Redirect Error",id:"keycloak-redirect-error",level:3},{value:"OIDC or Keycloak failure on numeric client IDs",id:"oidc-or-keycloak-failure-on-numeric-client-ids",level:3},{value:"Redirect loop after login",id:"redirect-loop-after-login",level:3},{value:"invalid_redirect_uri",id:"invalid_redirect_uri",level:3},{value:"Login works in the browser but the dashboard refuses to save anything (403)",id:"login-works-in-the-browser-but-the-dashboard-refuses-to-save-anything-403",level:3},{value:"Logged in but no admin controls",id:"logged-in-but-no-admin-controls",level:3},{value:"Login works but Dashy errors on the callback with "OIDC signinCallback returned no user"",id:"login-works-but-dashy-errors-on-the-callback-with-oidc-signincallback-returned-no-user",level:3},{value:"Sign-out leaves you stuck on Authentik",id:"sign-out-leaves-you-stuck-on-authentik",level:3},{value:"Untrusted certificate from Authentik",id:"untrusted-certificate-from-authentik",level:3},{value:"Numeric client_id getting truncated",id:"numeric-client_id-getting-truncated",level:3},{value:"Header auth: "Unauthorized - not from trusted proxy"",id:"header-auth-unauthorized---not-from-trusted-proxy",level:3},{value:"Header auth: "Unauthorized - missing user header"",id:"header-auth-unauthorized---missing-user-header",level:3},{value:""Invalid user object" warning on startup with an all-digit hash or placeholder",id:"invalid-user-object-warning-on-startup-with-an-all-digit-hash-or-placeholder",level:3},{value:"OIDC login looks like a CORS error in devtools, but the IdP is configured for CORS correctly",id:"oidc-login-looks-like-a-cors-error-in-devtools-but-the-idp-is-configured-for-cors-correctly",level:3},{value:"Docker & image issues",id:"docker--image-issues",level:2},{value:"App Not Starting After Update to 2.0.4",id:"app-not-starting-after-update-to-204",level:3},{value:"Mount Type Mismatch",id:"mount-type-mismatch",level:3},{value:"DockerHub toomanyrequests",id:"dockerhub-toomanyrequests",level:3},{value:"Solution 1 - Use an alternate container registry",id:"solution-1---use-an-alternate-container-registry",level:4},{value:"Solution 2 - Increase your rate limits",id:"solution-2---increase-your-rate-limits",level:4},{value:"Old image tags fail to pull",id:"old-image-tags-fail-to-pull",level:3},{value:"Healthcheck Failing in Docker",id:"healthcheck-failing-in-docker",level:3},{value:"SSL-enabled Dashy",id:"ssl-enabled-dashy",level:4},{value:"Custom port",id:"custom-port",level:4},{value:"Container is unhealthy past the grace period",id:"container-is-unhealthy-past-the-grace-period",level:4},{value:"Docker Login Fails on Ubuntu",id:"docker-login-fails-on-ubuntu",level:3},{value:"Styles and Assets not Updating",id:"styles-and-assets-not-updating",level:2},{value:"Config Validation Errors",id:"config-validation-errors",level:2},{value:"Invalid Host Header while running through ngrok",id:"invalid-host-header-while-running-through-ngrok",level:2},{value:"Warnings in the Console during deploy",id:"warnings-in-the-console-during-deploy",level:2},{value:"Status Checks Failing",id:"status-checks-failing",level:2},{value:"Widgets",id:"widgets",level:2},{value:"Widget Errors",id:"widget-errors",level:3},{value:"Find Error Message",id:"find-error-message",level:4},{value:"Check Config",id:"check-config",level:4},{value:"Timeout Error",id:"timeout-error",level:4},{value:"CORS error",id:"cors-error",level:4},{value:"More Info",id:"more-info",level:4},{value:"Widget CORS Errors",id:"widget-cors-errors",level:3},{value:"Option 1 - Ensure Correct Protocol",id:"option-1---ensure-correct-protocol",level:4},{value:"Option 2 - Set Headers",id:"option-2---set-headers",level:4},{value:"Option 3 - Proxying Request",id:"option-3---proxying-request",level:4},{value:"Option 4 - Use a plugin",id:"option-4---use-a-plugin",level:4},{value:"CORS Proxy connect ECONNREFUSED ... or getaddrinfo ENOTFOUND ...",id:"cors-proxy-connect-econnrefused--or-getaddrinfo-enotfound-",level:3},{value:"CORS Proxy Target-URL host '...' is blocked / must use http:// or https://",id:"cors-proxy-target-url-host--is-blocked--must-use-http-or-https",level:3},{value:"Widget Shows Error Incorrectly",id:"widget-shows-error-incorrectly",level:3},{value:"Weather Forecast Widget 401",id:"weather-forecast-widget-401",level:3},{value:"Widget Displaying Inaccurate Data",id:"widget-displaying-inaccurate-data",level:3},{value:"Public IP Widget not working for ipinfo or ipquery providers",id:"public-ip-widget-not-working-for-ipinfo-or-ipquery-providers",level:3},{value:"Font Awesome Icons not Displaying",id:"font-awesome-icons-not-displaying",level:2},{value:"Copy to Clipboard not Working",id:"copy-to-clipboard-not-working",level:2},{value:"How-To / Reference",id:"how-to--reference",level:2},{value:"How to Reset Local Settings",id:"how-to-reset-local-settings",level:3},{value:"How to make a bug report",id:"how-to-make-a-bug-report",level:3},{value:"Step 1 - Where to open issues",id:"step-1---where-to-open-issues",level:4},{value:"Step 2 - Checking it's not already covered",id:"step-2---checking-its-not-already-covered",level:4},{value:"Step 3 - Describe the Issue",id:"step-3---describe-the-issue",level:4},{value:"Step 4 - Provide Supporting Info",id:"step-4---provide-supporting-info",level:4},{value:"Step 5 - Fix Released",id:"step-5---fix-released",level:4},{value:"How-To Open Browser Console",id:"how-to-open-browser-console",level:3},{value:"Git Contributions not Displaying",id:"git-contributions-not-displaying",level:3}];function h(e){const n={a:"a",blockquote:"blockquote",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",kbd:"kbd",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"troubleshooting",children:"Troubleshooting"})}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.em,{children:(0,r.jsx)(n.strong,{children:"This document contains common problems and their solutions."})}),(0,r.jsx)(n.br,{}),"\nPlease ensure your issue isn't listed here, before opening a new ticket."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"Found something not listed here? Consider adding it, to help other users."})}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#config-not-saving",children:"Config not saving"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#permission-denied-or-read-only-filesystem-eacces-erofs",children:"Permission denied or read-only filesystem"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#kubernetes-configmap-mount-is-read-only",children:"Kubernetes ConfigMap mount is read-only"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#selinux-or-apparmor-blocks-the-write",children:"SELinux or AppArmor blocks the write"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#backup-step-fails-so-save-aborts",children:"Backup step fails so save aborts"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#save-button-is-missing-or-returns-403-forbidden",children:"Save button is missing or returns 403"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#save-unavailable-on-vercel-netlify-or-other-static-hosts",children:"Save unavailable on Vercel, Netlify or other static hosts"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#config-managersave-returns-404-or-html",children:"/config-manager/save returns 404 or HTML"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#invalid-filename-when-saving-a-sub-page",children:'"Invalid filename" when saving a sub-page'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cannot-save-to-an-external-url",children:'"Cannot save to an external URL"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#saved-successfully-but-the-ui-shows-the-old-config",children:"Saved successfully but the UI shows the old config"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#container-crashes-or-restart-loop-after-saving-310-and-311-only",children:"Container crashes or restart loop after saving"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#intentionally-read-only-mode",children:"Intentionally read-only mode"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#refused-to-connect-in-modal-or-workspace-view",children:"Refused to Connect in Web Content View"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#404--routing-issues",children:"404 / Routing issues"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#404-on-static-hosting",children:"404 On Static Hosting"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#404-after-launch-from-mobile-home-screen",children:"404 from Mobile Home Screen"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#404-on-multi-page-apps",children:"404 On Multi-Page Apps"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#dashy-hosted-at-a-sub-path-eg-examplecomdashy",children:"Dashy hosted at a sub-path"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#sub-pages",children:"Sub-pages"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sub-page-shows-unable-to-find-config-for",children:'Sub-page shows "Unable to find config for ..."'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sub-page-missing-from-nav-or-wont-open-when-clicked",children:"Sub-page missing from nav, or won't open when clicked"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sub-page-ignores-its-theme-layout-or-appconfig",children:"Sub-page ignores its theme, layout or appConfig"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sub-config-files-return-404",children:"Sub-config files return 404"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#remote-config-not-loading",children:"Remote Config Not Loading"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#build--memory-errors",children:"Build & memory errors"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#yarn-error",children:"Yarn Build or Run Error"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsxs)(n.a,{href:"#yarn-build-fails-inside-the-container",children:[(0,r.jsx)(n.code,{children:"yarn build"})," fails inside the container"]})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#high-cpu-or-ram-usage-on-startup",children:"High CPU or RAM Usage on Startup"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ineffective-mark-compacts-near-heap-limit-allocation-failed",children:"Heap limit Allocation failed"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#command-failed-with-signal-sigkill",children:'Command failed with signal "SIGKILL"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#node-sass-does-not-yet-support-your-current-environment",children:"Node Sass unsupported environment"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#unreachable-code-error",children:"Unreachable Code Error"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#error-cannot-find-module-_basevalues",children:"Cannot find module './_baseValues'"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#auth--oidc",children:"Auth & OIDC"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#auth-validation-error-should-be-object",children:'Auth Validation Error: "should be object"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#keycloak-redirect-error",children:"Keycloak Redirect Error"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#oidc-or-keycloak-failure-on-numeric-client-ids",children:"OIDC or Keycloak failure on numeric client IDs"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#redirect-loop-after-login",children:"Redirect loop after login"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#invalid_redirect_uri",children:"invalid_redirect_uri"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#login-works-in-the-browser-but-the-dashboard-refuses-to-save-anything-403",children:"Login works in the browser but the dashboard refuses to save anything (403)"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#logged-in-but-no-admin-controls",children:"Logged in but no admin controls"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#login-works-but-dashy-errors-on-the-callback-with-oidc-signincallback-returned-no-user",children:'Login works but Dashy errors on the callback with "OIDC signinCallback returned no user"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sign-out-leaves-you-stuck-on-authentik",children:"Sign-out leaves you stuck on Authentik"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#untrusted-certificate-from-authentik",children:"Untrusted certificate from Authentik"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#numeric-client_id-getting-truncated",children:"Numeric client_id getting truncated"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#header-auth-unauthorized---not-from-trusted-proxy",children:'Header auth: "not from trusted proxy"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#header-auth-unauthorized---missing-user-header",children:'Header auth: "missing user header"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#invalid-user-object-warning-on-startup-with-an-all-digit-hash-or-placeholder",children:"Invalid user object with all-digit hash"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#oidc-login-looks-like-a-cors-error-in-devtools-but-the-idp-is-configured-for-cors-correctly",children:"OIDC login fails with CORS-shaped error from untrusted cert"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#docker--image-issues",children:"Docker & image issues"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#app-not-starting-after-update-to-204",children:"App Not Starting After Update to 2.0.4"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#mount-type-mismatch",children:"Mount Type Mismatch"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#dockerhub-toomanyrequests",children:"DockerHub toomanyrequests"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#old-image-tags-fail-to-pull",children:"Old image tags fail to pull"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#healthcheck-failing-in-docker",children:"Healthcheck Failing in Docker"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#docker-login-fails-on-ubuntu",children:"Docker Login Fails on Ubuntu"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#styles-and-assets-not-updating",children:"Styles and Assets not Updating"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#config-validation-errors",children:"Config Validation Errors"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#invalid-host-header-while-running-through-ngrok",children:"Ngrok Invalid Host Headers"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#warnings-in-the-console-during-deploy",children:"Warnings in the Console during deploy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#status-checks-failing",children:"Status Checks Failing"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#widgets",children:"Widgets"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-errors",children:"Diagnosing Widget Errors"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-cors-errors",children:"Fixing Widget CORS Errors"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cors-proxy-connect-econnrefused--or-getaddrinfo-enotfound",children:"CORS Proxy connect ECONNREFUSED or ENOTFOUND"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cors-proxy-target-url-host--is-blocked--must-use-http-or-https",children:"CORS Proxy Target-URL host blocked or scheme rejected"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-shows-error-incorrectly",children:"Widget Shows Error Incorrectly"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#weather-forecast-widget-401",children:"Weather Forecast Widget 401"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-displaying-inaccurate-data",children:"Widget Displaying Inaccurate Data"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#public-ip-widget-not-working-for-ipinfo-or-ipquery-providers",children:"Public IP Widget not working for ipinfo or ipquery providers"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#font-awesome-icons-not-displaying",children:"Font Awesome Icons not Displaying"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#copy-to-clipboard-not-working",children:"Copy to Clipboard not Working"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#how-to--reference",children:"How-To / Reference"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#how-to-reset-local-settings",children:"How to Reset Local Settings"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#how-to-make-a-bug-report",children:"How to make a bug report"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"How-To Open Browser Console"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#git-contributions-not-displaying",children:"Git Contributions not Displaying"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"config-not-saving",children:"Config not saving"}),"\n",(0,r.jsxs)(n.p,{children:["There should be an error message, explaining the reason the config save failed. First check ",(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"browser console"})," (",(0,r.jsx)(n.kbd,{children:"F12"})," --\x3e Console), and then your server-side logs in the terminal. Then, see the following sections for solutions to each possible error."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{id:"config-not-saving-on-vercel--netlify--cdn"}),"\n",(0,r.jsx)(n.a,{id:"possible-issue-1-unable-to-call-save-endpoint-from-cdnstatic-server"}),"\n",(0,r.jsx)(n.a,{id:"unable-to-write-confyml-eacces-permission-denied"}),"\n",(0,r.jsx)(n.a,{id:"permission-denied-saving-config"}),"\n",(0,r.jsx)(n.a,{id:"possible-issue-2-unable-to-save"}),"\n",(0,r.jsx)(n.a,{id:"config-not-updating"}),"\n",(0,r.jsx)(n.a,{id:"config-still-not-updating"}),"\n",(0,r.jsx)(n.a,{id:"possible-issue-3-saved-but-not-updating"})]}),"\n",(0,r.jsx)(n.h3,{id:"permission-denied-or-read-only-filesystem-eacces-erofs",children:"Permission denied or read-only filesystem (EACCES, EROFS)"}),"\n",(0,r.jsxs)(n.p,{children:["The container can't write to your ",(0,r.jsx)(n.code,{children:"conf.yml"})," or its directory. Almost always an ownership mismatch: the host directory belongs to a different uid than the one Dashy runs as inside the container. Less commonly a read-only mount or an over-strict file mode."]}),"\n",(0,r.jsxs)(n.p,{children:["Dashy runs as UID=1000 (default non-root node user). You can see this by running ",(0,r.jsx)(n.code,{children:"docker exec -it dashy id"}),". Then, check who owns the user-data directory, with: ",(0,r.jsx)(n.code,{children:"docker exec -it dashy ls -la /app/user-data"})," - if it's not ",(0,r.jsx)(n.code,{children:"1000"})," then that's the issue. And the solution is just to run ",(0,r.jsx)(n.code,{children:"sudo chown -R 1000:1000 /path/to/your/user-data"})," to set the right owner."]}),"\n",(0,r.jsx)(n.p,{children:"Fixes:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Hand the directory to uid 1000"})," (recommended). Keeps the container running as a non-root user, which is how Dashy is built to run ",(0,r.jsx)(n.code,{children:"sudo chown -R 1000:1000 /path/to/your/user-data"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Run the container as your own user"})," if ",(0,r.jsx)(n.code,{children:"chown"})," isn't practical (multi-user hosts, NAS appliances, host directories you don't want relabelled). Add ",(0,r.jsx)(n.code,{children:"--user $(id -u):$(id -g)"})," to ",(0,r.jsx)(n.code,{children:"docker run"}),", or set ",(0,r.jsx)(n.code,{children:'user: "1000:1000"'})," (or your host uid:gid) on the service in ",(0,r.jsx)(n.code,{children:"docker-compose.yml"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Loosen a single-file mount"})," if its mode is ",(0,r.jsx)(n.code,{children:"444"}),". Narrow case, only fixes that one symptom: ",(0,r.jsx)(n.code,{children:"chmod 644 /path/to/conf.yml"})]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Common mistakes"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Using uid/gid 1001."})," A common guess on Synology, Unraid and similar where 1001 is the host's first user. Dashy's container is 1000, not 1001."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"chmod"})," alone for a UID mismatch."]})," Loosens permissions but doesn't change who owns the file. You need ",(0,r.jsx)(n.code,{children:"chown"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"chmod -R 777"})," or ",(0,r.jsx)(n.code,{children:"775"}),"."]})," Works as a workaround, masks the real problem, weakens security. Use ",(0,r.jsx)(n.code,{children:"chown"})," to the right uid instead."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Other gotchas:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Named Docker volumes"})," (created with ",(0,r.jsx)(n.code,{children:"docker volume create"}),") inherit ownership from whatever first writes to them. If an older container set them up as root, the diagnose step will show that. Recreate the volume or ",(0,r.jsx)(n.code,{children:"chown"})," the underlying directory under ",(0,r.jsx)(n.code,{children:"/var/lib/docker/volumes/"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"macOS hosts"})," rarely hit this. Docker Desktop transparently maps host uid to container uid through its VM. If saves are failing on macOS, look elsewhere first."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Storage layers that ignore POSIX permissions"})," (some NAS app-data folders use FUSE, SMB or overlay mounts where ",(0,r.jsx)(n.code,{children:"chmod"})," and ",(0,r.jsx)(n.code,{children:"chgrp"})," are silent no-ops). Bind-mount user-data from a native filesystem path instead."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"kubernetes-configmap-mount-is-read-only",children:"Kubernetes ConfigMap mount is read-only"}),"\n",(0,r.jsxs)(n.p,{children:["If you've mounted your ",(0,r.jsx)(n.code,{children:"conf.yml"})," from a ConfigMap, writes will always fail with ",(0,r.jsx)(n.code,{children:"EROFS"})," regardless of UID. ConfigMap volumes are read-only by design. Either treat the ConfigMap as the source of truth and edit it directly (saves through the UI won't work), or use a writable volume type like a ",(0,r.jsx)(n.code,{children:"PersistentVolumeClaim"})," for ",(0,r.jsx)(n.code,{children:"user-data/"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"selinux-or-apparmor-blocks-the-write",children:"SELinux or AppArmor blocks the write"}),"\n",(0,r.jsxs)(n.p,{children:["If you're on RHEL/Fedora, or systems with SELinux or AppArmour, and you've confirmed permissions are fine, and container's UID matches the host owner, but you still see ",(0,r.jsx)(n.code,{children:"EACCES"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["For SELinux, add the ",(0,r.jsx)(n.code,{children:":Z"})," flag to your volume mount so Docker relabels it for the container (e.g. ",(0,r.jsx)(n.code,{children:"volumes: [ './user-data:/app/user-data:Z' ]"}),")"]}),"\n",(0,r.jsxs)(n.p,{children:["For AppArmor, check ",(0,r.jsx)(n.code,{children:"dmesg"})," for ",(0,r.jsx)(n.code,{children:'apparmor="DENIED"'})," lines and adjust the profile. Disabling enforcement is a last resort."]}),"\n",(0,r.jsx)(n.h3,{id:"backup-step-fails-so-save-aborts",children:"Backup step fails so save aborts"}),"\n",(0,r.jsxs)(n.p,{children:["Before each save, Dashy backs up the current ",(0,r.jsx)(n.code,{children:"conf.yml"})," to ",(0,r.jsx)(n.code,{children:"user-data/config-backups/"}),". If that folder can't be written, the whole save aborts with ",(0,r.jsx)(n.code,{children:"Unable to backup conf.yml"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"Two ways out:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Point ",(0,r.jsx)(n.code,{children:"BACKUP_DIR"})," at a writable path"]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.code,{children:"DISABLE_CONFIG_BACKUPS=true"})," to skip the backup step entirely"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"save-button-is-missing-or-returns-403-forbidden",children:"Save button is missing or returns 403 Forbidden"}),"\n",(0,r.jsxs)(n.p,{children:["Have you got auth setup? If so, make sure you are logged in as an admin, or set ",(0,r.jsx)(n.code,{children:"type: admin"})," to your user in ",(0,r.jsx)(n.code,{children:"conf.yml"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Beyond that, there's several other config options which prevent saving the config file, so if you didn't mean to add them, just remove from ",(0,r.jsx)(n.code,{children:"conf.yml"})]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"appConfig.preventWriteToDisk: true"})})," disables disk save and the button"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"appConfig.preventLocalSave: true"})}),' disables the "Local" save option']}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"appConfig.disableConfiguration: true"})})," hides the editor entirely. ",(0,r.jsx)(n.code,{children:"disableConfigurationForNonAdmin: true"})," does the same just for non-admins."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"save-unavailable-on-vercel-netlify-or-other-static-hosts",children:"Save unavailable on Vercel, Netlify or other static hosts"}),"\n",(0,r.jsx)(n.p,{children:'Updating source config file on static hosts is not possible, since they have no Node server, nor have write access to modify any files.\nThe "Local" save mode will still work (changes are just persisted in your browser), but the real solution is to copy/export the updated YAML and replace it in the source config file in your repo.'}),"\n",(0,r.jsxs)(n.p,{children:["Related: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1465",children:"#1465"}),"."]}),"\n",(0,r.jsxs)(n.h3,{id:"config-managersave-returns-404-or-html",children:[(0,r.jsx)(n.code,{children:"/config-manager/save"})," returns 404 or HTML"]}),"\n",(0,r.jsxs)(n.p,{children:["How are you running/serving Dashy?\nIf you've got a reverse proxy which only forwards specific path prefixes then maybe you're missing the ",(0,r.jsx)(n.code,{children:"/config-manager/*"})," API endpoints?\nCheck the failed request in the browser's Network tab. If the response is HTML (a proxy error page) or a plain 404, your proxy isn't routing the path. Add ",(0,r.jsx)(n.code,{children:"/config-manager/"})," to whatever you're forwarding, or simplify the rules so everything reaches Dashy."]}),"\n",(0,r.jsx)(n.p,{children:"Or if you're serving up the compiled Vue app directly, instead of using the Node server, then the endpoint won't be available."}),"\n",(0,r.jsx)(n.h3,{id:"invalid-filename-when-saving-a-sub-page",children:'"Invalid filename" when saving a sub-page'}),"\n",(0,r.jsxs)(n.p,{children:["The save endpoint rejects sub-page filenames with path separators or non-yaml extensions. Check the ",(0,r.jsx)(n.code,{children:"path:"})," value of the page in your ",(0,r.jsx)(n.code,{children:"pages:"})," block. It needs to be a plain basename like ",(0,r.jsx)(n.code,{children:"home.yml"}),", not ",(0,r.jsx)(n.code,{children:"pages/home.yml"})," or ",(0,r.jsx)(n.code,{children:"home.txt"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"cannot-save-to-an-external-url",children:'"Cannot save to an external URL"'}),"\n",(0,r.jsxs)(n.p,{children:["The sub-page you are editing is loaded from a remote URL. Dashy can't write back to that URL.\nYou will need to edit the file at it's origin yourself instead (click the Export to view the YAML).\nOr you could download the config to ",(0,r.jsx)(n.code,{children:"user-data/something.yml"}),", and update ",(0,r.jsx)(n.code,{children:"path:"})," to point to the local version."]}),"\n",(0,r.jsx)(n.h3,{id:"saved-successfully-but-the-ui-shows-the-old-config",children:"Saved successfully but the UI shows the old config"}),"\n",(0,r.jsx)(n.p,{children:"Two unrelated causes share this symptom:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Local storage overrides the file."})," Dashy lets users save settings locally in browser storage, which take priority over ",(0,r.jsx)(n.code,{children:"conf.yml"}),'. Open Dashy in incognito to confirm. If the changes appear there, clear local settings via Config menu > "Clear Local Settings".']}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Docker isn't picking up file changes."})," Some text editors save by replacing the inode, which breaks single-file bind mounts. Edit the file in place, or mount the parent directory rather than the single file. ",(0,r.jsx)(n.a,{href:"https://medium.com/@jonsbun/why-need-to-be-careful-when-mounting-single-files-into-a-docker-container-4f929340834",children:"More background"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.a,{id:"container-crashes-or-restart-loop-after-saving-config"})}),"\n",(0,r.jsx)(n.h3,{id:"container-crashes-or-restart-loop-after-saving-310-and-311-only",children:"Container crashes or restart loop after saving (3.1.0 and 3.1.1 only)"}),"\n",(0,r.jsxs)(n.p,{children:["If your container crashes or restart-loops right after clicking save, with logs like ",(0,r.jsx)(n.code,{children:"ERR_HTTP_HEADERS_SENT"})," or ",(0,r.jsx)(n.code,{children:"ERR_STREAM_WRITE_AFTER_END"}),", this was a known double-",(0,r.jsx)(n.code,{children:"res.end()"})," bug in 3.1.0 and 3.1.1. Fixed in v3.2.13 and later."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker pull lissy93/dashy:latest\ndocker compose up -d --force-recreate\n"})}),"\n",(0,r.jsx)(n.h3,{id:"intentionally-read-only-mode",children:"Intentionally read-only mode"}),"\n",(0,r.jsxs)(n.p,{children:['To hide the "Save to disk" UI for everyone, set ',(0,r.jsx)(n.code,{children:"appConfig.preventWriteToDisk: true"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"}),". This is a UI-only flag \u2014 the ",(0,r.jsx)(n.code,{children:"/config-manager/save"})," server endpoint itself is gated by the configured auth method (",(0,r.jsx)(n.code,{children:"auth.users"})," with ",(0,r.jsx)(n.code,{children:"ENABLE_HTTP_AUTH"}),", OIDC/Keycloak admin role, header-auth, etc.), so anyone unauthenticated or non-admin already can't save regardless of this flag. For Docker users, you can harden things further by mounting ",(0,r.jsx)(n.code,{children:"user-data"})," (or just ",(0,r.jsx)(n.code,{children:"conf.yml"}),") as read-only \u2014 the kernel will refuse the write even before the server tries."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h2,{id:"refused-to-connect-in-modal-or-workspace-view",children:[(0,r.jsx)(n.code,{children:"Refused to Connect"})," in Modal or Workspace View"]}),"\n",(0,r.jsx)(n.p,{children:"This is not an issue with Dashy, but instead caused by the target app preventing direct access through embedded elements."}),"\n",(0,r.jsxs)(n.p,{children:["As defined in ",(0,r.jsx)(n.a,{href:"https://datatracker.ietf.org/doc/html/rfc7034",children:"RFC-7034"}),", for any web content to be accessed through an embedded element, it must have the ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options",children:(0,r.jsx)(n.code,{children:"X-Frame-Options"})})," HTTP header set to ",(0,r.jsx)(n.code,{children:"ALLOW"}),". If you are getting a ",(0,r.jsx)(n.code,{children:"Refused to Connect"})," error then this header is set to ",(0,r.jsx)(n.code,{children:"DENY"})," (or ",(0,r.jsx)(n.code,{children:"SAMEORIGIN"})," and it's on a different host). Thankfully, for self-hosted services, it is easy to set these headers."]}),"\n",(0,r.jsx)(n.p,{children:"These settings are usually set in the config file for the web server that's hosting the target application, here are some examples of how to enable cross-origin access with common web servers:"}),"\n",(0,r.jsx)(n.h3,{id:"nginx",children:"NGINX"}),"\n",(0,r.jsxs)(n.p,{children:["In NGINX, you can use the ",(0,r.jsx)(n.a,{href:"https://nginx.org/en/docs/http/ngx_http_headers_module.html",children:(0,r.jsx)(n.code,{children:"add_header"})})," module within the app block."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"server {\n ...\n add_header X-Frame-Options SAMEORIGIN always;\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Then reload with ",(0,r.jsx)(n.code,{children:"service nginx reload"})]}),"\n",(0,r.jsx)(n.h3,{id:"caddy",children:"Caddy"}),"\n",(0,r.jsxs)(n.p,{children:["In Caddy, you can use the ",(0,r.jsx)(n.a,{href:"https://caddyserver.com/docs/caddyfile/directives/header",children:(0,r.jsx)(n.code,{children:"header"})})," directive."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"header {\n X-Frame-Options SAMEORIGIN\n}\n"})}),"\n",(0,r.jsx)(n.h3,{id:"apache",children:"Apache"}),"\n",(0,r.jsxs)(n.p,{children:["In Apache, you can use the ",(0,r.jsx)(n.a,{href:"https://httpd.apache.org/docs/current/mod/mod_headers.html",children:(0,r.jsx)(n.code,{children:"mod_headers"})})," module to set the ",(0,r.jsx)(n.code,{children:"X-Frame-Options"})," in your config file. This file is usually located somewhere like `/etc/apache2/httpd.conf"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'Header set X-Frame-Options: "ALLOW-FROM http://[dashy-location]/"\n'})}),"\n",(0,r.jsx)(n.h3,{id:"lighthttpd",children:"LightHttpd"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Content-Security-Policy: frame-ancestors 'self' https://[dashy-location]/\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"404--routing-issues",children:"404 / Routing issues"}),"\n",(0,r.jsx)(n.h3,{id:"404-on-static-hosting",children:"404 On Static Hosting"}),"\n",(0,r.jsx)(n.p,{children:"If you're seeing Dashy's 404 page on initial load/ refresh, and then the main app when you go back to Home, then this is likely caused by the Vue router, and if so can be fixed in one of two ways."}),"\n",(0,r.jsxs)(n.p,{children:["The first solution is to switch the routing mode, from HTML5 ",(0,r.jsx)(n.code,{children:"history"})," mode to ",(0,r.jsx)(n.code,{children:"hash"})," mode, by rebuilding Dashy with the ",(0,r.jsx)(n.code,{children:"VITE_APP_ROUTING_MODE=hash"})," build-time environment variable set."]}),"\n",(0,r.jsxs)(n.p,{children:["If this works, but you wish to continue using HTML5 history mode, then a bit of extra ",(0,r.jsx)(n.a,{href:"/docs/management#web-server-configuration",children:"server configuration"})," is required. This is explained in more detaail in the ",(0,r.jsx)(n.a,{href:"https://router.vuejs.org/guide/essentials/history-mode.html",children:"Vue Docs"}),". Once completed, you can then use ",(0,r.jsx)(n.code,{children:"VITE_APP_ROUTING_MODE=history"})," (the default) again, for neater URLs."]}),"\n",(0,r.jsx)(n.h3,{id:"404-after-launch-from-mobile-home-screen",children:"404 after Launch from Mobile Home Screen"}),"\n",(0,r.jsxs)(n.p,{children:['Similar to the above issue, if you get a 404 after using iOS and Android\'s "Add to Home Screen" feature, then this is caused by Vue router.\nIt can be fixed by rebuilding Dashy with the ',(0,r.jsx)(n.code,{children:"VITE_APP_ROUTING_MODE=hash"})," build-time environment variable set."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/628",children:"#628"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/762",children:"#762"})]}),"\n",(0,r.jsx)(n.h3,{id:"404-on-multi-page-apps",children:"404 On Multi-Page Apps"}),"\n",(0,r.jsxs)(n.p,{children:["Similar to above, if you get a 404 error when visiting a page directly on multi-page apps, then this can be fixed by rebuilding Dashy with the ",(0,r.jsx)(n.code,{children:"VITE_APP_ROUTING_MODE=hash"})," build-time environment variable set, then refreshing the page."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/670",children:"#670"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/763",children:"#763"})]}),"\n",(0,r.jsxs)(n.h3,{id:"dashy-hosted-at-a-sub-path-eg-examplecomdashy",children:["Dashy hosted at a sub-path (e.g. ",(0,r.jsx)(n.code,{children:"example.com/dashy"}),")"]}),"\n",(0,r.jsxs)(n.p,{children:["If the homepage works but sub-page links 404, or assets fail to load, it's almost always the base path.\nRebuild with ",(0,r.jsx)(n.code,{children:"BASE_URL"})," set to the sub-path - leading slash, no trailing slash:"]}),"\n",(0,r.jsxs)(n.p,{children:["Vue Router uses this to prefix every route. Without it, links resolve to ",(0,r.jsx)(n.code,{children:"/home/..."})," instead of ",(0,r.jsx)(n.code,{children:"/dashy/home/..."})," and skip your reverse proxy altogether. More detail in ",(0,r.jsx)(n.a,{href:"/docs/management#web-server-configuration",children:"web-server configuration"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"sub-pages",children:"Sub-pages"}),"\n",(0,r.jsx)(n.h3,{id:"sub-page-shows-unable-to-find-config-for-",children:'Sub-page shows "Unable to find config for ..."'}),"\n",(0,r.jsxs)(n.p,{children:["This means Dashy couldn't match the URL segment to any entry in your ",(0,r.jsx)(n.code,{children:"pages:"})," list. A few causes:"]}),"\n",(0,r.jsx)(n.h4,{id:"old-bookmark-from-before-an-upgrade",children:"Old bookmark from before an upgrade"}),"\n",(0,r.jsxs)(n.p,{children:["Slugs are now trimmed more aggressively (e.g. ",(0,r.jsx)(n.code,{children:"\ud83c\udf10 Command Center"})," used to give ",(0,r.jsx)(n.code,{children:"-command-center"}),", now gives ",(0,r.jsx)(n.code,{children:"command-center"}),"). Re-bookmark from the nav, or update the URL by hand."]}),"\n",(0,r.jsx)(n.h4,{id:"the-page-was-renamed-or-removed",children:"The page was renamed or removed"}),"\n",(0,r.jsxs)(n.p,{children:["The URL no longer resolves to anything. Check the ",(0,r.jsx)(n.code,{children:"pages:"})," array in ",(0,r.jsx)(n.code,{children:"conf.yml"})," and confirm the sub-page still exists."]}),"\n",(0,r.jsx)(n.h4,{id:"the-path-points-at-an-unreachable-file",children:"The path points at an unreachable file"}),"\n",(0,r.jsxs)(n.p,{children:["If the sub-config YAML can't be fetched (404, CORS, auth), you'll see \"Unable to load config from ...\" instead. Verify the ",(0,r.jsx)(n.code,{children:"path:"})," is correct, reachable from the browser, and CORS-open if remote."]}),"\n",(0,r.jsx)(n.h4,{id:"page-name-literally-main",children:'Page name literally "Main"'}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"main"}),' is reserved in the URL scheme to mean "the root config". A page named "Main" becomes reachable at ',(0,r.jsx)(n.code,{children:"/home/main-page"})," (not ",(0,r.jsx)(n.code,{children:"/home/main"}),"). Rename the page if that's confusing."]}),"\n",(0,r.jsx)(n.h4,{id:"service-worker-is-serving-a-stale-app",children:"Service worker is serving a stale app"}),"\n",(0,r.jsxs)(n.p,{children:["Hard-refresh (",(0,r.jsx)(n.kbd,{children:"Ctrl"})," + ",(0,r.jsx)(n.kbd,{children:"F5"}),") after a major upgrade. The PWA cache may still be pointing at old routes. Also see ",(0,r.jsx)(n.a,{href:"#styles-and-assets-not-updating",children:"Styles and Assets not Updating"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"sub-page-missing-from-nav-or-wont-open-when-clicked",children:"Sub-page missing from nav, or won't open when clicked"}),"\n",(0,r.jsxs)(n.p,{children:["If page defined in ",(0,r.jsx)(n.code,{children:"pages:"})," is nowhere in the nav bar, or its link goes to a different page, then there's probably something wrong with the name you chose. Note that Dashy strips out any non-alphanumeric characters."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Ensure each page does have a valid ",(0,r.jsx)(n.code,{children:"name"})," and ",(0,r.jsx)(n.code,{children:"path"})," field"]}),"\n",(0,r.jsx)(n.li,{children:"Check two pages don't have the same/similar name"}),"\n",(0,r.jsx)(n.li,{children:"Check each page has a name which has at least some alpha-numeric characters"}),"\n",(0,r.jsx)(n.li,{children:"Very long names could be being stripped/truncated"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"sub-page-ignores-its-theme-layout-or-appconfig",children:"Sub-page ignores its theme, layout or appConfig"}),"\n",(0,r.jsxs)(n.p,{children:["This is by design. Only the ",(0,r.jsx)(n.code,{children:"appConfig"})," from your root ",(0,r.jsx)(n.code,{children:"conf.yml"})," is used - theme, layout, iconSize, statusCheck, etc. are inherited globally so behaviour stays consistent across pages."]}),"\n",(0,r.jsxs)(n.p,{children:["If you put ",(0,r.jsx)(n.code,{children:"appConfig"})," inside a sub-page YAML, it's silently dropped on load. Move the values to the root config. See ",(0,r.jsx)(n.a,{href:"/docs/pages-and-sections#restrictions",children:"Restrictions"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"sub-config-files-return-404",children:"Sub-config files return 404"}),"\n",(0,r.jsxs)(n.p,{children:["If your ",(0,r.jsx)(n.code,{children:"conf.yml"})," references additional pages via ",(0,r.jsx)(n.code,{children:"pages:"})," and the browser shows ",(0,r.jsx)(n.code,{children:"Sub-config load failed: /something.yml"}),", the cause is almost always a Docker mount that only exposes ",(0,r.jsx)(n.code,{children:"conf.yml"})," and not the rest of ",(0,r.jsx)(n.code,{children:"user-data/"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"If you've done this:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"volumes:\n - ./my-conf.yml:/app/user-data/conf.yml\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Only ",(0,r.jsx)(n.code,{children:"conf.yml"})," exists inside the container. Anything it references (sub-configs, custom icons, fonts, CSS) isn't there."]}),"\n",(0,r.jsx)(n.p,{children:"Mount the directory instead:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"volumes:\n - ./user-data:/app/user-data\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Now everything in your ",(0,r.jsx)(n.code,{children:"user-data"})," folder is reachable at the web root. Same applies to ",(0,r.jsx)(n.code,{children:"docker run -v"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"remote-config-not-loading",children:"Remote Config Not Loading"}),"\n",(0,r.jsx)(n.p,{children:"If you've got a multi-page dashboard, and are hosting the additional config files yourself, then CORS rules will apply. A CORS error will look something like:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access to XMLHttpRequest at 'https://example.com/raw/my-config.yml' from origin 'http://dashy.local' has been blocked by CORS policy:\nNo 'Access-Control-Allow-Origin' header is present on the requested resource.\n"})}),"\n",(0,r.jsx)(n.p,{children:"The solution is to add the appropriate headers onto the target server, to allow it to accept requests from the origin where you're running Dashy."}),"\n",(0,r.jsxs)(n.p,{children:["If it is a remote service, that you do not have admin access to, then another option is to proxy the request. Either host your own, or use a publicly accessible service, like ",(0,r.jsx)(n.a,{href:"https://allorigins.win",children:"allorigins.win"}),", e.g: ",(0,r.jsx)(n.code,{children:"https://api.allorigins.win/raw?url=https://pastebin.com/raw/4tZpaJV5"}),". For git-based services specifically, there's ",(0,r.jsx)(n.a,{href:"https://raw.githack.com/",children:"raw.githack.com"})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"build--memory-errors",children:"Build & memory errors"}),"\n",(0,r.jsx)(n.h3,{id:"yarn-error",children:"Yarn Error"}),"\n",(0,r.jsxs)(n.p,{children:["For more info, see ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1",children:"Issue #1"})]}),"\n",(0,r.jsxs)(n.p,{children:["First of all, check that you've got yarn installed correctly - see the ",(0,r.jsx)(n.a,{href:"https://classic.yarnpkg.com/en/docs/install",children:"yarn installation docs"})," for more info."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're getting an error about scenarios, then you've likely installed the wrong yarn... (you're ",(0,r.jsx)(n.a,{href:"https://github.com/yarnpkg/yarn/issues/2821",children:"not"})," the only one!). You can fix it by uninstalling, adding the correct repo, and reinstalling, for example, in Debian:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"sudo apt remove yarn"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:'echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"sudo apt update && sudo apt install yarn"})}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Alternatively, as a workaround, you have several options:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Try using ",(0,r.jsx)(n.a,{href:"https://www.npmjs.com/get-npm",children:"NPM"})," instead: So clone, cd, then run ",(0,r.jsx)(n.code,{children:"npm install"}),", ",(0,r.jsx)(n.code,{children:"npm run build"})," and ",(0,r.jsx)(n.code,{children:"npm start"})]}),"\n",(0,r.jsxs)(n.li,{children:["Try using ",(0,r.jsx)(n.a,{href:"https://www.docker.com/get-started",children:"Docker"})," instead, and all of the system setup and dependencies will already be taken care of. So from within the directory, just run ",(0,r.jsx)(n.code,{children:"docker build -t lissy93/dashy ."})," to build, and then use docker start to run the project, e.g: ",(0,r.jsx)(n.code,{children:"docker run -it -p 8080:8080 lissy93/dashy"})," (see the ",(0,r.jsx)(n.a,{href:"/docs/deployment#deploy-with-docker",children:"deploying docs"})," for more info)"]}),"\n"]}),"\n",(0,r.jsxs)(n.h3,{id:"yarn-build-fails-inside-the-container",children:[(0,r.jsx)(n.code,{children:"yarn build"})," fails inside the container"]}),"\n",(0,r.jsxs)(n.p,{children:["If you run ",(0,r.jsx)(n.code,{children:"docker exec yarn build"})," and get ",(0,r.jsx)(n.code,{children:"vite: not found"})," (or similar), it's because the published image ships only production dependencies. The build toolchain (vite, vue-tsc, sass, etc.) lives in ",(0,r.jsx)(n.code,{children:"devDependencies"})," and isn't installed in the runtime image."]}),"\n",(0,r.jsxs)(n.p,{children:["You almost certainly don't need to rebuild. Dashy's Express server reads ",(0,r.jsx)(n.code,{children:"user-data/conf.yml"})," on every request, so config changes show up on a page refresh, no rebuild required."]}),"\n",(0,r.jsxs)(n.p,{children:["If you genuinely need a fresh build (you've patched something in ",(0,r.jsx)(n.code,{children:"src/"}),"), do it on the host with ",(0,r.jsx)(n.code,{children:"yarn install && yarn build"}),", or build a custom image from a checkout of the repo."]}),"\n",(0,r.jsx)(n.h3,{id:"high-cpu-or-ram-usage-on-startup",children:"High CPU or RAM Usage on Startup"}),"\n",(0,r.jsxs)(n.p,{children:["When the Dashy container first starts, it runs a Vue production build in parallel with the server. This is ",(0,r.jsx)(n.strong,{children:"a one-time cost per container start"}),", but it briefly uses around ",(0,r.jsx)(n.strong,{children:"1\u20131.5 GB of RAM and 100% of one CPU core"})," for anywhere from 30 seconds to several minutes (depending on host speed). On Pi-class hardware or VMs with less than 1 GB of RAM, this spike can be enough to lock up the host."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"To work around it:"})}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Allocate at least 1 GB of RAM to the container"})," - 2 GB is recommended on Raspberry Pi or low-powered VMs. Anything below 512 MB is unlikely to complete the first build."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Set explicit Docker resource limits"})," so the build can't starve other services on the same host:\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"services:\n dashy:\n image: lissy93/dashy:latest\n deploy:\n resources:\n limits:\n memory: 2g\n cpus: '1.5'\n"})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Wait it out"})," - once the build completes, idle CPU drops to near zero and idle RAM is typically under 100 MB. If you watch ",(0,r.jsx)(n.code,{children:"docker stats"}),", you'll see the spike taper off."]}),"\n",(0,r.jsxs)(n.li,{children:["If the spike never tapers (i.e., Dashy stays at 100% CPU forever and never serves the page), see ",(0,r.jsx)(n.a,{href:"#ineffective-mark-compacts-near-heap-limit-allocation-failed",children:"Heap limit Allocation failed"})," below - that usually means the build was killed mid-way and is being retried."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1585",children:"#1585"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/969",children:"#969"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1500",children:"#1500"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/877",children:"#877"})]}),"\n",(0,r.jsx)(n.h3,{id:"ineffective-mark-compacts-near-heap-limit-allocation-failed",children:"Ineffective mark-compacts near heap limit Allocation failed"}),"\n",(0,r.jsx)(n.p,{children:"If you see an error message, similar to:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"<--- Last few GCs ---\x3e\n\n[61:0x74533040] 229060 ms: Mark-sweep (reduce) 127.1 (236.9) -> 127.1 (137.4) MB, 5560.7 / 0.3 ms (average mu = 0.286, current mu = 0.011) allocation failure scavenge might not succeed\n\n<--- JS stacktrace ---\x3e\n\nFATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory\n"})}),"\n",(0,r.jsxs)(n.p,{children:["This is likely caused by insufficient memory allocation to the container. When the container first starts up, or has to rebuild, the memory usage spikes, and if there isn't enough memory, it may terminate. This can be specified with, for example: ",(0,r.jsx)(n.code,{children:"--memory=1024m"}),". For more info, see ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/containers/resource_constraints/",children:"Docker: Runtime options with Memory, CPUs, and GPUs"}),". For more context on what the spike is, see ",(0,r.jsx)(n.a,{href:"#high-cpu-or-ram-usage-on-startup",children:"High CPU or RAM Usage on Startup"})," above."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/380",children:"#380"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/350",children:"#350"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/297",children:"#297"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/349",children:"#349"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/510",children:"#510"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/511",children:"#511"})," and ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/834",children:"#834"})]}),"\n",(0,r.jsx)(n.h3,{id:"command-failed-with-signal-sigkill",children:'Command failed with signal "SIGKILL"'}),"\n",(0,r.jsxs)(n.p,{children:["In Docker, this can be caused by not enough memory. When the container first starts up, or has to rebuild, the memory usage spikes, and so a larger allocation may be required. This can be specified with, for example: ",(0,r.jsx)(n.code,{children:"--memory=1024m"}),". For more info, see ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/containers/resource_constraints/",children:"Docker: Runtime options with Memory, CPUs, and GPUs"})]}),"\n",(0,r.jsxs)(n.p,{children:["See also ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/624",children:"#624"})]}),"\n",(0,r.jsx)(n.h3,{id:"node-sass-does-not-yet-support-your-current-environment",children:"Node Sass does not yet support your current environment"}),"\n",(0,r.jsxs)(n.p,{children:["Caused by node-sass's binaries being built for a for a different architecture\nTo fix this, just run: ",(0,r.jsx)(n.code,{children:"yarn rebuild node-sass"})]}),"\n",(0,r.jsx)(n.h3,{id:"unreachable-code-error",children:"Unreachable Code Error"}),"\n",(0,r.jsxs)(n.p,{children:["An error similar to: ",(0,r.jsx)(n.code,{children:"Fatal error in , line 0. Unreachable code, FailureMessage Object: 0xffe6c8ac. Illegal instruction (core dumped)"}),"\nIs related to a bug in a downstream package, see ",(0,r.jsx)(n.a,{href:"https://github.com/nodejs/docker-node/issues/1477",children:"nodejs/docker-node#1477"}),".\nUsually, updating your system and packages will resolve the issue."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/776",children:"#776"})]}),"\n",(0,r.jsx)(n.h3,{id:"error-cannot-find-module-_basevalues",children:"Error: Cannot find module './_baseValues'"}),"\n",(0,r.jsxs)(n.p,{children:["Clearing the cache should fix this: ",(0,r.jsx)(n.code,{children:"yarn cache clean"}),"\nIf the issue persists, remove (",(0,r.jsx)(n.code,{children:"rm -rf node_modules\\ yarn.lock"}),") and reinstall (",(0,r.jsx)(n.code,{children:"yarn"}),") node_modules"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"auth--oidc",children:"Auth & OIDC"}),"\n",(0,r.jsx)(n.h3,{id:"auth-validation-error-should-be-object",children:'Auth Validation Error: "should be object"'}),"\n",(0,r.jsxs)(n.p,{children:["In V 1.6.5 an update was made that in the future will become a breaking change. You will need to update you config to reflect this before V 2.0.0 is released. In the meantime, your previous config will continue to function normally, but you will see a validation warning. The change means that the structure of the ",(0,r.jsx)(n.code,{children:"appConfig.auth"})," object is now an object, which has a ",(0,r.jsx)(n.code,{children:"users"})," property."]}),"\n",(0,r.jsxs)(n.p,{children:["For more info, see ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/discussions/177",children:"this announcement"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"You can fix this by replacing:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"auth:\n- user: xxx\n hash: xxx\n"})}),"\n",(0,r.jsx)(n.p,{children:"with"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"auth:\n users:\n - user: xxx\n hash: xxx\n"})}),"\n",(0,r.jsx)(n.h3,{id:"keycloak-redirect-error",children:"Keycloak Redirect Error"}),"\n",(0,r.jsxs)(n.p,{children:["Check the ",(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"browser's console output"}),", if you've not set any headers, you will likely see a CORS error here, which would be the source of the issue."]}),"\n",(0,r.jsxs)(n.p,{children:["You need to allow Dashy to make requests to Keycloak, and Keycloak to redirect to Dashy. The way you do this depends on how you're hosting these applications / which proxy you are using, and examples can be found in the ",(0,r.jsx)(n.a,{href:"/docs/management#setting-headers",children:"Management Docs"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"For example, add the access control header to Keycloak, like:"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.code,{children:"Access-Control-Allow-Origin [URL-of Dashy]"})}),"\n",(0,r.jsxs)(n.p,{children:["Note that for requests that transport sensitive info like credentials, setting the accept header to a wildcard (",(0,r.jsx)(n.code,{children:"*"}),") is not allowed - see ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#requests_with_credentials",children:"MDN Docs"}),", so you will need to specify the actual URL."]}),"\n",(0,r.jsxs)(n.p,{children:["You should also ensure that Keycloak is correctly configured, with a user, realm and application, and be sure that you have set a valid redirect URL in Keycloak (",(0,r.jsx)(n.a,{href:"https://user-images.githubusercontent.com/1862727/148599768-db4ee4f8-72c5-402d-8f00-051d999e6267.png",children:"screenshot"}),")."]}),"\n",(0,r.jsxs)(n.p,{children:["For more details on how to set headers, see the ",(0,r.jsx)(n.a,{href:"/docs/management#setting-headers",children:"Example Headers"})," in the management docs, or reference the documentation for your proxy."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're running in Kubernetes, you will need to enable CORS ingress rules, see ",(0,r.jsx)(n.a,{href:"https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#enable-cors",children:"docs"}),", e.g:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'nginx.ingress.kubernetes.io/cors-allow-origin: "https://dashy.example.com"\nnginx.ingress.kubernetes.io/enable-cors: "true"\n'})}),"\n",(0,r.jsx)(n.p,{children:"See also: #479, #409, #507, #491, #341, #520"}),"\n",(0,r.jsx)(n.h3,{id:"oidc-or-keycloak-failure-on-numeric-client-ids",children:"OIDC or Keycloak failure on numeric client IDs"}),"\n",(0,r.jsxs)(n.p,{children:["If your IdP rejects the login with an ",(0,r.jsx)(n.em,{children:'"invalid client"'})," / ",(0,r.jsx)(n.em,{children:'"client not found"'})," error, and your ",(0,r.jsx)(n.code,{children:"clientId"})," is a long numeric value, the cause is almost certainly YAML number parsing."]}),"\n",(0,r.jsxs)(n.p,{children:["YAML parses unquoted numeric tokens as Numbers, and JavaScript can't represent integers larger than 2^53 (~16 digits) without losing precision. So an unquoted numeric ",(0,r.jsx)(n.code,{children:"clientId"})," will be silently truncated (e.g. ",(0,r.jsx)(n.code,{children:"918756876419824312"})," \u2192 ",(0,r.jsx)(n.code,{children:"918756876419824300"}),"), or - for very large values - converted to scientific notation (e.g. ",(0,r.jsx)(n.code,{children:"9.187568764198242e+37"}),"), and the IdP will reject it."]}),"\n",(0,r.jsxs)(n.p,{children:["The fix is to wrap the ",(0,r.jsx)(n.code,{children:"clientId"})," in quotes in your ",(0,r.jsx)(n.code,{children:"conf.yml"})," so it gets parsed as a string:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'appConfig:\n auth:\n enableOidc: true\n oidc:\n clientId: "918756876419824312"\n endpoint: https://idp.example.com/\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The same applies to ",(0,r.jsx)(n.code,{children:"auth.keycloak.clientId"}),". Dashy will print a warning in the ",(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"browser console"})," when it detects a numeric ",(0,r.jsx)(n.code,{children:"clientId"}),", to help diagnose this."]}),"\n",(0,r.jsx)(n.p,{children:"See also: #1941"}),"\n",(0,r.jsx)(n.h3,{id:"redirect-loop-after-login",children:"Redirect loop after login"}),"\n",(0,r.jsxs)(n.p,{children:["Your ",(0,r.jsx)(n.code,{children:"endpoint"})," probably includes ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"}),". Drop everything from ",(0,r.jsx)(n.code,{children:".well-known"})," onwards"]}),"\n",(0,r.jsx)(n.h3,{id:"invalid_redirect_uri",children:"invalid_redirect_uri"}),"\n",(0,r.jsxs)(n.p,{children:["The redirect URI Authentik has registered for the provider doesn't exactly match the URL Dashy is being served from. Register both the bare URL and the trailing-slash version, and make sure the scheme matches (",(0,r.jsx)(n.code,{children:"http"})," vs ",(0,r.jsx)(n.code,{children:"https"}),")."]}),"\n",(0,r.jsx)(n.h3,{id:"login-works-in-the-browser-but-the-dashboard-refuses-to-save-anything-403",children:"Login works in the browser but the dashboard refuses to save anything (403)"}),"\n",(0,r.jsxs)(n.p,{children:["Dashy's server is rejecting the id_token. Check Dashy's container logs for ",(0,r.jsx)(n.code,{children:"[auth-oidc] token verification failed"}),". Common causes:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Issuer mismatch"}),". Authentik is behind a reverse proxy that isn't sending ",(0,r.jsx)(n.code,{children:"X-Forwarded-Proto: https"}),", so its discovery document advertises ",(0,r.jsx)(n.code,{children:"http://"})," while you configured ",(0,r.jsx)(n.code,{children:"https://"})," in Dashy. Fix the proxy or set ",(0,r.jsx)(n.code,{children:"AUTHENTIK_HOST"}),"/",(0,r.jsx)(n.code,{children:"AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS"})," on the Authentik containers"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Audience mismatch"}),". The ",(0,r.jsx)(n.code,{children:"aud"})," claim in the id_token is not ",(0,r.jsx)(n.code,{children:"dashy"}),". Confirm the provider's Client ID is exactly ",(0,r.jsx)(n.code,{children:"dashy"})," (no leading or trailing whitespace)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Dashy server can't reach Authentik"}),". The Dashy container fails to fetch the discovery document. Exec into the container and try ",(0,r.jsx)(n.code,{children:"wget -qO- https://auth.example.com/application/o/dashy/.well-known/openid-configuration"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Clock skew"}),". The middleware allows 30 seconds of drift. If a container's clock is further off than that, ",(0,r.jsx)(n.code,{children:"exp"}),"/",(0,r.jsx)(n.code,{children:"iat"})," checks fail"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"logged-in-but-no-admin-controls",children:"Logged in but no admin controls"}),"\n",(0,r.jsxs)(n.p,{children:["The id_token doesn't include the groups claim. Open browser devtools after logging in, find the call to ",(0,r.jsx)(n.code,{children:"/application/o/dashy/userinfo/"}),", and check the response. You should see a ",(0,r.jsx)(n.code,{children:"groups"})," array containing ",(0,r.jsx)(n.code,{children:"dashy-admins"}),". If not:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"groups"})," scope mapping doesn't exist, or is not attached to the provider's property mappings"]}),"\n",(0,r.jsxs)(n.li,{children:["The user is not in the ",(0,r.jsx)(n.code,{children:"dashy-admins"})," group"]}),"\n",(0,r.jsxs)(n.li,{children:["The conf.yml is missing ",(0,r.jsx)(n.code,{children:"groups"})," from ",(0,r.jsx)(n.code,{children:"scope:"})," and Authentik is therefore not sending it"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"login-works-but-dashy-errors-on-the-callback-with-oidc-signincallback-returned-no-user",children:'Login works but Dashy errors on the callback with "OIDC signinCallback returned no user"'}),"\n",(0,r.jsx)(n.p,{children:'The id_token came back without a username claim. Confirm the provider has "profile" and "email" in its scopes and the "Include claims in id_token" is on'}),"\n",(0,r.jsx)(n.h3,{id:"sign-out-leaves-you-stuck-on-authentik",children:"Sign-out leaves you stuck on Authentik"}),"\n",(0,r.jsx)(n.p,{children:"Dashy redirects to Authentik's end-session endpoint on logout. If Authentik's invalidation flow prompts for confirmation (the default), that's expected - click through it. To skip the prompt entirely, change the provider's invalidation flow to one without a consent stage."}),"\n",(0,r.jsx)(n.h3,{id:"untrusted-certificate-from-authentik",children:"Untrusted certificate from Authentik"}),"\n",(0,r.jsx)(n.p,{children:"Self-signed certs make Dashy's server-side fetch of the discovery document fail. Use a real cert (Let's Encrypt, or your homelab CA installed into the Dashy image) for the Authentik hostname."}),"\n",(0,r.jsx)(n.h3,{id:"numeric-client_id-getting-truncated",children:"Numeric client_id getting truncated"}),"\n",(0,r.jsx)(n.p,{children:"Don't use numeric-only client IDs. If you must, wrap the value in quotes in conf.yml so YAML treats it as a string"}),"\n",(0,r.jsx)(n.h3,{id:"header-auth-unauthorized---not-from-trusted-proxy",children:'Header auth: "Unauthorized - not from trusted proxy"'}),"\n",(0,r.jsxs)(n.p,{children:["The IP your reverse proxy presents as isn't in ",(0,r.jsx)(n.code,{children:"auth.headerAuth.proxyWhitelist"}),". For Docker it's usually the bridge IP, not your LAN IP. Find it with ",(0,r.jsx)(n.code,{children:"docker compose exec dashy getent hosts "})," and paste that into ",(0,r.jsx)(n.code,{children:"proxyWhitelist"}),". Restart Dashy after the change."]}),"\n",(0,r.jsx)(n.h3,{id:"header-auth-unauthorized---missing-user-header",children:'Header auth: "Unauthorized - missing user header"'}),"\n",(0,r.jsxs)(n.p,{children:["The source IP check passed, but the configured ",(0,r.jsx)(n.code,{children:"userHeader"})," isn't on the request. Either the proxy isn't sending it (check the Cloudflare Access policy / Authelia forward-auth / Tailscale Serve config is actually applied), or the header name in ",(0,r.jsx)(n.code,{children:"conf.yml"})," doesn't match what the proxy sends. Header matching is case-insensitive, but spelling and prefix matter."]}),"\n",(0,r.jsx)(n.h3,{id:"invalid-user-object-warning-on-startup-with-an-all-digit-hash-or-placeholder",children:'"Invalid user object" warning on startup with an all-digit hash or placeholder'}),"\n",(0,r.jsxs)(n.p,{children:["YAML parsed your ",(0,r.jsx)(n.code,{children:"hash:"})," value as a number rather than a string, because every character was a digit. Common when using placeholder all-zero hashes under header auth. Quote it: ",(0,r.jsx)(n.code,{children:'hash: "0000..."'}),". Same root cause as the numeric ",(0,r.jsx)(n.code,{children:"clientId"})," issue above."]}),"\n",(0,r.jsx)(n.h3,{id:"oidc-login-looks-like-a-cors-error-in-devtools-but-the-idp-is-configured-for-cors-correctly",children:"OIDC login looks like a CORS error in devtools, but the IdP is configured for CORS correctly"}),"\n",(0,r.jsxs)(n.p,{children:["If your identity provider uses a self-signed or untrusted certificate, the browser will silently abort ",(0,r.jsx)(n.code,{children:"oidc-client-ts"}),"'s token-exchange fetch and surface it as a CORS-shaped error. Open the IdP URL directly in a new tab, accept the certificate warning, then retry the Dashy login. The real fix is to use a trusted cert (Let's Encrypt, or a homelab CA installed into the browser's trust store)."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"docker--image-issues",children:"Docker & image issues"}),"\n",(0,r.jsx)(n.h3,{id:"app-not-starting-after-update-to-204",children:"App Not Starting After Update to 2.0.4"}),"\n",(0,r.jsxs)(n.p,{children:["Version 2.0.4 introduced changes to how the config is read, and the app is build. If you were previously mounting ",(0,r.jsx)(n.code,{children:"/public"})," as a volume, then this will over-write the build app, preventing it from starting. The solution is to just pass in the file(s) / sub-directories that you need. For example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"volumes:\n- /srv/dashy/conf.yml:/app/user-data/conf.yml\n- /srv/dashy/item-icons:/app/public/item-icons\n"})}),"\n",(0,r.jsx)(n.h3,{id:"mount-type-mismatch",children:"Mount Type Mismatch"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Error response from daemon: ... mount through procfd: not a directory:\nAre you trying to mount a directory onto a file (or vice-versa)?\n"})}),"\n",(0,r.jsx)(n.p,{children:"This means the host side and container side of your volume don't agree on whether the target is a file or a directory."}),"\n",(0,r.jsxs)(n.p,{children:["Recommended pattern: mount a host directory onto ",(0,r.jsx)(n.code,{children:"/app/user-data"}),". The directory must exist on the host and contain at least a ",(0,r.jsx)(n.code,{children:"conf.yml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"mkdir -p ~/dashy-data\ncp /path/to/your/conf.yml ~/dashy-data/conf.yml\ndocker run -d -p 8080:8080 -v ~/dashy-data:/app/user-data lissy93/dashy:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["If you'd rather mount a single file (",(0,r.jsx)(n.code,{children:"-v ~/conf.yml:/app/user-data/conf.yml"}),"), the host path must be a file that already exists, otherwise Docker creates a directory in its place and you'll see this error."]}),"\n",(0,r.jsxs)(n.h3,{id:"dockerhub-toomanyrequests",children:["DockerHub ",(0,r.jsx)(n.code,{children:"toomanyrequests"})]}),"\n",(0,r.jsx)(n.p,{children:"This situation relates to error messages similar to one of the following, returned when pulling, updating or running the Docker container from Docker Hub."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Continuing execution. Pulling image lissy93/dashy:release-1.6.0\nerror pulling image configuration: toomanyrequests\n"})}),"\n",(0,r.jsx)(n.p,{children:"or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit\n"})}),"\n",(0,r.jsxs)(n.p,{children:["When DockerHub returns one of these errors, or a ",(0,r.jsx)(n.code,{children:"429"})," status, that means you've hit your rate limit. This was ",(0,r.jsx)(n.a,{href:"https://www.docker.com/blog/scaling-docker-to-serve-millions-more-developers-network-egress/",children:"introduced"})," last year, and prevents unauthenticated or free users from running docker pull more than 100 times per 6 hours.\nYou can ",(0,r.jsx)(n.a,{href:"https://www.docker.com/blog/checking-your-current-docker-pull-rate-limits-and-status/",children:"check your rate limit status"})," by looking for the ",(0,r.jsx)(n.code,{children:"ratelimit-remaining"})," header in any DockerHub responses."]}),"\n",(0,r.jsx)(n.h4,{id:"solution-1---use-an-alternate-container-registry",children:"Solution 1 - Use an alternate container registry"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Dashy is also available through GHCR, which at present does not have any hard limits. Just use ",(0,r.jsx)(n.code,{children:"docker pull ghcr.io/lissy93/dashy:latest"})," to fetch the image"]}),"\n",(0,r.jsxs)(n.li,{children:["You can also build the image from source, by cloning the repo, and running ",(0,r.jsx)(n.code,{children:"docker build -t dashy ."})," or use the pre-made docker compose"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"solution-2---increase-your-rate-limits",children:"Solution 2 - Increase your rate limits"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Logging in to DockerHub will increase your rate limit from 100 requests to 200 requests per 6 hour period"}),"\n",(0,r.jsx)(n.li,{children:"Upgrading to a Pro for $5/month will increase your image requests to 5,000 per day, and any plans above have no rate limits"}),"\n",(0,r.jsx)(n.li,{children:"Since rate limits are counted based on your IP address, proxying your requests, or using a VPN may work"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"old-image-tags-fail-to-pull",children:"Old image tags fail to pull"}),"\n",(0,r.jsxs)(n.p,{children:["If ",(0,r.jsx)(n.code,{children:"docker pull"})," returns ",(0,r.jsx)(n.code,{children:"manifest unknown"})," or ",(0,r.jsx)(n.code,{children:"manifest for lissy93/dashy:arm32v7 not found"}),", the cause is a stale architecture-specific tag in your compose file or run command. Tags like ",(0,r.jsx)(n.code,{children:":arm32v7"}),", ",(0,r.jsx)(n.code,{children:":arm64v8"}),", and ",(0,r.jsx)(n.code,{children:":multi-arch"})," are no longer published."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:":latest"})," tag is now multi-arch and works on amd64, arm64, and arm/v7 (Raspberry Pi 2 and up) without you having to pick a variant. Just use:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"image: lissy93/dashy:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Docker fetches the right architecture for your host automatically. To pin a version, use a semver tag, e.g. ",(0,r.jsx)(n.code,{children:"lissy93/dashy:3.2.14"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"healthcheck-failing-in-docker",children:"Healthcheck Failing in Docker"}),"\n",(0,r.jsxs)(n.p,{children:["If ",(0,r.jsx)(n.code,{children:"docker ps"})," shows the Dashy container as ",(0,r.jsx)(n.code,{children:"unhealthy"}),", the periodic healthcheck (",(0,r.jsx)(n.code,{children:"node services/healthcheck.js"}),", run every 5 minutes by default) couldn't reach the local server."]}),"\n",(0,r.jsx)(n.h4,{id:"ssl-enabled-dashy",children:"SSL-enabled Dashy"}),"\n",(0,r.jsxs)(n.p,{children:["The healthcheck reads the same cert paths as the main server (",(0,r.jsx)(n.code,{children:"/etc/ssl/certs/dashy-pub.pem"})," and ",(0,r.jsx)(n.code,{children:"/etc/ssl/certs/dashy-priv.key"}),") to detect whether to probe HTTPS or HTTP. If you've mounted certs at non-default paths via ",(0,r.jsx)(n.code,{children:"SSL_PUB_KEY_PATH"})," / ",(0,r.jsx)(n.code,{children:"SSL_PRIV_KEY_PATH"}),", the healthcheck will pick those up automatically as long as those env vars are set in the container environment (not just at run time)."]}),"\n",(0,r.jsx)(n.h4,{id:"custom-port",children:"Custom port"}),"\n",(0,r.jsxs)(n.p,{children:["If you've set ",(0,r.jsx)(n.code,{children:"PORT"})," to override the default 8080, the healthcheck honors the same env var, so it should work without changes. Make sure ",(0,r.jsx)(n.code,{children:"PORT"})," is set in the container environment, not just in the host-side Docker port mapping."]}),"\n",(0,r.jsx)(n.h4,{id:"container-is-unhealthy-past-the-grace-period",children:"Container is unhealthy past the grace period"}),"\n",(0,r.jsxs)(n.p,{children:["The healthcheck has a 20s ",(0,r.jsx)(n.code,{children:"start-period"})," after which failures start counting. The image is prebuilt, so startup is just ",(0,r.jsx)(n.code,{children:"node server.js"})," binding to a port - fast even on a Pi. If the container is still ",(0,r.jsx)(n.code,{children:"unhealthy"})," past the grace period, the server has likely crashed. Check ",(0,r.jsx)(n.code,{children:"docker logs "})," for the real error (usually a malformed ",(0,r.jsx)(n.code,{children:"conf.yml"})," or a missing ",(0,r.jsx)(n.code,{children:"user-data"})," mount)."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1410",children:"#1410"})]}),"\n",(0,r.jsx)(n.h3,{id:"docker-login-fails-on-ubuntu",children:"Docker Login Fails on Ubuntu"}),"\n",(0,r.jsxs)(n.p,{children:["Run ",(0,r.jsx)(n.code,{children:"sudo apt install gnupg2 pass && gpg2 -k"})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"styles-and-assets-not-updating",children:"Styles and Assets not Updating"}),"\n",(0,r.jsxs)(n.p,{children:["If you find that your styles and other visual assets work when visiting ",(0,r.jsx)(n.code,{children:"ip:port"})," by not ",(0,r.jsx)(n.code,{children:"dashy.domain.com"}),", then this is usually caused by caching. In your browser, do a hard-refresh (",(0,r.jsx)(n.kbd,{children:"Ctrl"})," + ",(0,r.jsx)(n.kbd,{children:"F5"}),"). If you use Cloudflare, then you can clear the cache through the management console, or set the cache level to Bypass for certain files, under the Rules tab."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"config-validation-errors",children:"Config Validation Errors"}),"\n",(0,r.jsxs)(n.p,{children:["The configuration file is validated against ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json",children:"Dashy's Schema"})," using AJV."]}),"\n",(0,r.jsxs)(n.p,{children:["First, check that your syntax is valid, using ",(0,r.jsx)(n.a,{href:"https://codebeautify.org/yaml-validator/",children:"YAML Validator"})," or ",(0,r.jsx)(n.a,{href:"https://codebeautify.org/jsonvalidator",children:"JSON Validator"}),". If the issue persists, then take a look at the ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json",children:"schema"}),", and verify that the field you are trying to add/ modify matches the required format. You can also use ",(0,r.jsx)(n.a,{href:"https://www.jsonschemavalidator.net/s/JFUj7X9J",children:"this tool"})," to validate your JSON config against the schema, or run ",(0,r.jsx)(n.code,{children:"yarn validate-config"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"If you're trying to use a recently released feature, and are getting a warning, this is likely because you've not yet updated the the current latest version of Dashy."}),"\n",(0,r.jsx)(n.p,{children:"If the issue still persists, you should raise an issue."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"invalid-host-header-while-running-through-ngrok",children:"Invalid Host Header while running through ngrok"}),"\n",(0,r.jsxs)(n.p,{children:["Just add the ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs#http-host-header",children:"-host-header"})," flag, e.g. ",(0,r.jsx)(n.code,{children:'ngrok http 8080 -host-header="localhost:8080"'})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"warnings-in-the-console-during-deploy",children:"Warnings in the Console during deploy"}),"\n",(0,r.jsx)(n.p,{children:"Please acknowledge the difference between errors and warnings before raising an issue about messages in the console. It's not unusual to see warnings about a new version of a certain package being available, an asset bundle bing oversized or a service worker not yet having a cache. These shouldn't have any impact on the running application, so please don't raise issues about these unless it directly relates to a bug or issue you're experiencing. Errors on the other hand should not appear in the console, and they are worth looking into further."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"status-checks-failing",children:"Status Checks Failing"}),"\n",(0,r.jsx)(n.p,{children:"If you're using status checks, and despite a given service being online, the check is displaying an error, there are a couple of things you can look at:"}),"\n",(0,r.jsxs)(n.p,{children:["If your service requires requests to include any authorization in the headers, then use the ",(0,r.jsx)(n.code,{children:"statusCheckHeaders"})," property, as described in the ",(0,r.jsx)(n.a,{href:"/docs/status-indicators#setting-custom-headers",children:"docs"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["If you are still having issues, it may be because your target application is blocking requests from Dashy's IP. This is a ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"CORS error"}),", and can be fixed by setting the headers on your target app, to include:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: https://location-of-dashy/\nVary: Origin\n"})}),"\n",(0,r.jsxs)(n.p,{children:["If the URL you are checking has an unsigned certificate, or is not using HTTPS, then you may need to disable the rejection of insecure requests. This can be done by setting ",(0,r.jsx)(n.code,{children:"statusCheckAllowInsecure"})," to true for a given item."]}),"\n",(0,r.jsxs)(n.p,{children:["If your service is online, but responds with a status code that is not in the 2xx range, then you can use ",(0,r.jsx)(n.code,{children:"statusCheckAcceptCodes"})," to set an accepted status code."]}),"\n",(0,r.jsxs)(n.p,{children:["If you get an error, like ",(0,r.jsx)(n.code,{children:"Service Unavailable: Server resulted in a fatal error"}),", even when it's definitely online, this is most likely caused by missing the protocol. Don't forget to include ",(0,r.jsx)(n.code,{children:"https://"})," (or whatever protocol) before the URL, and ensure that if needed, you've specified the port."]}),"\n",(0,r.jsxs)(n.p,{children:["Running Dashy in HOST network mode, instead of BRIDGE will allow status check access to other services in HOST mode. For more info, see ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/discussions/445",children:"#445"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"If you have firewall rules configured, then ensure that they don't prevent Dashy from making requests to the other services you are trying to access."}),"\n",(0,r.jsxs)(n.p,{children:["Currently, the status check needs a page to be rendered, so if this URL in your browser does not return anything, then status checks will not work. This may be modified in the future, but in the meantime, a fix would be to make your own status service, which just checks if your app responds with whatever code you'd like, and then return a 200 plus renders an arbitrary message. Then just point ",(0,r.jsx)(n.code,{children:"statusCheckUrl"})," to your custom page."]}),"\n",(0,r.jsxs)(n.p,{children:["For further troubleshooting, use an application like ",(0,r.jsx)(n.a,{href:"https://postman.com",children:"Postman"})," to diagnose the issue. Set the parameter to ",(0,r.jsx)(n.code,{children:"GET"}),", and then make a call to: ",(0,r.jsx)(n.code,{children:"https://[url-of-dashy]/status-check/?&url=[service-url]"}),". Where the service URL must have first been encoded (e.g. with ",(0,r.jsx)(n.code,{children:"encodeURIComponent()"})," or ",(0,r.jsx)(n.a,{href:"https://www.urlencoder.io/",children:"urlencoder.io"}),")"]}),"\n",(0,r.jsx)(n.p,{children:"If you're serving Dashy though a CDN, instead of using the Node server or Docker image, then the Node endpoint that makes requests will not be available to you, and all requests will fail. A workaround for this may be implemented in the future, but in the meantime, your only option is to use the Docker or Node deployment method."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"widgets",children:"Widgets"}),"\n",(0,r.jsx)(n.h3,{id:"widget-errors",children:"Widget Errors"}),"\n",(0,r.jsx)(n.h4,{id:"find-error-message",children:"Find Error Message"}),"\n",(0,r.jsxs)(n.p,{children:["If an error occurs when fetching or rendering results, you will see a short message in the UI. If that message doesn't adequately explain the problem, then you can ",(0,r.jsx)(n.a,{href:"/docs/troubleshooting#how-to-open-browser-console",children:"open the browser console"})," to see more details."]}),"\n",(0,r.jsx)(n.h4,{id:"check-config",children:"Check Config"}),"\n",(0,r.jsx)(n.p,{children:"Before proceeding, ensure that if the widget requires auth your API is correct, and for custom widgets, double check that the URL and protocol is correct."}),"\n",(0,r.jsx)(n.h4,{id:"timeout-error",children:"Timeout Error"}),"\n",(0,r.jsxs)(n.p,{children:["If the error message in the console includes: ",(0,r.jsx)(n.code,{children:"Error: timeout of 500ms exceeded"}),", then your Glances endpoint is slower to respond than expected. You can fix this by ",(0,r.jsx)(n.a,{href:"/docs/widgets#setting-timeout",children:"setting timeout"})," to a larger value. This is done on each widget, with the ",(0,r.jsx)(n.code,{children:"timeout"})," attribute, and is specified in ms. E.g. ",(0,r.jsx)(n.code,{children:"timeout: 5000"})," would only fail if no response is returned within 5 seconds."]}),"\n",(0,r.jsx)(n.h4,{id:"cors-error",children:"CORS error"}),"\n",(0,r.jsxs)(n.p,{children:["If the console message mentions to corss-origin blocking, then this is a CORS error, see: ",(0,r.jsx)(n.a,{href:"#widget-cors-errors",children:"Fixing Widget CORS Errors"})]}),"\n",(0,r.jsx)(n.h4,{id:"more-info",children:"More Info"}),"\n",(0,r.jsx)(n.p,{children:"If you're able to, you can find more information about why the request may be failing in the Dev Tools under the Network tab, and you can ensure your endpoint is correct and working using a tool like Postman."}),"\n",(0,r.jsx)(n.h3,{id:"widget-cors-errors",children:"Widget CORS Errors"}),"\n",(0,r.jsxs)(n.p,{children:["The most common widget issue is a CORS error. This is a browser security mechanism which prevents the client-side app (Dashy) from from accessing resources on a remote origin, without that server's explicit permission (e.g. with headers like Access-Control-Allow-Origin). See the MDN Docs for more info: ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"Cross-Origin Resource Sharing"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"There are several ways to fix a CORS error:"}),"\n",(0,r.jsx)(n.h4,{id:"option-1---ensure-correct-protocol",children:"Option 1 - Ensure Correct Protocol"}),"\n",(0,r.jsx)(n.p,{children:"You will get a CORS error if you try and access a http service from a https source. So ensure that the URL you are requesting has the right protocol, and is correctly formatted."}),"\n",(0,r.jsx)(n.h4,{id:"option-2---set-headers",children:"Option 2 - Set Headers"}),"\n",(0,r.jsxs)(n.p,{children:["If you have control over the destination (e.g. for a self-hosted service), then you can simply apply the correct headers.\nAdd the ",(0,r.jsx)(n.code,{children:"Access-Control-Allow-Origin"})," header, with the value of either ",(0,r.jsx)(n.code,{children:"*"})," to allow requests from anywhere, or more securely, the host of where Dashy is served from. For example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: https://url-of-dashy.local\n"})}),"\n",(0,r.jsx)(n.p,{children:"or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: *\n"})}),"\n",(0,r.jsxs)(n.p,{children:["For more info on how to set headers, see: ",(0,r.jsx)(n.a,{href:"/docs/management#setting-headers",children:"Setting Headers"})," in the management docs"]}),"\n",(0,r.jsx)(n.h4,{id:"option-3---proxying-request",children:"Option 3 - Proxying Request"}),"\n",(0,r.jsxs)(n.p,{children:["You can route requests through Dashy's built-in CORS proxy. Instructions and more details can be found ",(0,r.jsx)(n.a,{href:"/docs/widgets#proxying-requests",children:"here"}),". If you don't have control over the target origin, and you are running Dashy either through Docker, with the Node server or on Netlify, then this solution will work for you."]}),"\n",(0,r.jsxs)(n.p,{children:["Just add the ",(0,r.jsx)(n.code,{children:"useProxy: true"})," option to the failing widget."]}),"\n",(0,r.jsx)(n.h4,{id:"option-4---use-a-plugin",children:"Option 4 - Use a plugin"}),"\n",(0,r.jsxs)(n.p,{children:["For testing purposes, you can use an addon, which will disable the CORS checks. You can get the Allow-CORS extension for ",(0,r.jsx)(n.a,{href:"https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?hl=en-US",children:"Chrome"})," or ",(0,r.jsx)(n.a,{href:"https://addons.mozilla.org/en-US/firefox/addon/access-control-allow-origin/",children:"Firefox"}),", more details ",(0,r.jsx)(n.a,{href:"https://mybrowseraddon.com/access-control-allow-origin.html",children:"here"})]}),"\n",(0,r.jsxs)(n.h3,{id:"cors-proxy-connect-econnrefused--or-getaddrinfo-enotfound-",children:["CORS Proxy ",(0,r.jsx)(n.code,{children:"connect ECONNREFUSED ..."})," or ",(0,r.jsx)(n.code,{children:"getaddrinfo ENOTFOUND ..."})]}),"\n",(0,r.jsxs)(n.p,{children:["The target host is unreachable from the Dashy container. If the target is on the same host as Dashy, ",(0,r.jsxs)(n.strong,{children:["don't use ",(0,r.jsx)(n.code,{children:"localhost"})]})," - inside a Docker container that resolves to the container itself, not the host. Use the host's LAN IP, the Docker bridge gateway, or ",(0,r.jsx)(n.code,{children:"host.docker.internal"})," (on Docker Desktop). If the target is on a different Docker network, attach Dashy to that network too."]}),"\n",(0,r.jsxs)(n.h3,{id:"cors-proxy-target-url-host--is-blocked--must-use-http-or-https",children:["CORS Proxy ",(0,r.jsx)(n.code,{children:"Target-URL host '...' is blocked"})," / ",(0,r.jsx)(n.code,{children:"must use http:// or https://"})]}),"\n",(0,r.jsx)(n.p,{children:"To prevent the CORS proxy from being abused as a Server-Side Request Forgery vector, Dashy refuses to proxy a small number of host/scheme combinations by default:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Cloud instance metadata services"})," - ",(0,r.jsx)(n.code,{children:"169.254.169.254"}),", ",(0,r.jsx)(n.code,{children:"metadata.google.internal"}),", and the matching IPv6 forms. These are reserved magic addresses on AWS, Azure, GCP, DigitalOcean, Hetzner, Oracle Cloud and most other providers. A widget that successfully fetches them on a cloud-hosted Dashy can leak the host's IAM credentials, so they're blocked unconditionally."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Non-HTTP(S) schemes"})," - ",(0,r.jsx)(n.code,{children:"file://"}),", ",(0,r.jsx)(n.code,{children:"ftp://"}),", ",(0,r.jsx)(n.code,{children:"gopher://"}),", ",(0,r.jsx)(n.code,{children:"javascript:"}),", ",(0,r.jsx)(n.code,{children:"data:"}),", and similar. The proxy is meant for HTTP APIs only."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"If you're running Dashy in a fully isolated/private environment and you've deliberately decided you want to allow these (for example, you genuinely need to query your cloud provider's metadata API from a widget), you can opt out of all proxy restrictions by setting the environment variable:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"DANGEROUSLY_DISABLE_PROXY_RESTRICTIONS=true\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The variable is named so loudly because flipping it on a Dashy instance that's exposed to anything other than fully trusted users re-opens the SSRF surface - anyone who can hit ",(0,r.jsx)(n.code,{children:"/cors-proxy"})," can then use Dashy as a relay to reach internal services. ",(0,r.jsx)(n.strong,{children:"Don't set it on cloud-hosted or internet-exposed deployments."})]}),"\n",(0,r.jsx)(n.p,{children:"Note that this is an all-or-nothing escape hatch, not a per-host allowlist. If you only need to reach one specific host that's currently blocked, please open a feature request describing the use case."}),"\n",(0,r.jsx)(n.h3,{id:"widget-shows-error-incorrectly",children:"Widget Shows Error Incorrectly"}),"\n",(0,r.jsx)(n.p,{children:"When there's an error fetching or displaying a widgets data, then it will be highlighted in yellow, and a message displayed on the UI."}),"\n",(0,r.jsxs)(n.p,{children:["In some instances, this is a false positive, and the widget is actually functioning correctly.\nIf this is the case, you can disable the UI error message of a given widget by setting: ",(0,r.jsx)(n.code,{children:"ignoreErrors: true"})]}),"\n",(0,r.jsx)(n.h3,{id:"weather-forecast-widget-401",children:"Weather Forecast Widget 401"}),"\n",(0,r.jsx)(n.p,{children:"A 401 error means your API key is invalid, it is not an issue with Dashy."}),"\n",(0,r.jsxs)(n.p,{children:["Usually this happens due to an error in your config. If you're unsure, copy and paste the ",(0,r.jsx)(n.a,{href:"/docs/widgets#weather",children:"example"})," config, replacing the API key with your own."]}),"\n",(0,r.jsxs)(n.p,{children:["Check that ",(0,r.jsx)(n.code,{children:"apiKey"})," is correctly specified, and nested within ",(0,r.jsx)(n.code,{children:"options"}),". Ensure your input city is valid."]}),"\n",(0,r.jsxs)(n.p,{children:["To test your API key, try making a request to ",(0,r.jsx)(n.code,{children:"https://api.openweathermap.org/data/2.5/weather?q=London&appid=[your-api-key]"})]}),"\n",(0,r.jsxs)(n.p,{children:["If ",(0,r.jsx)(n.a,{href:"/docs/widgets#weather-forecast",children:"Weather widget"})," is working fine, but you are getting a ",(0,r.jsx)(n.code,{children:"401"})," for the ",(0,r.jsx)(n.a,{href:"/docs/widgets#weather-forecast",children:"Weather Forecast widget"}),", then this is also an OWM API key issue.\nSince the forecasting API requires an upgraded plan. ULPT: You can get a free, premium API key by filling in ",(0,r.jsx)(n.a,{href:"https://home.openweathermap.org/students",children:"this form"}),". It's a student plan, but there's no verification to check that you are still a student."]}),"\n",(0,r.jsx)(n.p,{children:"A future update will be pushed out, to use a free weather forecasting API."}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/803",children:"#803"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/789",children:"#789"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/577",children:"#577"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/621",children:"#621"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/578",children:"#578"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/discussions/806",children:"#806"})]}),"\n",(0,r.jsx)(n.h3,{id:"widget-displaying-inaccurate-data",children:"Widget Displaying Inaccurate Data"}),"\n",(0,r.jsx)(n.p,{children:"If any widget is not displaying the data you expect, first confirm that your config is correct, then try manually calling the API endpoint."}),"\n",(0,r.jsxs)(n.p,{children:["If the raw API output is correct, yet the widget is rendering incorrect results, then it is likely a bug, and a ticket should be raised. You can start to debug the issue, by looking at the widget's code (",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/src/components/Widgets",children:"here"}),"), and the browser console + networking tab."]}),"\n",(0,r.jsxs)(n.p,{children:["If the API itself is returning incorrect, incomplete or inaccurate data then an issue needs to be raised ",(0,r.jsx)(n.strong,{children:"with the API provider"})," (not Dashy!). You can find the API provider included within the widget docs, or for a full list see the ",(0,r.jsx)(n.a,{href:"/docs/privacy#widgets",children:"Privacy Docs"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/807",children:"#807"})," (re, domain monitor)"]}),"\n",(0,r.jsxs)(n.h3,{id:"public-ip-widget-not-working-for-ipinfo-or-ipquery-providers",children:["Public IP Widget not working for ",(0,r.jsx)(n.code,{children:"ipinfo"})," or ",(0,r.jsx)(n.code,{children:"ipquery"})," providers"]}),"\n",(0,r.jsxs)(n.p,{children:["If you've set ",(0,r.jsx)(n.code,{children:"options.provider"})," to either ",(0,r.jsx)(n.code,{children:"ipinfo"})," and ",(0,r.jsx)(n.code,{children:"ipquery"})," and the requests are failing, it's likley that they're being blocked. Check your adblocker (uBlock, PrivacyBadger, etc), DNS block lists (PiHole, AdGuard, etc). Or, try proxying the request (with ",(0,r.jsx)(n.code,{children:"useProxy: true"}),") or just try a different provider."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"font-awesome-icons-not-displaying",children:"Font Awesome Icons not Displaying"}),"\n",(0,r.jsxs)(n.p,{children:["Usually, Font Awesome will be automatically enabled if one or more of your icons are using Font-Awesome. If this is not happening, then you can always manually enable (or disable) Font Awesome by setting: ",(0,r.jsx)(n.a,{href:"/docs/configuring#appconfig-optional",children:(0,r.jsx)(n.code,{children:"appConfig"})}),".",(0,r.jsx)(n.code,{children:"enableFontAwesome"})," to ",(0,r.jsx)(n.code,{children:"true"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["If you are trying to use a premium icon, then you must have a ",(0,r.jsx)(n.a,{href:"https://fontawesome.com/plans",children:"Pro License"}),". You'll then need to specify your Pro plan API key under ",(0,r.jsx)(n.code,{children:"appConfig.fontAwesomeKey"}),". You can find this key, by logging into your FA account, navigate to Account \u2192 ",(0,r.jsx)(n.a,{href:"https://fontawesome.com/kits",children:"Kits"})," \u2192 New Kit \u2192 Copy Kit Code. The code is a 10-digit alpha-numeric code, and is also visible within the new kit's URL, for example: ",(0,r.jsx)(n.code,{children:"81e48ce079"}),"."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{src:"https://i.ibb.co/hZ0D9vs/where-do-i-find-my-font-awesome-key.png",width:"600"})}),"\n",(0,r.jsxs)(n.p,{children:["Be sure that you're specifying the icon category and name correctly. You're icon should look be ",(0,r.jsx)(n.code,{children:"[category] fa-[icon-name]"}),". The following categories are supported: ",(0,r.jsx)(n.code,{children:"far"})," ",(0,r.jsx)(n.em,{children:"(regular)"}),", ",(0,r.jsx)(n.code,{children:"fas"})," ",(0,r.jsx)(n.em,{children:"(solid)"}),", ",(0,r.jsx)(n.code,{children:"fal"}),(0,r.jsx)(n.em,{children:"(light)"}),", ",(0,r.jsx)(n.code,{children:"fad"})," ",(0,r.jsx)(n.em,{children:"(duo-tone)"})," and ",(0,r.jsx)(n.code,{children:"fab"}),(0,r.jsx)(n.em,{children:"(brands)"}),". With the exception of brands, you'll usually want all your icons to be in from same category, so they look uniform."]}),"\n",(0,r.jsxs)(n.p,{children:["Ensure the icon you are trying to use, is available within ",(0,r.jsx)(n.a,{href:"https://fontawesome.com/v5/search",children:"FontAwesome Version 5"})," (we've not yet upgraded to V6, as it works a little differently)."]}),"\n",(0,r.jsxs)(n.p,{children:["Examples: ",(0,r.jsx)(n.code,{children:"fab fa-raspberry-pi"}),", ",(0,r.jsx)(n.code,{children:"fas fa-database"}),", ",(0,r.jsx)(n.code,{children:"fas fa-server"}),", ",(0,r.jsx)(n.code,{children:"fas fa-ethernet"})]}),"\n",(0,r.jsxs)(n.p,{children:["Finally, check the ",(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"browser console"})," for any error messages, and raise a ticket if the issue persists."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"copy-to-clipboard-not-working",children:"Copy to Clipboard not Working"}),"\n",(0,r.jsxs)(n.p,{children:["If the copy to clipboard feature (either under Config --\x3e Export, or Item --\x3e Copy URL) isn't functioning as expected, first check the browser console. If you see ",(0,r.jsx)(n.code,{children:"TypeError: Cannot read properties of undefined (reading 'writeText')"})," then this feature is not supported by your browser.\nThe most common reason for this, is if you not running the app over HTTPS. Copying to the clipboard requires the app to be running in a secure origin / aka have valid HTTPS cert. You can read more about this ",(0,r.jsx)(n.a,{href:"https://stackoverflow.com/a/71876238/979052",children:"here"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"As a workaround, you could either:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Highlight the text and copy / ",(0,r.jsx)(n.kbd,{children:"Ctrl"})," + ",(0,r.jsx)(n.kbd,{children:"C"})]}),"\n",(0,r.jsxs)(n.li,{children:["Or setup SSL - ",(0,r.jsx)(n.a,{href:"/docs/management#ssl-certificates",children:"here's a guide"})," on doing so"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"how-to--reference",children:"How-To / Reference"}),"\n",(0,r.jsx)(n.h3,{id:"how-to-reset-local-settings",children:"How to Reset Local Settings"}),"\n",(0,r.jsx)(n.p,{children:"Some settings are stored locally, in the browser's storage."}),"\n",(0,r.jsx)(n.p,{children:"In some instances cached assets can prevent your settings from being updated, in which case you may wish to reset local data."}),"\n",(0,r.jsx)(n.p,{children:'To clear all local data from the UI, head to the Config Menu, then click "Reset Local Settings", and Confirm when prompted.\nThis will not affect your config file. But be sure that you keep a backup of your config, if you\'ve not written changes it to disk.'}),"\n",(0,r.jsxs)(n.p,{children:["You can also view any and all data that Dashy is storing, using the developer tools. Open your browser's dev tools (usually ",(0,r.jsx)(n.kbd,{children:"F12"}),"), in Chromium head to the Application tab, or in Firefox go to the Storage tab. Select Local Storage, then scroll down the the URL Dashy is running on. You should now see all data being stored, and you can select and delete any fields you wish."]}),"\n",(0,r.jsxs)(n.p,{children:["For a full list of all data that may be cached, see the ",(0,r.jsx)(n.a,{href:"/docs/privacy#browser-storage",children:"Privacy Docs"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"how-to-make-a-bug-report",children:"How to make a bug report"}),"\n",(0,r.jsx)(n.h4,{id:"step-1---where-to-open-issues",children:"Step 1 - Where to open issues"}),"\n",(0,r.jsxs)(n.p,{children:["You will need a GitHub account in order to raise a ticket. You can then ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/new?assignees=lissy93&labels=%F0%9F%90%9B+Bug&template=bug.yml&title=%5BBUG%5D+%3Ctitle%3E",children:"click here"})," to open a new bug report."]}),"\n",(0,r.jsx)(n.h4,{id:"step-2---checking-its-not-already-covered",children:"Step 2 - Checking it's not already covered"}),"\n",(0,r.jsx)(n.p,{children:"Before submitting, please check that:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"A similar ticket has not previously been opened"}),"\n",(0,r.jsxs)(n.li,{children:["The issue is not covered in the ",(0,r.jsx)(n.a,{href:"/docs/troubleshooting",children:"troubleshooting guide"})," or ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/docs#readme",children:"docs"})]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"step-3---describe-the-issue",children:"Step 3 - Describe the Issue"}),"\n",(0,r.jsx)(n.p,{children:"Your ticket will likely be dealt with more effectively if you can explain the issue clearly, and provide all relevant supporting material."}),"\n",(0,r.jsx)(n.p,{children:"Complete the fields, asking for your environment info and version of Dashy.\nThen describe the issue, briefly explaining the steps to reproduce, expected outcome and actual outcome."}),"\n",(0,r.jsx)(n.h4,{id:"step-4---provide-supporting-info",children:"Step 4 - Provide Supporting Info"}),"\n",(0,r.jsx)(n.p,{children:"Where relevant please also include:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"A screenshot of the issue"}),"\n",(0,r.jsx)(n.li,{children:"The relevant parts of your config file"}),"\n",(0,r.jsxs)(n.li,{children:["Logs\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["If client-side issue, then include the browser logs (",(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"see how"}),")"]}),"\n",(0,r.jsx)(n.li,{children:"If server-side / during deployment, include the terminal output"}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"Take care to redact any personal info, (like IP addresses, auth hashes or API keys)."})}),"\n",(0,r.jsx)(n.h4,{id:"step-5---fix-released",children:"Step 5 - Fix Released"}),"\n",(0,r.jsx)(n.p,{children:"A maintainer will aim to respond within 48 hours.\nThe timeframe for resolving your issue, will vary depending on severity of the bug and the complexity of the fix.\nYou will be notified on your ticket, when a fix has been released."}),"\n",(0,r.jsxs)(n.p,{children:["Finally, be sure to remain respectful to other users and project maintainers, in line with the ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/.github/CODE_OF_CONDUCT#contributor-covenant-code-of-conduct",children:"Contributor Covenant Code of Conduct"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"how-to-open-browser-console",children:"How-To Open Browser Console"}),"\n",(0,r.jsx)(n.p,{children:"When raising a bug, one crucial piece of info needed is the browser's console output. This will help the developer diagnose and fix the issue."}),"\n",(0,r.jsxs)(n.p,{children:['If you\'ve been asked for this info, but are unsure where to find it, then it is under the "Console" tab, in the browsers developer tools, which can be opened with ',(0,r.jsx)(n.kbd,{children:"F12"}),". You can right-click the console, and select Save As to download the log."]}),"\n",(0,r.jsx)(n.p,{children:"To open dev tools, and jump straight to the console:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Win / Linux: ",(0,r.jsx)(n.kbd,{children:"Ctrl"})," + ",(0,r.jsx)(n.kbd,{children:"Shift"})," + ",(0,r.jsx)(n.kbd,{children:"J"})]}),"\n",(0,r.jsxs)(n.li,{children:["MacOS: ",(0,r.jsx)(n.kbd,{children:"Cmd"})," + ",(0,r.jsx)(n.kbd,{children:"Option"})," + ",(0,r.jsx)(n.kbd,{children:"J"})]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["For more detailed walk through, see ",(0,r.jsx)(n.a,{href:"https://support.shortpoint.com/support/solutions/articles/1000222881-save-browser-console-file",children:"this article"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"git-contributions-not-displaying",children:"Git Contributions not Displaying"}),"\n",(0,r.jsxs)(n.p,{children:["If you've contributed to Dashy (or any other project), but your contributions are not showing up on your GH profile, or in Dashy's ",(0,r.jsx)(n.a,{href:"/docs/credits",children:"Credits Page"}),", then this is likely a git config issue."]}),"\n",(0,r.jsxs)(n.p,{children:["These statistics are generated using the username / email associated with commits. This info needs to be setup on your local machine using ",(0,r.jsx)(n.a,{href:"https://git-scm.com/docs/git-config",children:(0,r.jsx)(n.code,{children:"git config"})}),"."]}),"\n",(0,r.jsx)(n.p,{children:"Run the following commands (replacing name + email with your info):"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:'git config --global user.name "John Doe"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"git config --global user.email johndoe@example.com"})}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["For more info, see ",(0,r.jsx)(n.a,{href:"https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup",children:"Git First Time Setup Docs"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"Note that only contributions to the master / main branch or a project are counted"})]})}function c(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(h,{...e})}):h(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>a});var i=s(96540);const r={},o=i.createContext(r);function t(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[9013],{77309(e,n,s){s.r(n),s.d(n,{assets:()=>l,contentTitle:()=>a,default:()=>c,frontMatter:()=>t,metadata:()=>i,toc:()=>d});const i=JSON.parse('{"id":"troubleshooting","title":"Troubleshooting","description":"**This document contains common problems and their solutions.**","source":"@site/docs/troubleshooting.md","sourceDirName":".","slug":"/troubleshooting","permalink":"/docs/troubleshooting","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/troubleshooting.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"App Management","permalink":"/docs/management"},"next":{"title":"Icons","permalink":"/docs/icons"}}');var r=s(74848),o=s(28453);const t={},a="Troubleshooting",l={},d=[{value:"Contents",id:"contents",level:2},{value:"Config not saving",id:"config-not-saving",level:2},{value:"Permission denied or read-only filesystem (EACCES, EROFS)",id:"permission-denied-or-read-only-filesystem-eacces-erofs",level:3},{value:"Kubernetes ConfigMap mount is read-only",id:"kubernetes-configmap-mount-is-read-only",level:3},{value:"SELinux or AppArmor blocks the write",id:"selinux-or-apparmor-blocks-the-write",level:3},{value:"Backup step fails so save aborts",id:"backup-step-fails-so-save-aborts",level:3},{value:"Save button is missing or returns 403 Forbidden",id:"save-button-is-missing-or-returns-403-forbidden",level:3},{value:"Save unavailable on Vercel, Netlify or other static hosts",id:"save-unavailable-on-vercel-netlify-or-other-static-hosts",level:3},{value:"/config-manager/save returns 404 or HTML",id:"config-managersave-returns-404-or-html",level:3},{value:""Invalid filename" when saving a sub-page",id:"invalid-filename-when-saving-a-sub-page",level:3},{value:""Cannot save to an external URL"",id:"cannot-save-to-an-external-url",level:3},{value:"Saved successfully but the UI shows the old config",id:"saved-successfully-but-the-ui-shows-the-old-config",level:3},{value:"Container crashes or restart loop after saving (3.1.0 and 3.1.1 only)",id:"container-crashes-or-restart-loop-after-saving-310-and-311-only",level:3},{value:"Intentionally read-only mode",id:"intentionally-read-only-mode",level:3},{value:"Refused to Connect in Modal or Workspace View",id:"refused-to-connect-in-modal-or-workspace-view",level:2},{value:"NGINX",id:"nginx",level:3},{value:"Caddy",id:"caddy",level:3},{value:"Apache",id:"apache",level:3},{value:"LightHttpd",id:"lighthttpd",level:3},{value:"404 / Routing issues",id:"404--routing-issues",level:2},{value:"404 On Static Hosting",id:"404-on-static-hosting",level:3},{value:"404 after Launch from Mobile Home Screen",id:"404-after-launch-from-mobile-home-screen",level:3},{value:"404 On Multi-Page Apps",id:"404-on-multi-page-apps",level:3},{value:"Dashy hosted at a sub-path (e.g. example.com/dashy)",id:"dashy-hosted-at-a-sub-path-eg-examplecomdashy",level:3},{value:"Sub-pages",id:"sub-pages",level:2},{value:"Sub-page shows "Unable to find config for ..."",id:"sub-page-shows-unable-to-find-config-for-",level:3},{value:"Old bookmark from before an upgrade",id:"old-bookmark-from-before-an-upgrade",level:4},{value:"The page was renamed or removed",id:"the-page-was-renamed-or-removed",level:4},{value:"The path points at an unreachable file",id:"the-path-points-at-an-unreachable-file",level:4},{value:"Page name literally "Main"",id:"page-name-literally-main",level:4},{value:"Service worker is serving a stale app",id:"service-worker-is-serving-a-stale-app",level:4},{value:"Sub-page missing from nav, or won't open when clicked",id:"sub-page-missing-from-nav-or-wont-open-when-clicked",level:3},{value:"Sub-page ignores its theme, layout or appConfig",id:"sub-page-ignores-its-theme-layout-or-appconfig",level:3},{value:"Sub-config files return 404",id:"sub-config-files-return-404",level:3},{value:"Remote Config Not Loading",id:"remote-config-not-loading",level:3},{value:"Build & memory errors",id:"build--memory-errors",level:2},{value:"Yarn Error",id:"yarn-error",level:3},{value:"yarn build fails inside the container",id:"yarn-build-fails-inside-the-container",level:3},{value:"High CPU or RAM Usage on Startup",id:"high-cpu-or-ram-usage-on-startup",level:3},{value:"Ineffective mark-compacts near heap limit Allocation failed",id:"ineffective-mark-compacts-near-heap-limit-allocation-failed",level:3},{value:"Command failed with signal "SIGKILL"",id:"command-failed-with-signal-sigkill",level:3},{value:"Node Sass does not yet support your current environment",id:"node-sass-does-not-yet-support-your-current-environment",level:3},{value:"Unreachable Code Error",id:"unreachable-code-error",level:3},{value:"Error: Cannot find module './_baseValues'",id:"error-cannot-find-module-_basevalues",level:3},{value:"Auth & OIDC",id:"auth--oidc",level:2},{value:"Auth Validation Error: "should be object"",id:"auth-validation-error-should-be-object",level:3},{value:"Keycloak Redirect Error",id:"keycloak-redirect-error",level:3},{value:"OIDC or Keycloak failure on numeric client IDs",id:"oidc-or-keycloak-failure-on-numeric-client-ids",level:3},{value:"Redirect loop after login",id:"redirect-loop-after-login",level:3},{value:"invalid_redirect_uri",id:"invalid_redirect_uri",level:3},{value:"Login works in the browser but the dashboard refuses to save anything (403)",id:"login-works-in-the-browser-but-the-dashboard-refuses-to-save-anything-403",level:3},{value:"Logged in but no admin controls",id:"logged-in-but-no-admin-controls",level:3},{value:"Login works but Dashy errors on the callback with "OIDC signinCallback returned no user"",id:"login-works-but-dashy-errors-on-the-callback-with-oidc-signincallback-returned-no-user",level:3},{value:"Sign-out leaves you stuck on Authentik",id:"sign-out-leaves-you-stuck-on-authentik",level:3},{value:"Untrusted certificate from Authentik",id:"untrusted-certificate-from-authentik",level:3},{value:"Numeric client_id getting truncated",id:"numeric-client_id-getting-truncated",level:3},{value:"Header auth: "Unauthorized - not from trusted proxy"",id:"header-auth-unauthorized---not-from-trusted-proxy",level:3},{value:"Header auth: "Unauthorized - missing user header"",id:"header-auth-unauthorized---missing-user-header",level:3},{value:""Invalid user object" warning on startup with an all-digit hash or placeholder",id:"invalid-user-object-warning-on-startup-with-an-all-digit-hash-or-placeholder",level:3},{value:"OIDC login looks like a CORS error in devtools, but the IdP is configured for CORS correctly",id:"oidc-login-looks-like-a-cors-error-in-devtools-but-the-idp-is-configured-for-cors-correctly",level:3},{value:"Docker & image issues",id:"docker--image-issues",level:2},{value:"App Not Starting After Update to 2.0.4",id:"app-not-starting-after-update-to-204",level:3},{value:"Mount Type Mismatch",id:"mount-type-mismatch",level:3},{value:"DockerHub toomanyrequests",id:"dockerhub-toomanyrequests",level:3},{value:"Solution 1 - Use an alternate container registry",id:"solution-1---use-an-alternate-container-registry",level:4},{value:"Solution 2 - Increase your rate limits",id:"solution-2---increase-your-rate-limits",level:4},{value:"Old image tags fail to pull",id:"old-image-tags-fail-to-pull",level:3},{value:"Healthcheck Failing in Docker",id:"healthcheck-failing-in-docker",level:3},{value:"SSL-enabled Dashy",id:"ssl-enabled-dashy",level:4},{value:"Custom port",id:"custom-port",level:4},{value:"Container is unhealthy past the grace period",id:"container-is-unhealthy-past-the-grace-period",level:4},{value:"Docker Login Fails on Ubuntu",id:"docker-login-fails-on-ubuntu",level:3},{value:"Styles and Assets not Updating",id:"styles-and-assets-not-updating",level:2},{value:"Config Validation Errors",id:"config-validation-errors",level:2},{value:"Invalid Host Header while running through ngrok",id:"invalid-host-header-while-running-through-ngrok",level:2},{value:"Warnings in the Console during deploy",id:"warnings-in-the-console-during-deploy",level:2},{value:"Status Checks Failing",id:"status-checks-failing",level:2},{value:"Widgets",id:"widgets",level:2},{value:"Widget Errors",id:"widget-errors",level:3},{value:"Find Error Message",id:"find-error-message",level:4},{value:"Check Config",id:"check-config",level:4},{value:"Timeout Error",id:"timeout-error",level:4},{value:"CORS error",id:"cors-error",level:4},{value:"More Info",id:"more-info",level:4},{value:"Widget CORS Errors",id:"widget-cors-errors",level:3},{value:"Option 1 - Ensure Correct Protocol",id:"option-1---ensure-correct-protocol",level:4},{value:"Option 2 - Set Headers",id:"option-2---set-headers",level:4},{value:"Option 3 - Proxying Request",id:"option-3---proxying-request",level:4},{value:"Option 4 - Use a plugin",id:"option-4---use-a-plugin",level:4},{value:"CORS Proxy connect ECONNREFUSED ... or getaddrinfo ENOTFOUND ...",id:"cors-proxy-connect-econnrefused--or-getaddrinfo-enotfound-",level:3},{value:"CORS Proxy Target-URL host '...' is blocked / must use http:// or https://",id:"cors-proxy-target-url-host--is-blocked--must-use-http-or-https",level:3},{value:"Widget Shows Error Incorrectly",id:"widget-shows-error-incorrectly",level:3},{value:"Weather Forecast Widget 401",id:"weather-forecast-widget-401",level:3},{value:"Widget Displaying Inaccurate Data",id:"widget-displaying-inaccurate-data",level:3},{value:"Public IP Widget not working for ipinfo or ipquery providers",id:"public-ip-widget-not-working-for-ipinfo-or-ipquery-providers",level:3},{value:"Font Awesome Icons not Displaying",id:"font-awesome-icons-not-displaying",level:2},{value:"Copy to Clipboard not Working",id:"copy-to-clipboard-not-working",level:2},{value:"How-To / Reference",id:"how-to--reference",level:2},{value:"How to Reset Local Settings",id:"how-to-reset-local-settings",level:3},{value:"How to make a bug report",id:"how-to-make-a-bug-report",level:3},{value:"Step 1 - Where to open issues",id:"step-1---where-to-open-issues",level:4},{value:"Step 2 - Checking it's not already covered",id:"step-2---checking-its-not-already-covered",level:4},{value:"Step 3 - Describe the Issue",id:"step-3---describe-the-issue",level:4},{value:"Step 4 - Provide Supporting Info",id:"step-4---provide-supporting-info",level:4},{value:"Step 5 - Fix Released",id:"step-5---fix-released",level:4},{value:"How-To Open Browser Console",id:"how-to-open-browser-console",level:3},{value:"Git Contributions not Displaying",id:"git-contributions-not-displaying",level:3}];function h(e){const n={a:"a",blockquote:"blockquote",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",kbd:"kbd",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"troubleshooting",children:"Troubleshooting"})}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.em,{children:(0,r.jsx)(n.strong,{children:"This document contains common problems and their solutions."})}),(0,r.jsx)(n.br,{}),"\nPlease ensure your issue isn't listed here, before opening a new ticket."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"Found something not listed here? Consider adding it, to help other users."})}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"contents",children:"Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#config-not-saving",children:"Config not saving"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#permission-denied-or-read-only-filesystem-eacces-erofs",children:"Permission denied or read-only filesystem"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#kubernetes-configmap-mount-is-read-only",children:"Kubernetes ConfigMap mount is read-only"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#selinux-or-apparmor-blocks-the-write",children:"SELinux or AppArmor blocks the write"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#backup-step-fails-so-save-aborts",children:"Backup step fails so save aborts"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#save-button-is-missing-or-returns-403-forbidden",children:"Save button is missing or returns 403"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#save-unavailable-on-vercel-netlify-or-other-static-hosts",children:"Save unavailable on Vercel, Netlify or other static hosts"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#config-managersave-returns-404-or-html",children:"/config-manager/save returns 404 or HTML"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#invalid-filename-when-saving-a-sub-page",children:'"Invalid filename" when saving a sub-page'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cannot-save-to-an-external-url",children:'"Cannot save to an external URL"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#saved-successfully-but-the-ui-shows-the-old-config",children:"Saved successfully but the UI shows the old config"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#container-crashes-or-restart-loop-after-saving-310-and-311-only",children:"Container crashes or restart loop after saving"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#intentionally-read-only-mode",children:"Intentionally read-only mode"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#refused-to-connect-in-modal-or-workspace-view",children:"Refused to Connect in Web Content View"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#404--routing-issues",children:"404 / Routing issues"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#404-on-static-hosting",children:"404 On Static Hosting"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#404-after-launch-from-mobile-home-screen",children:"404 from Mobile Home Screen"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#404-on-multi-page-apps",children:"404 On Multi-Page Apps"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#dashy-hosted-at-a-sub-path-eg-examplecomdashy",children:"Dashy hosted at a sub-path"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#sub-pages",children:"Sub-pages"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sub-page-shows-unable-to-find-config-for",children:'Sub-page shows "Unable to find config for ..."'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sub-page-missing-from-nav-or-wont-open-when-clicked",children:"Sub-page missing from nav, or won't open when clicked"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sub-page-ignores-its-theme-layout-or-appconfig",children:"Sub-page ignores its theme, layout or appConfig"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sub-config-files-return-404",children:"Sub-config files return 404"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#remote-config-not-loading",children:"Remote Config Not Loading"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#build--memory-errors",children:"Build & memory errors"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#yarn-error",children:"Yarn Build or Run Error"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsxs)(n.a,{href:"#yarn-build-fails-inside-the-container",children:[(0,r.jsx)(n.code,{children:"yarn build"})," fails inside the container"]})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#high-cpu-or-ram-usage-on-startup",children:"High CPU or RAM Usage on Startup"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#ineffective-mark-compacts-near-heap-limit-allocation-failed",children:"Heap limit Allocation failed"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#command-failed-with-signal-sigkill",children:'Command failed with signal "SIGKILL"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#node-sass-does-not-yet-support-your-current-environment",children:"Node Sass unsupported environment"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#unreachable-code-error",children:"Unreachable Code Error"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#error-cannot-find-module-_basevalues",children:"Cannot find module './_baseValues'"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#auth--oidc",children:"Auth & OIDC"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#auth-validation-error-should-be-object",children:'Auth Validation Error: "should be object"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#keycloak-redirect-error",children:"Keycloak Redirect Error"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#oidc-or-keycloak-failure-on-numeric-client-ids",children:"OIDC or Keycloak failure on numeric client IDs"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#redirect-loop-after-login",children:"Redirect loop after login"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#invalid_redirect_uri",children:"invalid_redirect_uri"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#login-works-in-the-browser-but-the-dashboard-refuses-to-save-anything-403",children:"Login works in the browser but the dashboard refuses to save anything (403)"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#logged-in-but-no-admin-controls",children:"Logged in but no admin controls"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#login-works-but-dashy-errors-on-the-callback-with-oidc-signincallback-returned-no-user",children:'Login works but Dashy errors on the callback with "OIDC signinCallback returned no user"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#sign-out-leaves-you-stuck-on-authentik",children:"Sign-out leaves you stuck on Authentik"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#untrusted-certificate-from-authentik",children:"Untrusted certificate from Authentik"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#numeric-client_id-getting-truncated",children:"Numeric client_id getting truncated"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#header-auth-unauthorized---not-from-trusted-proxy",children:'Header auth: "not from trusted proxy"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#header-auth-unauthorized---missing-user-header",children:'Header auth: "missing user header"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#invalid-user-object-warning-on-startup-with-an-all-digit-hash-or-placeholder",children:"Invalid user object with all-digit hash"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#oidc-login-looks-like-a-cors-error-in-devtools-but-the-idp-is-configured-for-cors-correctly",children:"OIDC login fails with CORS-shaped error from untrusted cert"})}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#docker--image-issues",children:"Docker & image issues"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#app-not-starting-after-update-to-204",children:"App Not Starting After Update to 2.0.4"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#mount-type-mismatch",children:"Mount Type Mismatch"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#dockerhub-toomanyrequests",children:"DockerHub toomanyrequests"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#old-image-tags-fail-to-pull",children:"Old image tags fail to pull"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#healthcheck-failing-in-docker",children:"Healthcheck Failing in Docker"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#docker-login-fails-on-ubuntu",children:"Docker Login Fails on Ubuntu"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#styles-and-assets-not-updating",children:"Styles and Assets not Updating"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#config-validation-errors",children:"Config Validation Errors"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#invalid-host-header-while-running-through-ngrok",children:"Ngrok Invalid Host Headers"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#warnings-in-the-console-during-deploy",children:"Warnings in the Console during deploy"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#status-checks-failing",children:"Status Checks Failing"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#widgets",children:"Widgets"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-errors",children:"Diagnosing Widget Errors"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-cors-errors",children:"Fixing Widget CORS Errors"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cors-proxy-connect-econnrefused--or-getaddrinfo-enotfound",children:"CORS Proxy connect ECONNREFUSED or ENOTFOUND"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#cors-proxy-target-url-host--is-blocked--must-use-http-or-https",children:"CORS Proxy Target-URL host blocked or scheme rejected"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-shows-error-incorrectly",children:"Widget Shows Error Incorrectly"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#weather-forecast-widget-401",children:"Weather Forecast Widget 401"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#widget-displaying-inaccurate-data",children:"Widget Displaying Inaccurate Data"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#public-ip-widget-not-working-for-ipinfo-or-ipquery-providers",children:"Public IP Widget not working for ipinfo or ipquery providers"})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#font-awesome-icons-not-displaying",children:"Font Awesome Icons not Displaying"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#copy-to-clipboard-not-working",children:"Copy to Clipboard not Working"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"#how-to--reference",children:"How-To / Reference"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#how-to-reset-local-settings",children:"How to Reset Local Settings"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#how-to-make-a-bug-report",children:"How to make a bug report"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"How-To Open Browser Console"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#git-contributions-not-displaying",children:"Git Contributions not Displaying"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"config-not-saving",children:"Config not saving"}),"\n",(0,r.jsxs)(n.p,{children:["There should be an error message, explaining the reason the config save failed. First check ",(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"browser console"})," (",(0,r.jsx)(n.kbd,{children:"F12"})," --\x3e Console), and then your server-side logs in the terminal. Then, see the following sections for solutions to each possible error."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{id:"config-not-saving-on-vercel--netlify--cdn"}),"\n",(0,r.jsx)(n.a,{id:"possible-issue-1-unable-to-call-save-endpoint-from-cdnstatic-server"}),"\n",(0,r.jsx)(n.a,{id:"unable-to-write-confyml-eacces-permission-denied"}),"\n",(0,r.jsx)(n.a,{id:"permission-denied-saving-config"}),"\n",(0,r.jsx)(n.a,{id:"possible-issue-2-unable-to-save"}),"\n",(0,r.jsx)(n.a,{id:"config-not-updating"}),"\n",(0,r.jsx)(n.a,{id:"config-still-not-updating"}),"\n",(0,r.jsx)(n.a,{id:"possible-issue-3-saved-but-not-updating"})]}),"\n",(0,r.jsx)(n.h3,{id:"permission-denied-or-read-only-filesystem-eacces-erofs",children:"Permission denied or read-only filesystem (EACCES, EROFS)"}),"\n",(0,r.jsxs)(n.p,{children:["The container can't write to your ",(0,r.jsx)(n.code,{children:"conf.yml"})," or its directory. Almost always an ownership mismatch: the host directory belongs to a different uid than the one Dashy runs as inside the container. Less commonly a read-only mount or an over-strict file mode."]}),"\n",(0,r.jsxs)(n.p,{children:["Dashy runs as UID=1000 (default non-root node user). You can see this by running ",(0,r.jsx)(n.code,{children:"docker exec -it dashy id"}),". Then, check who owns the user-data directory, with: ",(0,r.jsx)(n.code,{children:"docker exec -it dashy ls -la /app/user-data"})," - if it's not ",(0,r.jsx)(n.code,{children:"1000"})," then that's the issue. And the solution is just to run ",(0,r.jsx)(n.code,{children:"sudo chown -R 1000:1000 /path/to/your/user-data"})," to set the right owner."]}),"\n",(0,r.jsx)(n.p,{children:"Fixes:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Hand the directory to uid 1000"})," (recommended). Keeps the container running as a non-root user, which is how Dashy is built to run ",(0,r.jsx)(n.code,{children:"sudo chown -R 1000:1000 /path/to/your/user-data"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Run the container as your own user"})," if ",(0,r.jsx)(n.code,{children:"chown"})," isn't practical (multi-user hosts, NAS appliances, host directories you don't want relabelled). Add ",(0,r.jsx)(n.code,{children:"--user $(id -u):$(id -g)"})," to ",(0,r.jsx)(n.code,{children:"docker run"}),", or set ",(0,r.jsx)(n.code,{children:'user: "1000:1000"'})," (or your host uid:gid) on the service in ",(0,r.jsx)(n.code,{children:"docker-compose.yml"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Loosen a single-file mount"})," if its mode is ",(0,r.jsx)(n.code,{children:"444"}),". Narrow case, only fixes that one symptom: ",(0,r.jsx)(n.code,{children:"chmod 644 /path/to/conf.yml"})]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Common mistakes"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Using uid/gid 1001."})," A common guess on Synology, Unraid and similar where 1001 is the host's first user. Dashy's container is 1000, not 1001."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"chmod"})," alone for a UID mismatch."]})," Loosens permissions but doesn't change who owns the file. You need ",(0,r.jsx)(n.code,{children:"chown"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"chmod -R 777"})," or ",(0,r.jsx)(n.code,{children:"775"}),"."]})," Works as a workaround, masks the real problem, weakens security. Use ",(0,r.jsx)(n.code,{children:"chown"})," to the right uid instead."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Other gotchas:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Named Docker volumes"})," (created with ",(0,r.jsx)(n.code,{children:"docker volume create"}),") inherit ownership from whatever first writes to them. If an older container set them up as root, the diagnose step will show that. Recreate the volume or ",(0,r.jsx)(n.code,{children:"chown"})," the underlying directory under ",(0,r.jsx)(n.code,{children:"/var/lib/docker/volumes/"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"macOS hosts"})," rarely hit this. Docker Desktop transparently maps host uid to container uid through its VM. If saves are failing on macOS, look elsewhere first."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Storage layers that ignore POSIX permissions"})," (some NAS app-data folders use FUSE, SMB or overlay mounts where ",(0,r.jsx)(n.code,{children:"chmod"})," and ",(0,r.jsx)(n.code,{children:"chgrp"})," are silent no-ops). Bind-mount user-data from a native filesystem path instead."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"kubernetes-configmap-mount-is-read-only",children:"Kubernetes ConfigMap mount is read-only"}),"\n",(0,r.jsxs)(n.p,{children:["If you've mounted your ",(0,r.jsx)(n.code,{children:"conf.yml"})," from a ConfigMap, writes will always fail with ",(0,r.jsx)(n.code,{children:"EROFS"})," regardless of UID. ConfigMap volumes are read-only by design. Either treat the ConfigMap as the source of truth and edit it directly (saves through the UI won't work), or use a writable volume type like a ",(0,r.jsx)(n.code,{children:"PersistentVolumeClaim"})," for ",(0,r.jsx)(n.code,{children:"user-data/"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"selinux-or-apparmor-blocks-the-write",children:"SELinux or AppArmor blocks the write"}),"\n",(0,r.jsxs)(n.p,{children:["If you're on RHEL/Fedora, or systems with SELinux or AppArmour, and you've confirmed permissions are fine, and container's UID matches the host owner, but you still see ",(0,r.jsx)(n.code,{children:"EACCES"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["For SELinux, add the ",(0,r.jsx)(n.code,{children:":Z"})," flag to your volume mount so Docker relabels it for the container (e.g. ",(0,r.jsx)(n.code,{children:"volumes: [ './user-data:/app/user-data:Z' ]"}),")"]}),"\n",(0,r.jsxs)(n.p,{children:["For AppArmor, check ",(0,r.jsx)(n.code,{children:"dmesg"})," for ",(0,r.jsx)(n.code,{children:'apparmor="DENIED"'})," lines and adjust the profile. Disabling enforcement is a last resort."]}),"\n",(0,r.jsx)(n.h3,{id:"backup-step-fails-so-save-aborts",children:"Backup step fails so save aborts"}),"\n",(0,r.jsxs)(n.p,{children:["Before each save, Dashy backs up the current ",(0,r.jsx)(n.code,{children:"conf.yml"})," to ",(0,r.jsx)(n.code,{children:"user-data/config-backups/"}),". If that folder can't be written, the whole save aborts with ",(0,r.jsx)(n.code,{children:"Unable to backup conf.yml"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"Two ways out:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Point ",(0,r.jsx)(n.code,{children:"BACKUP_DIR"})," at a writable path"]}),"\n",(0,r.jsxs)(n.li,{children:["Set ",(0,r.jsx)(n.code,{children:"DISABLE_CONFIG_BACKUPS=true"})," to skip the backup step entirely"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"save-button-is-missing-or-returns-403-forbidden",children:"Save button is missing or returns 403 Forbidden"}),"\n",(0,r.jsxs)(n.p,{children:["Have you got auth setup? If so, make sure you are logged in as an admin, or set ",(0,r.jsx)(n.code,{children:"type: admin"})," to your user in ",(0,r.jsx)(n.code,{children:"conf.yml"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Beyond that, there's several other config options which prevent saving the config file, so if you didn't mean to add them, just remove from ",(0,r.jsx)(n.code,{children:"conf.yml"})]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"appConfig.preventWriteToDisk: true"})})," disables disk save and the button"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"appConfig.preventLocalSave: true"})}),' disables the "Local" save option']}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"appConfig.disableConfiguration: true"})})," hides the editor entirely. ",(0,r.jsx)(n.code,{children:"disableConfigurationForNonAdmin: true"})," does the same just for non-admins."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"save-unavailable-on-vercel-netlify-or-other-static-hosts",children:"Save unavailable on Vercel, Netlify or other static hosts"}),"\n",(0,r.jsx)(n.p,{children:'Updating source config file on static hosts is not possible, since they have no Node server, nor have write access to modify any files.\nThe "Local" save mode will still work (changes are just persisted in your browser), but the real solution is to copy/export the updated YAML and replace it in the source config file in your repo.'}),"\n",(0,r.jsxs)(n.p,{children:["Related: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1465",children:"#1465"}),"."]}),"\n",(0,r.jsxs)(n.h3,{id:"config-managersave-returns-404-or-html",children:[(0,r.jsx)(n.code,{children:"/config-manager/save"})," returns 404 or HTML"]}),"\n",(0,r.jsxs)(n.p,{children:["How are you running/serving Dashy?\nIf you've got a reverse proxy which only forwards specific path prefixes then maybe you're missing the ",(0,r.jsx)(n.code,{children:"/config-manager/*"})," API endpoints?\nCheck the failed request in the browser's Network tab. If the response is HTML (a proxy error page) or a plain 404, your proxy isn't routing the path. Add ",(0,r.jsx)(n.code,{children:"/config-manager/"})," to whatever you're forwarding, or simplify the rules so everything reaches Dashy."]}),"\n",(0,r.jsx)(n.p,{children:"Or if you're serving up the compiled Vue app directly, instead of using the Node server, then the endpoint won't be available."}),"\n",(0,r.jsx)(n.h3,{id:"invalid-filename-when-saving-a-sub-page",children:'"Invalid filename" when saving a sub-page'}),"\n",(0,r.jsxs)(n.p,{children:["The save endpoint rejects sub-page filenames with path separators or non-yaml extensions. Check the ",(0,r.jsx)(n.code,{children:"path:"})," value of the page in your ",(0,r.jsx)(n.code,{children:"pages:"})," block. It needs to be a plain basename like ",(0,r.jsx)(n.code,{children:"home.yml"}),", not ",(0,r.jsx)(n.code,{children:"pages/home.yml"})," or ",(0,r.jsx)(n.code,{children:"home.txt"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"cannot-save-to-an-external-url",children:'"Cannot save to an external URL"'}),"\n",(0,r.jsxs)(n.p,{children:["The sub-page you are editing is loaded from a remote URL. Dashy can't write back to that URL.\nYou will need to edit the file at it's origin yourself instead (click the Export to view the YAML).\nOr you could download the config to ",(0,r.jsx)(n.code,{children:"user-data/something.yml"}),", and update ",(0,r.jsx)(n.code,{children:"path:"})," to point to the local version."]}),"\n",(0,r.jsx)(n.h3,{id:"saved-successfully-but-the-ui-shows-the-old-config",children:"Saved successfully but the UI shows the old config"}),"\n",(0,r.jsx)(n.p,{children:"Two unrelated causes share this symptom:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Local storage overrides the file."})," Dashy lets users save settings locally in browser storage, which take priority over ",(0,r.jsx)(n.code,{children:"conf.yml"}),'. Open Dashy in incognito to confirm. If the changes appear there, clear local settings via Config menu > "Clear Local Settings".']}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Docker isn't picking up file changes."})," Some text editors save by replacing the inode, which breaks single-file bind mounts. Edit the file in place, or mount the parent directory rather than the single file. ",(0,r.jsx)(n.a,{href:"https://medium.com/@jonsbun/why-need-to-be-careful-when-mounting-single-files-into-a-docker-container-4f929340834",children:"More background"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.a,{id:"container-crashes-or-restart-loop-after-saving-config"})}),"\n",(0,r.jsx)(n.h3,{id:"container-crashes-or-restart-loop-after-saving-310-and-311-only",children:"Container crashes or restart loop after saving (3.1.0 and 3.1.1 only)"}),"\n",(0,r.jsxs)(n.p,{children:["If your container crashes or restart-loops right after clicking save, with logs like ",(0,r.jsx)(n.code,{children:"ERR_HTTP_HEADERS_SENT"})," or ",(0,r.jsx)(n.code,{children:"ERR_STREAM_WRITE_AFTER_END"}),", this was a known double-",(0,r.jsx)(n.code,{children:"res.end()"})," bug in 3.1.0 and 3.1.1. Fixed in v3.2.13 and later."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker pull lissy93/dashy:latest\ndocker compose up -d --force-recreate\n"})}),"\n",(0,r.jsx)(n.h3,{id:"intentionally-read-only-mode",children:"Intentionally read-only mode"}),"\n",(0,r.jsxs)(n.p,{children:['To hide the "Save to disk" UI for everyone, set ',(0,r.jsx)(n.code,{children:"appConfig.preventWriteToDisk: true"})," in ",(0,r.jsx)(n.code,{children:"conf.yml"}),". This is a UI-only flag \u2014 the ",(0,r.jsx)(n.code,{children:"/config-manager/save"})," server endpoint itself is gated by the configured auth method (",(0,r.jsx)(n.code,{children:"auth.users"})," with ",(0,r.jsx)(n.code,{children:"ENABLE_HTTP_AUTH"}),", OIDC/Keycloak admin role, header-auth, etc.), so anyone unauthenticated or non-admin already can't save regardless of this flag. For Docker users, you can harden things further by mounting ",(0,r.jsx)(n.code,{children:"user-data"})," (or just ",(0,r.jsx)(n.code,{children:"conf.yml"}),") as read-only \u2014 the kernel will refuse the write even before the server tries."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h2,{id:"refused-to-connect-in-modal-or-workspace-view",children:[(0,r.jsx)(n.code,{children:"Refused to Connect"})," in Modal or Workspace View"]}),"\n",(0,r.jsx)(n.p,{children:"This is not an issue with Dashy, but instead caused by the target app preventing direct access through embedded elements."}),"\n",(0,r.jsxs)(n.p,{children:["As defined in ",(0,r.jsx)(n.a,{href:"https://datatracker.ietf.org/doc/html/rfc7034",children:"RFC-7034"}),", for any web content to be accessed through an embedded element, it must have the ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options",children:(0,r.jsx)(n.code,{children:"X-Frame-Options"})})," HTTP header set to ",(0,r.jsx)(n.code,{children:"ALLOW"}),". If you are getting a ",(0,r.jsx)(n.code,{children:"Refused to Connect"})," error then this header is set to ",(0,r.jsx)(n.code,{children:"DENY"})," (or ",(0,r.jsx)(n.code,{children:"SAMEORIGIN"})," and it's on a different host). Thankfully, for self-hosted services, it is easy to set these headers."]}),"\n",(0,r.jsx)(n.p,{children:"These settings are usually set in the config file for the web server that's hosting the target application, here are some examples of how to enable cross-origin access with common web servers:"}),"\n",(0,r.jsx)(n.h3,{id:"nginx",children:"NGINX"}),"\n",(0,r.jsxs)(n.p,{children:["In NGINX, you can use the ",(0,r.jsx)(n.a,{href:"https://nginx.org/en/docs/http/ngx_http_headers_module.html",children:(0,r.jsx)(n.code,{children:"add_header"})})," module within the app block."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"server {\n ...\n add_header X-Frame-Options SAMEORIGIN always;\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Then reload with ",(0,r.jsx)(n.code,{children:"service nginx reload"})]}),"\n",(0,r.jsx)(n.h3,{id:"caddy",children:"Caddy"}),"\n",(0,r.jsxs)(n.p,{children:["In Caddy, you can use the ",(0,r.jsx)(n.a,{href:"https://caddyserver.com/docs/caddyfile/directives/header",children:(0,r.jsx)(n.code,{children:"header"})})," directive."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"header {\n X-Frame-Options SAMEORIGIN\n}\n"})}),"\n",(0,r.jsx)(n.h3,{id:"apache",children:"Apache"}),"\n",(0,r.jsxs)(n.p,{children:["In Apache, you can use the ",(0,r.jsx)(n.a,{href:"https://httpd.apache.org/docs/current/mod/mod_headers.html",children:(0,r.jsx)(n.code,{children:"mod_headers"})})," module to set the ",(0,r.jsx)(n.code,{children:"X-Frame-Options"})," in your config file. This file is usually located somewhere like `/etc/apache2/httpd.conf"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'Header set X-Frame-Options: "ALLOW-FROM http://[dashy-location]/"\n'})}),"\n",(0,r.jsx)(n.h3,{id:"lighthttpd",children:"LightHttpd"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Content-Security-Policy: frame-ancestors 'self' https://[dashy-location]/\n"})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"404--routing-issues",children:"404 / Routing issues"}),"\n",(0,r.jsx)(n.h3,{id:"404-on-static-hosting",children:"404 On Static Hosting"}),"\n",(0,r.jsx)(n.p,{children:"If you're seeing Dashy's 404 page on initial load/ refresh, and then the main app when you go back to Home, then this is likely caused by the Vue router, and if so can be fixed in one of two ways."}),"\n",(0,r.jsxs)(n.p,{children:["The first solution is to switch the routing mode, from HTML5 ",(0,r.jsx)(n.code,{children:"history"})," mode to ",(0,r.jsx)(n.code,{children:"hash"})," mode, by rebuilding Dashy with the ",(0,r.jsx)(n.code,{children:"VITE_APP_ROUTING_MODE=hash"})," build-time environment variable set."]}),"\n",(0,r.jsxs)(n.p,{children:["If this works, but you wish to continue using HTML5 history mode, then a bit of extra ",(0,r.jsx)(n.a,{href:"/docs/management#web-server-configuration",children:"server configuration"})," is required. This is explained in more detaail in the ",(0,r.jsx)(n.a,{href:"https://router.vuejs.org/guide/essentials/history-mode.html",children:"Vue Docs"}),". Once completed, you can then use ",(0,r.jsx)(n.code,{children:"VITE_APP_ROUTING_MODE=history"})," (the default) again, for neater URLs."]}),"\n",(0,r.jsx)(n.h3,{id:"404-after-launch-from-mobile-home-screen",children:"404 after Launch from Mobile Home Screen"}),"\n",(0,r.jsxs)(n.p,{children:['Similar to the above issue, if you get a 404 after using iOS and Android\'s "Add to Home Screen" feature, then this is caused by Vue router.\nIt can be fixed by rebuilding Dashy with the ',(0,r.jsx)(n.code,{children:"VITE_APP_ROUTING_MODE=hash"})," build-time environment variable set."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/628",children:"#628"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/762",children:"#762"})]}),"\n",(0,r.jsx)(n.h3,{id:"404-on-multi-page-apps",children:"404 On Multi-Page Apps"}),"\n",(0,r.jsxs)(n.p,{children:["Similar to above, if you get a 404 error when visiting a page directly on multi-page apps, then this can be fixed by rebuilding Dashy with the ",(0,r.jsx)(n.code,{children:"VITE_APP_ROUTING_MODE=hash"})," build-time environment variable set, then refreshing the page."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/670",children:"#670"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/763",children:"#763"})]}),"\n",(0,r.jsxs)(n.h3,{id:"dashy-hosted-at-a-sub-path-eg-examplecomdashy",children:["Dashy hosted at a sub-path (e.g. ",(0,r.jsx)(n.code,{children:"example.com/dashy"}),")"]}),"\n",(0,r.jsxs)(n.p,{children:["If the homepage works but sub-page links 404, or assets fail to load, it's almost always the base path.\nRebuild with ",(0,r.jsx)(n.code,{children:"BASE_URL"})," set to the sub-path - leading slash, no trailing slash:"]}),"\n",(0,r.jsxs)(n.p,{children:["Vue Router uses this to prefix every route. Without it, links resolve to ",(0,r.jsx)(n.code,{children:"/home/..."})," instead of ",(0,r.jsx)(n.code,{children:"/dashy/home/..."})," and skip your reverse proxy altogether. More detail in ",(0,r.jsx)(n.a,{href:"/docs/management#web-server-configuration",children:"web-server configuration"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"sub-pages",children:"Sub-pages"}),"\n",(0,r.jsx)(n.h3,{id:"sub-page-shows-unable-to-find-config-for-",children:'Sub-page shows "Unable to find config for ..."'}),"\n",(0,r.jsxs)(n.p,{children:["This means Dashy couldn't match the URL segment to any entry in your ",(0,r.jsx)(n.code,{children:"pages:"})," list. A few causes:"]}),"\n",(0,r.jsx)(n.h4,{id:"old-bookmark-from-before-an-upgrade",children:"Old bookmark from before an upgrade"}),"\n",(0,r.jsxs)(n.p,{children:["Slugs are now trimmed more aggressively (e.g. ",(0,r.jsx)(n.code,{children:"\ud83c\udf10 Command Center"})," used to give ",(0,r.jsx)(n.code,{children:"-command-center"}),", now gives ",(0,r.jsx)(n.code,{children:"command-center"}),"). Re-bookmark from the nav, or update the URL by hand."]}),"\n",(0,r.jsx)(n.h4,{id:"the-page-was-renamed-or-removed",children:"The page was renamed or removed"}),"\n",(0,r.jsxs)(n.p,{children:["The URL no longer resolves to anything. Check the ",(0,r.jsx)(n.code,{children:"pages:"})," array in ",(0,r.jsx)(n.code,{children:"conf.yml"})," and confirm the sub-page still exists."]}),"\n",(0,r.jsx)(n.h4,{id:"the-path-points-at-an-unreachable-file",children:"The path points at an unreachable file"}),"\n",(0,r.jsxs)(n.p,{children:["If the sub-config YAML can't be fetched (404, CORS, auth), you'll see \"Unable to load config from ...\" instead. Verify the ",(0,r.jsx)(n.code,{children:"path:"})," is correct, reachable from the browser, and CORS-open if remote."]}),"\n",(0,r.jsx)(n.h4,{id:"page-name-literally-main",children:'Page name literally "Main"'}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"main"}),' is reserved in the URL scheme to mean "the root config". A page named "Main" becomes reachable at ',(0,r.jsx)(n.code,{children:"/home/main-page"})," (not ",(0,r.jsx)(n.code,{children:"/home/main"}),"). Rename the page if that's confusing."]}),"\n",(0,r.jsx)(n.h4,{id:"service-worker-is-serving-a-stale-app",children:"Service worker is serving a stale app"}),"\n",(0,r.jsxs)(n.p,{children:["Hard-refresh (",(0,r.jsx)(n.kbd,{children:"Ctrl"})," + ",(0,r.jsx)(n.kbd,{children:"F5"}),") after a major upgrade. The PWA cache may still be pointing at old routes. Also see ",(0,r.jsx)(n.a,{href:"#styles-and-assets-not-updating",children:"Styles and Assets not Updating"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"sub-page-missing-from-nav-or-wont-open-when-clicked",children:"Sub-page missing from nav, or won't open when clicked"}),"\n",(0,r.jsxs)(n.p,{children:["If page defined in ",(0,r.jsx)(n.code,{children:"pages:"})," is nowhere in the nav bar, or its link goes to a different page, then there's probably something wrong with the name you chose. Note that Dashy strips out any non-alphanumeric characters."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Ensure each page does have a valid ",(0,r.jsx)(n.code,{children:"name"})," and ",(0,r.jsx)(n.code,{children:"path"})," field"]}),"\n",(0,r.jsx)(n.li,{children:"Check two pages don't have the same/similar name"}),"\n",(0,r.jsx)(n.li,{children:"Check each page has a name which has at least some alpha-numeric characters"}),"\n",(0,r.jsx)(n.li,{children:"Very long names could be being stripped/truncated"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"sub-page-ignores-its-theme-layout-or-appconfig",children:"Sub-page ignores its theme, layout or appConfig"}),"\n",(0,r.jsxs)(n.p,{children:["This is by design. Only the ",(0,r.jsx)(n.code,{children:"appConfig"})," from your root ",(0,r.jsx)(n.code,{children:"conf.yml"})," is used - theme, layout, iconSize, statusCheck, etc. are inherited globally so behaviour stays consistent across pages."]}),"\n",(0,r.jsxs)(n.p,{children:["If you put ",(0,r.jsx)(n.code,{children:"appConfig"})," inside a sub-page YAML, it's silently dropped on load. Move the values to the root config. See ",(0,r.jsx)(n.a,{href:"/docs/pages-and-sections#restrictions",children:"Restrictions"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"sub-config-files-return-404",children:"Sub-config files return 404"}),"\n",(0,r.jsxs)(n.p,{children:["If your ",(0,r.jsx)(n.code,{children:"conf.yml"})," references additional pages via ",(0,r.jsx)(n.code,{children:"pages:"})," and the browser shows ",(0,r.jsx)(n.code,{children:"Sub-config load failed: /something.yml"}),", the cause is almost always a Docker mount that only exposes ",(0,r.jsx)(n.code,{children:"conf.yml"})," and not the rest of ",(0,r.jsx)(n.code,{children:"user-data/"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"If you've done this:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"volumes:\n - ./my-conf.yml:/app/user-data/conf.yml\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Only ",(0,r.jsx)(n.code,{children:"conf.yml"})," exists inside the container. Anything it references (sub-configs, custom icons, fonts, CSS) isn't there."]}),"\n",(0,r.jsx)(n.p,{children:"Mount the directory instead:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"volumes:\n - ./user-data:/app/user-data\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Now everything in your ",(0,r.jsx)(n.code,{children:"user-data"})," folder is reachable at the web root. Same applies to ",(0,r.jsx)(n.code,{children:"docker run -v"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"remote-config-not-loading",children:"Remote Config Not Loading"}),"\n",(0,r.jsx)(n.p,{children:"If you've got a multi-page dashboard, and are hosting the additional config files yourself, then CORS rules will apply. A CORS error will look something like:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access to XMLHttpRequest at 'https://example.com/raw/my-config.yml' from origin 'http://dashy.local' has been blocked by CORS policy:\nNo 'Access-Control-Allow-Origin' header is present on the requested resource.\n"})}),"\n",(0,r.jsx)(n.p,{children:"The solution is to add the appropriate headers onto the target server, to allow it to accept requests from the origin where you're running Dashy."}),"\n",(0,r.jsxs)(n.p,{children:["If it is a remote service, that you do not have admin access to, then another option is to proxy the request. Either host your own, or use a publicly accessible service, like ",(0,r.jsx)(n.a,{href:"https://allorigins.win",children:"allorigins.win"}),", e.g: ",(0,r.jsx)(n.code,{children:"https://api.allorigins.win/raw?url=https://pastebin.com/raw/4tZpaJV5"}),". For git-based services specifically, there's ",(0,r.jsx)(n.a,{href:"https://raw.githack.com/",children:"raw.githack.com"})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"build--memory-errors",children:"Build & memory errors"}),"\n",(0,r.jsx)(n.h3,{id:"yarn-error",children:"Yarn Error"}),"\n",(0,r.jsxs)(n.p,{children:["For more info, see ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1",children:"Issue #1"})]}),"\n",(0,r.jsxs)(n.p,{children:["First of all, check that you've got yarn installed correctly - see the ",(0,r.jsx)(n.a,{href:"https://classic.yarnpkg.com/en/docs/install",children:"yarn installation docs"})," for more info."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're getting an error about scenarios, then you've likely installed the wrong yarn... (you're ",(0,r.jsx)(n.a,{href:"https://github.com/yarnpkg/yarn/issues/2821",children:"not"})," the only one!). You can fix it by uninstalling, adding the correct repo, and reinstalling, for example, in Debian:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"sudo apt remove yarn"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:'echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"sudo apt update && sudo apt install yarn"})}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Alternatively, as a workaround, you have several options:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Try using ",(0,r.jsx)(n.a,{href:"https://www.npmjs.com/get-npm",children:"NPM"})," instead: So clone, cd, then run ",(0,r.jsx)(n.code,{children:"npm install"}),", ",(0,r.jsx)(n.code,{children:"npm run build"})," and ",(0,r.jsx)(n.code,{children:"npm start"})]}),"\n",(0,r.jsxs)(n.li,{children:["Try using ",(0,r.jsx)(n.a,{href:"https://www.docker.com/get-started",children:"Docker"})," instead, and all of the system setup and dependencies will already be taken care of. So from within the directory, just run ",(0,r.jsx)(n.code,{children:"docker build -t lissy93/dashy ."})," to build, and then use docker start to run the project, e.g: ",(0,r.jsx)(n.code,{children:"docker run -it -p 8080:8080 lissy93/dashy"})," (see the ",(0,r.jsx)(n.a,{href:"/docs/deployment#deploy-with-docker",children:"deploying docs"})," for more info)"]}),"\n"]}),"\n",(0,r.jsxs)(n.h3,{id:"yarn-build-fails-inside-the-container",children:[(0,r.jsx)(n.code,{children:"yarn build"})," fails inside the container"]}),"\n",(0,r.jsxs)(n.p,{children:["If you run ",(0,r.jsx)(n.code,{children:"docker exec yarn build"})," and get ",(0,r.jsx)(n.code,{children:"vite: not found"})," (or similar), it's because the published image ships only production dependencies. The build toolchain (vite, vue-tsc, sass, etc.) lives in ",(0,r.jsx)(n.code,{children:"devDependencies"})," and isn't installed in the runtime image."]}),"\n",(0,r.jsxs)(n.p,{children:["You almost certainly don't need to rebuild. Dashy's Express server reads ",(0,r.jsx)(n.code,{children:"user-data/conf.yml"})," on every request, so config changes show up on a page refresh, no rebuild required."]}),"\n",(0,r.jsxs)(n.p,{children:["If you genuinely need a fresh build (you've patched something in ",(0,r.jsx)(n.code,{children:"src/"}),"), do it on the host with ",(0,r.jsx)(n.code,{children:"yarn install && yarn build"}),", or build a custom image from a checkout of the repo."]}),"\n",(0,r.jsx)(n.h3,{id:"high-cpu-or-ram-usage-on-startup",children:"High CPU or RAM Usage on Startup"}),"\n",(0,r.jsxs)(n.p,{children:["When the Dashy container first starts, it runs a Vue production build in parallel with the server. This is ",(0,r.jsx)(n.strong,{children:"a one-time cost per container start"}),", but it briefly uses around ",(0,r.jsx)(n.strong,{children:"1\u20131.5 GB of RAM and 100% of one CPU core"})," for anywhere from 30 seconds to several minutes (depending on host speed). On Pi-class hardware or VMs with less than 1 GB of RAM, this spike can be enough to lock up the host."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"To work around it:"})}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Allocate at least 1 GB of RAM to the container"})," - 2 GB is recommended on Raspberry Pi or low-powered VMs. Anything below 512 MB is unlikely to complete the first build."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Set explicit Docker resource limits"})," so the build can't starve other services on the same host:\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"services:\n dashy:\n image: lissy93/dashy:latest\n deploy:\n resources:\n limits:\n memory: 2g\n cpus: '1.5'\n"})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Wait it out"})," - once the build completes, idle CPU drops to near zero and idle RAM is typically under 100 MB. If you watch ",(0,r.jsx)(n.code,{children:"docker stats"}),", you'll see the spike taper off."]}),"\n",(0,r.jsxs)(n.li,{children:["If the spike never tapers (i.e., Dashy stays at 100% CPU forever and never serves the page), see ",(0,r.jsx)(n.a,{href:"#ineffective-mark-compacts-near-heap-limit-allocation-failed",children:"Heap limit Allocation failed"})," below - that usually means the build was killed mid-way and is being retried."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1585",children:"#1585"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/969",children:"#969"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1500",children:"#1500"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/877",children:"#877"})]}),"\n",(0,r.jsx)(n.h3,{id:"ineffective-mark-compacts-near-heap-limit-allocation-failed",children:"Ineffective mark-compacts near heap limit Allocation failed"}),"\n",(0,r.jsx)(n.p,{children:"If you see an error message, similar to:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"<--- Last few GCs ---\x3e\n\n[61:0x74533040] 229060 ms: Mark-sweep (reduce) 127.1 (236.9) -> 127.1 (137.4) MB, 5560.7 / 0.3 ms (average mu = 0.286, current mu = 0.011) allocation failure scavenge might not succeed\n\n<--- JS stacktrace ---\x3e\n\nFATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory\n"})}),"\n",(0,r.jsxs)(n.p,{children:["This is likely caused by insufficient memory allocation to the container. When the container first starts up, or has to rebuild, the memory usage spikes, and if there isn't enough memory, it may terminate. This can be specified with, for example: ",(0,r.jsx)(n.code,{children:"--memory=1024m"}),". For more info, see ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/containers/resource_constraints/",children:"Docker: Runtime options with Memory, CPUs, and GPUs"}),". For more context on what the spike is, see ",(0,r.jsx)(n.a,{href:"#high-cpu-or-ram-usage-on-startup",children:"High CPU or RAM Usage on Startup"})," above."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/380",children:"#380"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/350",children:"#350"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/297",children:"#297"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/349",children:"#349"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/510",children:"#510"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/511",children:"#511"})," and ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/834",children:"#834"})]}),"\n",(0,r.jsx)(n.h3,{id:"command-failed-with-signal-sigkill",children:'Command failed with signal "SIGKILL"'}),"\n",(0,r.jsxs)(n.p,{children:["In Docker, this can be caused by not enough memory. When the container first starts up, or has to rebuild, the memory usage spikes, and so a larger allocation may be required. This can be specified with, for example: ",(0,r.jsx)(n.code,{children:"--memory=1024m"}),". For more info, see ",(0,r.jsx)(n.a,{href:"https://docs.docker.com/config/containers/resource_constraints/",children:"Docker: Runtime options with Memory, CPUs, and GPUs"})]}),"\n",(0,r.jsxs)(n.p,{children:["See also ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/624",children:"#624"})]}),"\n",(0,r.jsx)(n.h3,{id:"node-sass-does-not-yet-support-your-current-environment",children:"Node Sass does not yet support your current environment"}),"\n",(0,r.jsxs)(n.p,{children:["Caused by node-sass's binaries being built for a for a different architecture\nTo fix this, just run: ",(0,r.jsx)(n.code,{children:"yarn rebuild node-sass"})]}),"\n",(0,r.jsx)(n.h3,{id:"unreachable-code-error",children:"Unreachable Code Error"}),"\n",(0,r.jsxs)(n.p,{children:["An error similar to: ",(0,r.jsx)(n.code,{children:"Fatal error in , line 0. Unreachable code, FailureMessage Object: 0xffe6c8ac. Illegal instruction (core dumped)"}),"\nIs related to a bug in a downstream package, see ",(0,r.jsx)(n.a,{href:"https://github.com/nodejs/docker-node/issues/1477",children:"nodejs/docker-node#1477"}),".\nUsually, updating your system and packages will resolve the issue."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/776",children:"#776"})]}),"\n",(0,r.jsx)(n.h3,{id:"error-cannot-find-module-_basevalues",children:"Error: Cannot find module './_baseValues'"}),"\n",(0,r.jsxs)(n.p,{children:["Clearing the cache should fix this: ",(0,r.jsx)(n.code,{children:"yarn cache clean"}),"\nIf the issue persists, remove (",(0,r.jsx)(n.code,{children:"rm -rf node_modules\\ yarn.lock"}),") and reinstall (",(0,r.jsx)(n.code,{children:"yarn"}),") node_modules"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"auth--oidc",children:"Auth & OIDC"}),"\n",(0,r.jsx)(n.h3,{id:"auth-validation-error-should-be-object",children:'Auth Validation Error: "should be object"'}),"\n",(0,r.jsxs)(n.p,{children:["In V 1.6.5 an update was made that in the future will become a breaking change. You will need to update you config to reflect this before V 2.0.0 is released. In the meantime, your previous config will continue to function normally, but you will see a validation warning. The change means that the structure of the ",(0,r.jsx)(n.code,{children:"appConfig.auth"})," object is now an object, which has a ",(0,r.jsx)(n.code,{children:"users"})," property."]}),"\n",(0,r.jsxs)(n.p,{children:["For more info, see ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/discussions/177",children:"this announcement"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"You can fix this by replacing:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"auth:\n- user: xxx\n hash: xxx\n"})}),"\n",(0,r.jsx)(n.p,{children:"with"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"auth:\n users:\n - user: xxx\n hash: xxx\n"})}),"\n",(0,r.jsx)(n.h3,{id:"keycloak-redirect-error",children:"Keycloak Redirect Error"}),"\n",(0,r.jsxs)(n.p,{children:["Check the ",(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"browser's console output"}),", if you've not set any headers, you will likely see a CORS error here, which would be the source of the issue."]}),"\n",(0,r.jsxs)(n.p,{children:["You need to allow Dashy to make requests to Keycloak, and Keycloak to redirect to Dashy. The way you do this depends on how you're hosting these applications / which proxy you are using, and examples can be found in the ",(0,r.jsx)(n.a,{href:"/docs/management#setting-headers",children:"Management Docs"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"For example, add the access control header to Keycloak, like:"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.code,{children:"Access-Control-Allow-Origin [URL-of Dashy]"})}),"\n",(0,r.jsxs)(n.p,{children:["Note that for requests that transport sensitive info like credentials, setting the accept header to a wildcard (",(0,r.jsx)(n.code,{children:"*"}),") is not allowed - see ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#requests_with_credentials",children:"MDN Docs"}),", so you will need to specify the actual URL."]}),"\n",(0,r.jsxs)(n.p,{children:["You should also ensure that Keycloak is correctly configured, with a user, realm and application, and be sure that you have set a valid redirect URL in Keycloak (",(0,r.jsx)(n.a,{href:"https://user-images.githubusercontent.com/1862727/148599768-db4ee4f8-72c5-402d-8f00-051d999e6267.png",children:"screenshot"}),")."]}),"\n",(0,r.jsxs)(n.p,{children:["For more details on how to set headers, see the ",(0,r.jsx)(n.a,{href:"/docs/management#setting-headers",children:"Example Headers"})," in the management docs, or reference the documentation for your proxy."]}),"\n",(0,r.jsxs)(n.p,{children:["If you're running in Kubernetes, you will need to enable CORS ingress rules, see ",(0,r.jsx)(n.a,{href:"https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#enable-cors",children:"docs"}),", e.g:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:'nginx.ingress.kubernetes.io/cors-allow-origin: "https://dashy.example.com"\nnginx.ingress.kubernetes.io/enable-cors: "true"\n'})}),"\n",(0,r.jsx)(n.p,{children:"See also: #479, #409, #507, #491, #341, #520"}),"\n",(0,r.jsx)(n.h3,{id:"oidc-or-keycloak-failure-on-numeric-client-ids",children:"OIDC or Keycloak failure on numeric client IDs"}),"\n",(0,r.jsxs)(n.p,{children:["If your IdP rejects the login with an ",(0,r.jsx)(n.em,{children:'"invalid client"'})," / ",(0,r.jsx)(n.em,{children:'"client not found"'})," error, and your ",(0,r.jsx)(n.code,{children:"clientId"})," is a long numeric value, the cause is almost certainly YAML number parsing."]}),"\n",(0,r.jsxs)(n.p,{children:["YAML parses unquoted numeric tokens as Numbers, and JavaScript can't represent integers larger than 2^53 (~16 digits) without losing precision. So an unquoted numeric ",(0,r.jsx)(n.code,{children:"clientId"})," will be silently truncated (e.g. ",(0,r.jsx)(n.code,{children:"918756876419824312"})," \u2192 ",(0,r.jsx)(n.code,{children:"918756876419824300"}),"), or - for very large values - converted to scientific notation (e.g. ",(0,r.jsx)(n.code,{children:"9.187568764198242e+37"}),"), and the IdP will reject it."]}),"\n",(0,r.jsxs)(n.p,{children:["The fix is to wrap the ",(0,r.jsx)(n.code,{children:"clientId"})," in quotes in your ",(0,r.jsx)(n.code,{children:"conf.yml"})," so it gets parsed as a string:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'appConfig:\n auth:\n enableOidc: true\n oidc:\n clientId: "918756876419824312"\n endpoint: https://idp.example.com/\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The same applies to ",(0,r.jsx)(n.code,{children:"auth.keycloak.clientId"}),". Dashy will print a warning in the ",(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"browser console"})," when it detects a numeric ",(0,r.jsx)(n.code,{children:"clientId"}),", to help diagnose this."]}),"\n",(0,r.jsx)(n.p,{children:"See also: #1941"}),"\n",(0,r.jsx)(n.h3,{id:"redirect-loop-after-login",children:"Redirect loop after login"}),"\n",(0,r.jsxs)(n.p,{children:["Your ",(0,r.jsx)(n.code,{children:"endpoint"})," probably includes ",(0,r.jsx)(n.code,{children:".well-known/openid-configuration"}),". Drop everything from ",(0,r.jsx)(n.code,{children:".well-known"})," onwards"]}),"\n",(0,r.jsx)(n.h3,{id:"invalid_redirect_uri",children:"invalid_redirect_uri"}),"\n",(0,r.jsxs)(n.p,{children:["The redirect URI Authentik has registered for the provider doesn't exactly match the URL Dashy is being served from. Register both the bare URL and the trailing-slash version, and make sure the scheme matches (",(0,r.jsx)(n.code,{children:"http"})," vs ",(0,r.jsx)(n.code,{children:"https"}),")."]}),"\n",(0,r.jsx)(n.h3,{id:"login-works-in-the-browser-but-the-dashboard-refuses-to-save-anything-403",children:"Login works in the browser but the dashboard refuses to save anything (403)"}),"\n",(0,r.jsxs)(n.p,{children:["Dashy's server is rejecting the id_token. Check Dashy's container logs for ",(0,r.jsx)(n.code,{children:"[auth-oidc] token verification failed"}),". Common causes:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Issuer mismatch"}),". Authentik is behind a reverse proxy that isn't sending ",(0,r.jsx)(n.code,{children:"X-Forwarded-Proto: https"}),", so its discovery document advertises ",(0,r.jsx)(n.code,{children:"http://"})," while you configured ",(0,r.jsx)(n.code,{children:"https://"})," in Dashy. Fix the proxy or set ",(0,r.jsx)(n.code,{children:"AUTHENTIK_HOST"}),"/",(0,r.jsx)(n.code,{children:"AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS"})," on the Authentik containers"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Audience mismatch"}),". The ",(0,r.jsx)(n.code,{children:"aud"})," claim in the id_token is not ",(0,r.jsx)(n.code,{children:"dashy"}),". Confirm the provider's Client ID is exactly ",(0,r.jsx)(n.code,{children:"dashy"})," (no leading or trailing whitespace)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Dashy server can't reach Authentik"}),". The Dashy container fails to fetch the discovery document. Exec into the container and try ",(0,r.jsx)(n.code,{children:"wget -qO- https://auth.example.com/application/o/dashy/.well-known/openid-configuration"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Clock skew"}),". The middleware allows 30 seconds of drift. If a container's clock is further off than that, ",(0,r.jsx)(n.code,{children:"exp"}),"/",(0,r.jsx)(n.code,{children:"iat"})," checks fail"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"logged-in-but-no-admin-controls",children:"Logged in but no admin controls"}),"\n",(0,r.jsxs)(n.p,{children:["The id_token doesn't include the groups claim. Open browser devtools after logging in, find the call to ",(0,r.jsx)(n.code,{children:"/application/o/dashy/userinfo/"}),", and check the response. You should see a ",(0,r.jsx)(n.code,{children:"groups"})," array containing ",(0,r.jsx)(n.code,{children:"dashy-admins"}),". If not:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["The ",(0,r.jsx)(n.code,{children:"groups"})," scope mapping doesn't exist, or is not attached to the provider's property mappings"]}),"\n",(0,r.jsxs)(n.li,{children:["The user is not in the ",(0,r.jsx)(n.code,{children:"dashy-admins"})," group"]}),"\n",(0,r.jsxs)(n.li,{children:["The conf.yml is missing ",(0,r.jsx)(n.code,{children:"groups"})," from ",(0,r.jsx)(n.code,{children:"scope:"})," and Authentik is therefore not sending it"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"login-works-but-dashy-errors-on-the-callback-with-oidc-signincallback-returned-no-user",children:'Login works but Dashy errors on the callback with "OIDC signinCallback returned no user"'}),"\n",(0,r.jsx)(n.p,{children:'The id_token came back without a username claim. Confirm the provider has "profile" and "email" in its scopes and the "Include claims in id_token" is on'}),"\n",(0,r.jsx)(n.h3,{id:"sign-out-leaves-you-stuck-on-authentik",children:"Sign-out leaves you stuck on Authentik"}),"\n",(0,r.jsx)(n.p,{children:"Dashy redirects to Authentik's end-session endpoint on logout. If Authentik's invalidation flow prompts for confirmation (the default), that's expected - click through it. To skip the prompt entirely, change the provider's invalidation flow to one without a consent stage."}),"\n",(0,r.jsx)(n.h3,{id:"untrusted-certificate-from-authentik",children:"Untrusted certificate from Authentik"}),"\n",(0,r.jsx)(n.p,{children:"Self-signed certs make Dashy's server-side fetch of the discovery document fail. Use a real cert (Let's Encrypt, or your homelab CA installed into the Dashy image) for the Authentik hostname."}),"\n",(0,r.jsx)(n.h3,{id:"numeric-client_id-getting-truncated",children:"Numeric client_id getting truncated"}),"\n",(0,r.jsx)(n.p,{children:"Don't use numeric-only client IDs. If you must, wrap the value in quotes in conf.yml so YAML treats it as a string"}),"\n",(0,r.jsx)(n.h3,{id:"header-auth-unauthorized---not-from-trusted-proxy",children:'Header auth: "Unauthorized - not from trusted proxy"'}),"\n",(0,r.jsxs)(n.p,{children:["The IP your reverse proxy presents as isn't in ",(0,r.jsx)(n.code,{children:"auth.headerAuth.proxyWhitelist"}),". For Docker it's usually the bridge IP, not your LAN IP. Find it with ",(0,r.jsx)(n.code,{children:"docker compose exec dashy getent hosts "})," and paste that into ",(0,r.jsx)(n.code,{children:"proxyWhitelist"}),". Restart Dashy after the change."]}),"\n",(0,r.jsx)(n.h3,{id:"header-auth-unauthorized---missing-user-header",children:'Header auth: "Unauthorized - missing user header"'}),"\n",(0,r.jsxs)(n.p,{children:["The source IP check passed, but the configured ",(0,r.jsx)(n.code,{children:"userHeader"})," isn't on the request. Either the proxy isn't sending it (check the Cloudflare Access policy / Authelia forward-auth / Tailscale Serve config is actually applied), or the header name in ",(0,r.jsx)(n.code,{children:"conf.yml"})," doesn't match what the proxy sends. Header matching is case-insensitive, but spelling and prefix matter."]}),"\n",(0,r.jsx)(n.h3,{id:"invalid-user-object-warning-on-startup-with-an-all-digit-hash-or-placeholder",children:'"Invalid user object" warning on startup with an all-digit hash or placeholder'}),"\n",(0,r.jsxs)(n.p,{children:["YAML parsed your ",(0,r.jsx)(n.code,{children:"hash:"})," value as a number rather than a string, because every character was a digit. Common when using placeholder all-zero hashes under header auth. Quote it: ",(0,r.jsx)(n.code,{children:'hash: "0000..."'}),". Same root cause as the numeric ",(0,r.jsx)(n.code,{children:"clientId"})," issue above."]}),"\n",(0,r.jsx)(n.h3,{id:"oidc-login-looks-like-a-cors-error-in-devtools-but-the-idp-is-configured-for-cors-correctly",children:"OIDC login looks like a CORS error in devtools, but the IdP is configured for CORS correctly"}),"\n",(0,r.jsxs)(n.p,{children:["If your identity provider uses a self-signed or untrusted certificate, the browser will silently abort ",(0,r.jsx)(n.code,{children:"oidc-client-ts"}),"'s token-exchange fetch and surface it as a CORS-shaped error. Open the IdP URL directly in a new tab, accept the certificate warning, then retry the Dashy login. The real fix is to use a trusted cert (Let's Encrypt, or a homelab CA installed into the browser's trust store)."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"docker--image-issues",children:"Docker & image issues"}),"\n",(0,r.jsx)(n.h3,{id:"app-not-starting-after-update-to-204",children:"App Not Starting After Update to 2.0.4"}),"\n",(0,r.jsxs)(n.p,{children:["Version 2.0.4 introduced changes to how the config is read, and the app is build. If you were previously mounting ",(0,r.jsx)(n.code,{children:"/public"})," as a volume, then this will over-write the build app, preventing it from starting. The solution is to just pass in the file(s) / sub-directories that you need. For example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"volumes:\n- /srv/dashy/conf.yml:/app/user-data/conf.yml\n- /srv/dashy/item-icons:/app/public/item-icons\n"})}),"\n",(0,r.jsx)(n.h3,{id:"mount-type-mismatch",children:"Mount Type Mismatch"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Error response from daemon: ... mount through procfd: not a directory:\nAre you trying to mount a directory onto a file (or vice-versa)?\n"})}),"\n",(0,r.jsx)(n.p,{children:"This means the host side and container side of your volume don't agree on whether the target is a file or a directory."}),"\n",(0,r.jsxs)(n.p,{children:["Recommended pattern: mount a host directory onto ",(0,r.jsx)(n.code,{children:"/app/user-data"}),". The directory must exist on the host and contain at least a ",(0,r.jsx)(n.code,{children:"conf.yml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"mkdir -p ~/dashy-data\ncp /path/to/your/conf.yml ~/dashy-data/conf.yml\ndocker run -d -p 8080:8080 -v ~/dashy-data:/app/user-data lissy93/dashy:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["If you'd rather mount a single file (",(0,r.jsx)(n.code,{children:"-v ~/conf.yml:/app/user-data/conf.yml"}),"), the host path must be a file that already exists, otherwise Docker creates a directory in its place and you'll see this error."]}),"\n",(0,r.jsxs)(n.h3,{id:"dockerhub-toomanyrequests",children:["DockerHub ",(0,r.jsx)(n.code,{children:"toomanyrequests"})]}),"\n",(0,r.jsx)(n.p,{children:"This situation relates to error messages similar to one of the following, returned when pulling, updating or running the Docker container from Docker Hub."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Continuing execution. Pulling image lissy93/dashy:release-1.6.0\nerror pulling image configuration: toomanyrequests\n"})}),"\n",(0,r.jsx)(n.p,{children:"or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit\n"})}),"\n",(0,r.jsxs)(n.p,{children:["When DockerHub returns one of these errors, or a ",(0,r.jsx)(n.code,{children:"429"})," status, that means you've hit your rate limit. This was ",(0,r.jsx)(n.a,{href:"https://www.docker.com/blog/scaling-docker-to-serve-millions-more-developers-network-egress/",children:"introduced"})," last year, and prevents unauthenticated or free users from running docker pull more than 100 times per 6 hours.\nYou can ",(0,r.jsx)(n.a,{href:"https://www.docker.com/blog/checking-your-current-docker-pull-rate-limits-and-status/",children:"check your rate limit status"})," by looking for the ",(0,r.jsx)(n.code,{children:"ratelimit-remaining"})," header in any DockerHub responses."]}),"\n",(0,r.jsx)(n.h4,{id:"solution-1---use-an-alternate-container-registry",children:"Solution 1 - Use an alternate container registry"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Dashy is also available through GHCR, which at present does not have any hard limits. Just use ",(0,r.jsx)(n.code,{children:"docker pull ghcr.io/lissy93/dashy:latest"})," to fetch the image"]}),"\n",(0,r.jsxs)(n.li,{children:["You can also build the image from source, by cloning the repo, and running ",(0,r.jsx)(n.code,{children:"docker build -t dashy ."})," or use the pre-made docker compose"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"solution-2---increase-your-rate-limits",children:"Solution 2 - Increase your rate limits"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Logging in to DockerHub will increase your rate limit from 100 requests to 200 requests per 6 hour period"}),"\n",(0,r.jsx)(n.li,{children:"Upgrading to a Pro for $5/month will increase your image requests to 5,000 per day, and any plans above have no rate limits"}),"\n",(0,r.jsx)(n.li,{children:"Since rate limits are counted based on your IP address, proxying your requests, or using a VPN may work"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"old-image-tags-fail-to-pull",children:"Old image tags fail to pull"}),"\n",(0,r.jsxs)(n.p,{children:["If ",(0,r.jsx)(n.code,{children:"docker pull"})," returns ",(0,r.jsx)(n.code,{children:"manifest unknown"})," or ",(0,r.jsx)(n.code,{children:"manifest for lissy93/dashy:arm32v7 not found"}),", the cause is a stale architecture-specific tag in your compose file or run command. Tags like ",(0,r.jsx)(n.code,{children:":arm32v7"}),", ",(0,r.jsx)(n.code,{children:":arm64v8"}),", and ",(0,r.jsx)(n.code,{children:":multi-arch"})," are no longer published."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:":latest"})," tag is now multi-arch and works on amd64, arm64, and arm/v7 (Raspberry Pi 2 and up) without you having to pick a variant. Just use:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"image: lissy93/dashy:latest\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Docker fetches the right architecture for your host automatically. To pin a version, use a semver tag, e.g. ",(0,r.jsx)(n.code,{children:"lissy93/dashy:3.2.14"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"healthcheck-failing-in-docker",children:"Healthcheck Failing in Docker"}),"\n",(0,r.jsxs)(n.p,{children:["If ",(0,r.jsx)(n.code,{children:"docker ps"})," shows the Dashy container as ",(0,r.jsx)(n.code,{children:"unhealthy"}),", the periodic healthcheck (",(0,r.jsx)(n.code,{children:"node services/healthcheck.js"}),", run every 5 minutes by default) couldn't reach the local server."]}),"\n",(0,r.jsx)(n.h4,{id:"ssl-enabled-dashy",children:"SSL-enabled Dashy"}),"\n",(0,r.jsxs)(n.p,{children:["The healthcheck reads the same cert paths as the main server (",(0,r.jsx)(n.code,{children:"/etc/ssl/certs/dashy-pub.pem"})," and ",(0,r.jsx)(n.code,{children:"/etc/ssl/certs/dashy-priv.key"}),") to detect whether to probe HTTPS or HTTP. If you've mounted certs at non-default paths via ",(0,r.jsx)(n.code,{children:"SSL_PUB_KEY_PATH"})," / ",(0,r.jsx)(n.code,{children:"SSL_PRIV_KEY_PATH"}),", the healthcheck will pick those up automatically as long as those env vars are set in the container environment (not just at run time)."]}),"\n",(0,r.jsx)(n.h4,{id:"custom-port",children:"Custom port"}),"\n",(0,r.jsxs)(n.p,{children:["If you've set ",(0,r.jsx)(n.code,{children:"PORT"})," to override the default 8080, the healthcheck honors the same env var, so it should work without changes. Make sure ",(0,r.jsx)(n.code,{children:"PORT"})," is set in the container environment, not just in the host-side Docker port mapping."]}),"\n",(0,r.jsx)(n.h4,{id:"container-is-unhealthy-past-the-grace-period",children:"Container is unhealthy past the grace period"}),"\n",(0,r.jsxs)(n.p,{children:["The healthcheck has a 20s ",(0,r.jsx)(n.code,{children:"start-period"})," after which failures start counting. The image is prebuilt, so startup is just ",(0,r.jsx)(n.code,{children:"node server.js"})," binding to a port - fast even on a Pi. If the container is still ",(0,r.jsx)(n.code,{children:"unhealthy"})," past the grace period, the server has likely crashed. Check ",(0,r.jsx)(n.code,{children:"docker logs "})," for the real error (usually a malformed ",(0,r.jsx)(n.code,{children:"conf.yml"})," or a missing ",(0,r.jsx)(n.code,{children:"user-data"})," mount)."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/1410",children:"#1410"})]}),"\n",(0,r.jsx)(n.h3,{id:"docker-login-fails-on-ubuntu",children:"Docker Login Fails on Ubuntu"}),"\n",(0,r.jsxs)(n.p,{children:["Run ",(0,r.jsx)(n.code,{children:"sudo apt install gnupg2 pass && gpg2 -k"})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"styles-and-assets-not-updating",children:"Styles and Assets not Updating"}),"\n",(0,r.jsxs)(n.p,{children:["If you find that your styles and other visual assets work when visiting ",(0,r.jsx)(n.code,{children:"ip:port"})," by not ",(0,r.jsx)(n.code,{children:"dashy.domain.com"}),", then this is usually caused by caching. In your browser, do a hard-refresh (",(0,r.jsx)(n.kbd,{children:"Ctrl"})," + ",(0,r.jsx)(n.kbd,{children:"F5"}),"). If you use Cloudflare, then you can clear the cache through the management console, or set the cache level to Bypass for certain files, under the Rules tab."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"config-validation-errors",children:"Config Validation Errors"}),"\n",(0,r.jsxs)(n.p,{children:["The configuration file is validated against ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json",children:"Dashy's Schema"})," using AJV."]}),"\n",(0,r.jsxs)(n.p,{children:["First, check that your syntax is valid, using ",(0,r.jsx)(n.a,{href:"https://codebeautify.org/yaml-validator/",children:"YAML Validator"})," or ",(0,r.jsx)(n.a,{href:"https://codebeautify.org/jsonvalidator",children:"JSON Validator"}),". If the issue persists, then take a look at the ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json",children:"schema"}),", and verify that the field you are trying to add/ modify matches the required format. You can also use ",(0,r.jsx)(n.a,{href:"https://www.jsonschemavalidator.net/s/JFUj7X9J",children:"this tool"})," to validate your JSON config against the schema, or run ",(0,r.jsx)(n.code,{children:"yarn validate-config"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"If you're trying to use a recently released feature, and are getting a warning, this is likely because you've not yet updated the the current latest version of Dashy."}),"\n",(0,r.jsx)(n.p,{children:"If the issue still persists, you should raise an issue."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"invalid-host-header-while-running-through-ngrok",children:"Invalid Host Header while running through ngrok"}),"\n",(0,r.jsxs)(n.p,{children:["Just add the ",(0,r.jsx)(n.a,{href:"https://ngrok.com/docs#http-host-header",children:"-host-header"})," flag, e.g. ",(0,r.jsx)(n.code,{children:'ngrok http 8080 -host-header="localhost:8080"'})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"warnings-in-the-console-during-deploy",children:"Warnings in the Console during deploy"}),"\n",(0,r.jsx)(n.p,{children:"Please acknowledge the difference between errors and warnings before raising an issue about messages in the console. It's not unusual to see warnings about a new version of a certain package being available, an asset bundle bing oversized or a service worker not yet having a cache. These shouldn't have any impact on the running application, so please don't raise issues about these unless it directly relates to a bug or issue you're experiencing. Errors on the other hand should not appear in the console, and they are worth looking into further."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"status-checks-failing",children:"Status Checks Failing"}),"\n",(0,r.jsx)(n.p,{children:"If you're using status checks, and despite a given service being online, the check is displaying an error, there are a couple of things you can look at:"}),"\n",(0,r.jsxs)(n.p,{children:["If your service requires requests to include any authorization in the headers, then use the ",(0,r.jsx)(n.code,{children:"statusCheckHeaders"})," property, as described in the ",(0,r.jsx)(n.a,{href:"/docs/status-indicators#setting-custom-headers",children:"docs"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["If you are still having issues, it may be because your target application is blocking requests from Dashy's IP. This is a ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"CORS error"}),", and can be fixed by setting the headers on your target app, to include:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: https://location-of-dashy/\nVary: Origin\n"})}),"\n",(0,r.jsxs)(n.p,{children:["If the URL you are checking has an unsigned certificate, or is not using HTTPS, then you may need to disable the rejection of insecure requests. This can be done by setting ",(0,r.jsx)(n.code,{children:"statusCheckAllowInsecure"})," to true for a given item."]}),"\n",(0,r.jsxs)(n.p,{children:["If your service is online, but responds with a status code that is not in the 2xx range, then you can use ",(0,r.jsx)(n.code,{children:"statusCheckAcceptCodes"})," to set an accepted status code."]}),"\n",(0,r.jsxs)(n.p,{children:["If you get an error, like ",(0,r.jsx)(n.code,{children:"Service Unavailable: Server resulted in a fatal error"}),", even when it's definitely online, this is most likely caused by missing the protocol. Don't forget to include ",(0,r.jsx)(n.code,{children:"https://"})," (or whatever protocol) before the URL, and ensure that if needed, you've specified the port."]}),"\n",(0,r.jsxs)(n.p,{children:["Running Dashy in HOST network mode, instead of BRIDGE will allow status check access to other services in HOST mode. For more info, see ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/discussions/445",children:"#445"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"If you have firewall rules configured, then ensure that they don't prevent Dashy from making requests to the other services you are trying to access."}),"\n",(0,r.jsxs)(n.p,{children:["Currently, the status check needs a page to be rendered, so if this URL in your browser does not return anything, then status checks will not work. This may be modified in the future, but in the meantime, a fix would be to make your own status service, which just checks if your app responds with whatever code you'd like, and then return a 200 plus renders an arbitrary message. Then just point ",(0,r.jsx)(n.code,{children:"statusCheckUrl"})," to your custom page."]}),"\n",(0,r.jsxs)(n.p,{children:["For further troubleshooting, use an application like ",(0,r.jsx)(n.a,{href:"https://postman.com",children:"Postman"})," to diagnose the issue. Set the parameter to ",(0,r.jsx)(n.code,{children:"GET"}),", and then make a call to: ",(0,r.jsx)(n.code,{children:"https://[url-of-dashy]/status-check/?&url=[service-url]"}),". Where the service URL must have first been encoded (e.g. with ",(0,r.jsx)(n.code,{children:"encodeURIComponent()"})," or ",(0,r.jsx)(n.a,{href:"https://www.urlencoder.io/",children:"urlencoder.io"}),")"]}),"\n",(0,r.jsx)(n.p,{children:"If you're serving Dashy though a CDN, instead of using the Node server or Docker image, then the Node endpoint that makes requests will not be available to you, and all requests will fail. A workaround for this may be implemented in the future, but in the meantime, your only option is to use the Docker or Node deployment method."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"widgets",children:"Widgets"}),"\n",(0,r.jsx)(n.h3,{id:"widget-errors",children:"Widget Errors"}),"\n",(0,r.jsx)(n.h4,{id:"find-error-message",children:"Find Error Message"}),"\n",(0,r.jsxs)(n.p,{children:["If an error occurs when fetching or rendering results, you will see a short message in the UI. If that message doesn't adequately explain the problem, then you can ",(0,r.jsx)(n.a,{href:"/docs/troubleshooting#how-to-open-browser-console",children:"open the browser console"})," to see more details."]}),"\n",(0,r.jsx)(n.h4,{id:"check-config",children:"Check Config"}),"\n",(0,r.jsx)(n.p,{children:"Before proceeding, ensure that if the widget requires auth your API is correct, and for custom widgets, double check that the URL and protocol is correct."}),"\n",(0,r.jsx)(n.h4,{id:"timeout-error",children:"Timeout Error"}),"\n",(0,r.jsxs)(n.p,{children:["If the error message in the console includes: ",(0,r.jsx)(n.code,{children:"Error: timeout of 500ms exceeded"}),", then your Glances endpoint is slower to respond than expected. You can fix this by ",(0,r.jsx)(n.a,{href:"/docs/widgets#setting-timeout",children:"setting timeout"})," to a larger value. This is done on each widget, with the ",(0,r.jsx)(n.code,{children:"timeout"})," attribute, and is specified in ms. E.g. ",(0,r.jsx)(n.code,{children:"timeout: 5000"})," would only fail if no response is returned within 5 seconds."]}),"\n",(0,r.jsx)(n.h4,{id:"cors-error",children:"CORS error"}),"\n",(0,r.jsxs)(n.p,{children:["If the console message mentions to corss-origin blocking, then this is a CORS error, see: ",(0,r.jsx)(n.a,{href:"#widget-cors-errors",children:"Fixing Widget CORS Errors"})]}),"\n",(0,r.jsx)(n.h4,{id:"more-info",children:"More Info"}),"\n",(0,r.jsx)(n.p,{children:"If you're able to, you can find more information about why the request may be failing in the Dev Tools under the Network tab, and you can ensure your endpoint is correct and working using a tool like Postman."}),"\n",(0,r.jsx)(n.h3,{id:"widget-cors-errors",children:"Widget CORS Errors"}),"\n",(0,r.jsxs)(n.p,{children:["The most common widget issue is a CORS error. This is a browser security mechanism which prevents the client-side app (Dashy) from from accessing resources on a remote origin, without that server's explicit permission (e.g. with headers like Access-Control-Allow-Origin). See the MDN Docs for more info: ",(0,r.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS",children:"Cross-Origin Resource Sharing"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"There are several ways to fix a CORS error:"}),"\n",(0,r.jsx)(n.h4,{id:"option-1---ensure-correct-protocol",children:"Option 1 - Ensure Correct Protocol"}),"\n",(0,r.jsx)(n.p,{children:"You will get a CORS error if you try and access a http service from a https source. So ensure that the URL you are requesting has the right protocol, and is correctly formatted."}),"\n",(0,r.jsx)(n.h4,{id:"option-2---set-headers",children:"Option 2 - Set Headers"}),"\n",(0,r.jsxs)(n.p,{children:["If you have control over the destination (e.g. for a self-hosted service), then you can simply apply the correct headers.\nAdd the ",(0,r.jsx)(n.code,{children:"Access-Control-Allow-Origin"})," header, with the value of either ",(0,r.jsx)(n.code,{children:"*"})," to allow requests from anywhere, or more securely, the host of where Dashy is served from. For example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: https://url-of-dashy.local\n"})}),"\n",(0,r.jsx)(n.p,{children:"or"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"Access-Control-Allow-Origin: *\n"})}),"\n",(0,r.jsxs)(n.p,{children:["For more info on how to set headers, see: ",(0,r.jsx)(n.a,{href:"/docs/management#setting-headers",children:"Setting Headers"})," in the management docs"]}),"\n",(0,r.jsx)(n.h4,{id:"option-3---proxying-request",children:"Option 3 - Proxying Request"}),"\n",(0,r.jsxs)(n.p,{children:["You can route requests through Dashy's built-in CORS proxy. Instructions and more details can be found ",(0,r.jsx)(n.a,{href:"/docs/widgets#proxying-requests",children:"here"}),". If you don't have control over the target origin, and you are running Dashy either through Docker, with the Node server or on Netlify, then this solution will work for you."]}),"\n",(0,r.jsxs)(n.p,{children:["Just add the ",(0,r.jsx)(n.code,{children:"useProxy: true"})," option to the failing widget."]}),"\n",(0,r.jsx)(n.h4,{id:"option-4---use-a-plugin",children:"Option 4 - Use a plugin"}),"\n",(0,r.jsxs)(n.p,{children:["For testing purposes, you can use an addon, which will disable the CORS checks. You can get the Allow-CORS extension for ",(0,r.jsx)(n.a,{href:"https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?hl=en-US",children:"Chrome"})," or ",(0,r.jsx)(n.a,{href:"https://addons.mozilla.org/en-US/firefox/addon/access-control-allow-origin/",children:"Firefox"}),", more details ",(0,r.jsx)(n.a,{href:"https://mybrowseraddon.com/access-control-allow-origin.html",children:"here"})]}),"\n",(0,r.jsxs)(n.h3,{id:"cors-proxy-connect-econnrefused--or-getaddrinfo-enotfound-",children:["CORS Proxy ",(0,r.jsx)(n.code,{children:"connect ECONNREFUSED ..."})," or ",(0,r.jsx)(n.code,{children:"getaddrinfo ENOTFOUND ..."})]}),"\n",(0,r.jsxs)(n.p,{children:["The target host is unreachable from the Dashy container. If the target is on the same host as Dashy, ",(0,r.jsxs)(n.strong,{children:["don't use ",(0,r.jsx)(n.code,{children:"localhost"})]})," - inside a Docker container that resolves to the container itself, not the host. Use the host's LAN IP, the Docker bridge gateway, or ",(0,r.jsx)(n.code,{children:"host.docker.internal"})," (on Docker Desktop). If the target is on a different Docker network, attach Dashy to that network too."]}),"\n",(0,r.jsxs)(n.h3,{id:"cors-proxy-target-url-host--is-blocked--must-use-http-or-https",children:["CORS Proxy ",(0,r.jsx)(n.code,{children:"Target-URL host '...' is blocked"})," / ",(0,r.jsx)(n.code,{children:"must use http:// or https://"})]}),"\n",(0,r.jsx)(n.p,{children:"To prevent the CORS proxy from being abused as a Server-Side Request Forgery vector, Dashy refuses to proxy a small number of host/scheme combinations by default:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Cloud instance metadata services"})," - ",(0,r.jsx)(n.code,{children:"169.254.169.254"}),", ",(0,r.jsx)(n.code,{children:"metadata.google.internal"}),", and the matching IPv6 forms. These are reserved magic addresses on AWS, Azure, GCP, DigitalOcean, Hetzner, Oracle Cloud and most other providers. A widget that successfully fetches them on a cloud-hosted Dashy can leak the host's IAM credentials, so they're blocked unconditionally."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Non-HTTP(S) schemes"})," - ",(0,r.jsx)(n.code,{children:"file://"}),", ",(0,r.jsx)(n.code,{children:"ftp://"}),", ",(0,r.jsx)(n.code,{children:"gopher://"}),", ",(0,r.jsx)(n.code,{children:"javascript:"}),", ",(0,r.jsx)(n.code,{children:"data:"}),", and similar. The proxy is meant for HTTP APIs only."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"If you're running Dashy in a fully isolated/private environment and you've deliberately decided you want to allow these (for example, you genuinely need to query your cloud provider's metadata API from a widget), you can opt out of all proxy restrictions by setting the environment variable:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"DANGEROUSLY_DISABLE_PROXY_RESTRICTIONS=true\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The variable is named so loudly because flipping it on a Dashy instance that's exposed to anything other than fully trusted users re-opens the SSRF surface - anyone who can hit ",(0,r.jsx)(n.code,{children:"/cors-proxy"})," can then use Dashy as a relay to reach internal services. ",(0,r.jsx)(n.strong,{children:"Don't set it on cloud-hosted or internet-exposed deployments."})]}),"\n",(0,r.jsx)(n.p,{children:"Note that this is an all-or-nothing escape hatch, not a per-host allowlist. If you only need to reach one specific host that's currently blocked, please open a feature request describing the use case."}),"\n",(0,r.jsx)(n.h3,{id:"widget-shows-error-incorrectly",children:"Widget Shows Error Incorrectly"}),"\n",(0,r.jsx)(n.p,{children:"When there's an error fetching or displaying a widgets data, then it will be highlighted in yellow, and a message displayed on the UI."}),"\n",(0,r.jsxs)(n.p,{children:["In some instances, this is a false positive, and the widget is actually functioning correctly.\nIf this is the case, you can disable the UI error message of a given widget by setting: ",(0,r.jsx)(n.code,{children:"ignoreErrors: true"})]}),"\n",(0,r.jsx)(n.h3,{id:"weather-forecast-widget-401",children:"Weather Forecast Widget 401"}),"\n",(0,r.jsx)(n.p,{children:"A 401 error means your API key is invalid, it is not an issue with Dashy."}),"\n",(0,r.jsxs)(n.p,{children:["Usually this happens due to an error in your config. If you're unsure, copy and paste the ",(0,r.jsx)(n.a,{href:"/docs/widgets#weather",children:"example"})," config, replacing the API key with your own."]}),"\n",(0,r.jsxs)(n.p,{children:["Check that ",(0,r.jsx)(n.code,{children:"apiKey"})," is correctly specified, and nested within ",(0,r.jsx)(n.code,{children:"options"}),". Ensure your input city is valid."]}),"\n",(0,r.jsxs)(n.p,{children:["To test your API key, try making a request to ",(0,r.jsx)(n.code,{children:"https://api.openweathermap.org/data/2.5/weather?q=London&appid=[your-api-key]"})]}),"\n",(0,r.jsxs)(n.p,{children:["If ",(0,r.jsx)(n.a,{href:"/docs/widgets#weather-forecast",children:"Weather widget"})," is working fine, but you are getting a ",(0,r.jsx)(n.code,{children:"401"})," for the ",(0,r.jsx)(n.a,{href:"/docs/widgets#weather-forecast",children:"Weather Forecast widget"}),", then this is also an OWM API key issue.\nSince the forecasting API requires an upgraded plan. ULPT: You can get a free, premium API key by filling in ",(0,r.jsx)(n.a,{href:"https://home.openweathermap.org/students",children:"this form"}),". It's a student plan, but there's no verification to check that you are still a student."]}),"\n",(0,r.jsx)(n.p,{children:"A future update will be pushed out, to use a free weather forecasting API."}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/803",children:"#803"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/789",children:"#789"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/577",children:"#577"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/621",children:"#621"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/578",children:"#578"}),", ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/discussions/806",children:"#806"})]}),"\n",(0,r.jsx)(n.h3,{id:"widget-displaying-inaccurate-data",children:"Widget Displaying Inaccurate Data"}),"\n",(0,r.jsx)(n.p,{children:"If any widget is not displaying the data you expect, first confirm that your config is correct, then try manually calling the API endpoint."}),"\n",(0,r.jsxs)(n.p,{children:["If the raw API output is correct, yet the widget is rendering incorrect results, then it is likely a bug, and a ticket should be raised. You can start to debug the issue, by looking at the widget's code (",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/src/components/Widgets",children:"here"}),"), and the browser console + networking tab."]}),"\n",(0,r.jsxs)(n.p,{children:["If the API itself is returning incorrect, incomplete or inaccurate data then an issue needs to be raised ",(0,r.jsx)(n.strong,{children:"with the API provider"})," (not Dashy!). You can find the API provider included within the widget docs, or for a full list see the ",(0,r.jsx)(n.a,{href:"/docs/privacy#widgets",children:"Privacy Docs"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["See also: ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/807",children:"#807"})," (re, domain monitor)"]}),"\n",(0,r.jsxs)(n.h3,{id:"public-ip-widget-not-working-for-ipinfo-or-ipquery-providers",children:["Public IP Widget not working for ",(0,r.jsx)(n.code,{children:"ipinfo"})," or ",(0,r.jsx)(n.code,{children:"ipquery"})," providers"]}),"\n",(0,r.jsxs)(n.p,{children:["If you've set ",(0,r.jsx)(n.code,{children:"options.provider"})," to either ",(0,r.jsx)(n.code,{children:"ipinfo"})," and ",(0,r.jsx)(n.code,{children:"ipquery"})," and the requests are failing, it's likley that they're being blocked. Check your adblocker (uBlock, PrivacyBadger, etc), DNS block lists (PiHole, AdGuard, etc). Or, try proxying the request (with ",(0,r.jsx)(n.code,{children:"useProxy: true"}),") or just try a different provider."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"font-awesome-icons-not-displaying",children:"Font Awesome Icons not Displaying"}),"\n",(0,r.jsxs)(n.p,{children:["Usually, Font Awesome will be automatically enabled if one or more of your icons are using Font-Awesome. If this is not happening, then you can always manually enable (or disable) Font Awesome by setting: ",(0,r.jsx)(n.a,{href:"/docs/configuring#appconfig-optional",children:(0,r.jsx)(n.code,{children:"appConfig"})}),".",(0,r.jsx)(n.code,{children:"enableFontAwesome"})," to ",(0,r.jsx)(n.code,{children:"true"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["If you are trying to use a premium icon, then you must have a ",(0,r.jsx)(n.a,{href:"https://fontawesome.com/plans",children:"Pro License"}),". You'll then need to specify your Pro plan API key under ",(0,r.jsx)(n.code,{children:"appConfig.fontAwesomeKey"}),". You can find this key, by logging into your FA account, navigate to Account \u2192 ",(0,r.jsx)(n.a,{href:"https://fontawesome.com/kits",children:"Kits"})," \u2192 New Kit \u2192 Copy Kit Code. The code is a 10-digit alpha-numeric code, and is also visible within the new kit's URL, for example: ",(0,r.jsx)(n.code,{children:"81e48ce079"}),"."]}),"\n",(0,r.jsx)(n.p,{align:"center",children:(0,r.jsx)(n.img,{src:"https://i.ibb.co/hZ0D9vs/where-do-i-find-my-font-awesome-key.png",width:"600"})}),"\n",(0,r.jsxs)(n.p,{children:["Be sure that you're specifying the icon category and name correctly. You're icon should look be ",(0,r.jsx)(n.code,{children:"[category] fa-[icon-name]"}),". The following categories are supported: ",(0,r.jsx)(n.code,{children:"far"})," ",(0,r.jsx)(n.em,{children:"(regular)"}),", ",(0,r.jsx)(n.code,{children:"fas"})," ",(0,r.jsx)(n.em,{children:"(solid)"}),", ",(0,r.jsx)(n.code,{children:"fal"}),(0,r.jsx)(n.em,{children:"(light)"}),", ",(0,r.jsx)(n.code,{children:"fad"})," ",(0,r.jsx)(n.em,{children:"(duo-tone)"})," and ",(0,r.jsx)(n.code,{children:"fab"}),(0,r.jsx)(n.em,{children:"(brands)"}),". With the exception of brands, you'll usually want all your icons to be in from same category, so they look uniform."]}),"\n",(0,r.jsxs)(n.p,{children:["Ensure the icon you are trying to use, is available within ",(0,r.jsx)(n.a,{href:"https://fontawesome.com/v5/search",children:"FontAwesome Version 5"})," (we've not yet upgraded to V6, as it works a little differently)."]}),"\n",(0,r.jsxs)(n.p,{children:["Examples: ",(0,r.jsx)(n.code,{children:"fab fa-raspberry-pi"}),", ",(0,r.jsx)(n.code,{children:"fas fa-database"}),", ",(0,r.jsx)(n.code,{children:"fas fa-server"}),", ",(0,r.jsx)(n.code,{children:"fas fa-ethernet"})]}),"\n",(0,r.jsxs)(n.p,{children:["Finally, check the ",(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"browser console"})," for any error messages, and raise a ticket if the issue persists."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"copy-to-clipboard-not-working",children:"Copy to Clipboard not Working"}),"\n",(0,r.jsxs)(n.p,{children:["If the copy to clipboard feature (either under Config --\x3e Export, or Item --\x3e Copy URL) isn't functioning as expected, first check the browser console. If you see ",(0,r.jsx)(n.code,{children:"TypeError: Cannot read properties of undefined (reading 'writeText')"})," then this feature is not supported by your browser.\nThe most common reason for this, is if you not running the app over HTTPS. Copying to the clipboard requires the app to be running in a secure origin / aka have valid HTTPS cert. You can read more about this ",(0,r.jsx)(n.a,{href:"https://stackoverflow.com/a/71876238/979052",children:"here"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"As a workaround, you could either:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Highlight the text and copy / ",(0,r.jsx)(n.kbd,{children:"Ctrl"})," + ",(0,r.jsx)(n.kbd,{children:"C"})]}),"\n",(0,r.jsxs)(n.li,{children:["Or setup SSL - ",(0,r.jsx)(n.a,{href:"/docs/management#ssl-certificates",children:"here's a guide"})," on doing so"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"how-to--reference",children:"How-To / Reference"}),"\n",(0,r.jsx)(n.h3,{id:"how-to-reset-local-settings",children:"How to Reset Local Settings"}),"\n",(0,r.jsx)(n.p,{children:"Some settings are stored locally, in the browser's storage."}),"\n",(0,r.jsx)(n.p,{children:"In some instances cached assets can prevent your settings from being updated, in which case you may wish to reset local data."}),"\n",(0,r.jsx)(n.p,{children:'To clear all local data from the UI, head to the Config Menu, then click "Reset Local Settings", and Confirm when prompted.\nThis will not affect your config file. But be sure that you keep a backup of your config, if you\'ve not written changes it to disk.'}),"\n",(0,r.jsxs)(n.p,{children:["You can also view any and all data that Dashy is storing, using the developer tools. Open your browser's dev tools (usually ",(0,r.jsx)(n.kbd,{children:"F12"}),"), in Chromium head to the Application tab, or in Firefox go to the Storage tab. Select Local Storage, then scroll down the the URL Dashy is running on. You should now see all data being stored, and you can select and delete any fields you wish."]}),"\n",(0,r.jsxs)(n.p,{children:["For a full list of all data that may be cached, see the ",(0,r.jsx)(n.a,{href:"/docs/privacy#browser-storage",children:"Privacy Docs"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"how-to-make-a-bug-report",children:"How to make a bug report"}),"\n",(0,r.jsx)(n.h4,{id:"step-1---where-to-open-issues",children:"Step 1 - Where to open issues"}),"\n",(0,r.jsxs)(n.p,{children:["You will need a GitHub account in order to raise a ticket. You can then ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/issues/new?assignees=lissy93&labels=%F0%9F%90%9B+Bug&template=bug.yml&title=%5BBUG%5D+%3Ctitle%3E",children:"click here"})," to open a new bug report."]}),"\n",(0,r.jsx)(n.h4,{id:"step-2---checking-its-not-already-covered",children:"Step 2 - Checking it's not already covered"}),"\n",(0,r.jsx)(n.p,{children:"Before submitting, please check that:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"A similar ticket has not previously been opened"}),"\n",(0,r.jsxs)(n.li,{children:["The issue is not covered in the ",(0,r.jsx)(n.a,{href:"/docs/troubleshooting",children:"troubleshooting guide"})," or ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/docs#readme",children:"docs"})]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"step-3---describe-the-issue",children:"Step 3 - Describe the Issue"}),"\n",(0,r.jsx)(n.p,{children:"Your ticket will likely be dealt with more effectively if you can explain the issue clearly, and provide all relevant supporting material."}),"\n",(0,r.jsx)(n.p,{children:"Complete the fields, asking for your environment info and version of Dashy.\nThen describe the issue, briefly explaining the steps to reproduce, expected outcome and actual outcome."}),"\n",(0,r.jsx)(n.h4,{id:"step-4---provide-supporting-info",children:"Step 4 - Provide Supporting Info"}),"\n",(0,r.jsx)(n.p,{children:"Where relevant please also include:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"A screenshot of the issue"}),"\n",(0,r.jsx)(n.li,{children:"The relevant parts of your config file"}),"\n",(0,r.jsxs)(n.li,{children:["Logs\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["If client-side issue, then include the browser logs (",(0,r.jsx)(n.a,{href:"#how-to-open-browser-console",children:"see how"}),")"]}),"\n",(0,r.jsx)(n.li,{children:"If server-side / during deployment, include the terminal output"}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.em,{children:"Take care to redact any personal info, (like IP addresses, auth hashes or API keys)."})}),"\n",(0,r.jsx)(n.h4,{id:"step-5---fix-released",children:"Step 5 - Fix Released"}),"\n",(0,r.jsx)(n.p,{children:"A maintainer will aim to respond within 48 hours.\nThe timeframe for resolving your issue, will vary depending on severity of the bug and the complexity of the fix.\nYou will be notified on your ticket, when a fix has been released."}),"\n",(0,r.jsxs)(n.p,{children:["Finally, be sure to remain respectful to other users and project maintainers, in line with the ",(0,r.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/.github/CODE_OF_CONDUCT#contributor-covenant-code-of-conduct",children:"Contributor Covenant Code of Conduct"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"how-to-open-browser-console",children:"How-To Open Browser Console"}),"\n",(0,r.jsx)(n.p,{children:"When raising a bug, one crucial piece of info needed is the browser's console output. This will help the developer diagnose and fix the issue."}),"\n",(0,r.jsxs)(n.p,{children:['If you\'ve been asked for this info, but are unsure where to find it, then it is under the "Console" tab, in the browsers developer tools, which can be opened with ',(0,r.jsx)(n.kbd,{children:"F12"}),". You can right-click the console, and select Save As to download the log."]}),"\n",(0,r.jsx)(n.p,{children:"To open dev tools, and jump straight to the console:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Win / Linux: ",(0,r.jsx)(n.kbd,{children:"Ctrl"})," + ",(0,r.jsx)(n.kbd,{children:"Shift"})," + ",(0,r.jsx)(n.kbd,{children:"J"})]}),"\n",(0,r.jsxs)(n.li,{children:["MacOS: ",(0,r.jsx)(n.kbd,{children:"Cmd"})," + ",(0,r.jsx)(n.kbd,{children:"Option"})," + ",(0,r.jsx)(n.kbd,{children:"J"})]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["For more detailed walk through, see ",(0,r.jsx)(n.a,{href:"https://support.shortpoint.com/support/solutions/articles/1000222881-save-browser-console-file",children:"this article"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"git-contributions-not-displaying",children:"Git Contributions not Displaying"}),"\n",(0,r.jsxs)(n.p,{children:["If you've contributed to Dashy (or any other project), but your contributions are not showing up on your GH profile, or in Dashy's ",(0,r.jsx)(n.a,{href:"/docs/credits",children:"Credits Page"}),", then this is likely a git config issue."]}),"\n",(0,r.jsxs)(n.p,{children:["These statistics are generated using the username / email associated with commits. This info needs to be setup on your local machine using ",(0,r.jsx)(n.a,{href:"https://git-scm.com/docs/git-config",children:(0,r.jsx)(n.code,{children:"git config"})}),"."]}),"\n",(0,r.jsx)(n.p,{children:"Run the following commands (replacing name + email with your info):"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:'git config --global user.name "John Doe"'})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"git config --global user.email johndoe@example.com"})}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["For more info, see ",(0,r.jsx)(n.a,{href:"https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup",children:"Git First Time Setup Docs"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"Note that only contributions to the master / main branch or a project are counted"})]})}function c(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(h,{...e})}):h(e)}},28453(e,n,s){s.d(n,{R:()=>t,x:()=>a});var i=s(96540);const r={},o=i.createContext(r);function t(e){const n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/a3713279.75a2e5b1.js b/assets/js/a3713279.8b3d526e.js similarity index 99% rename from assets/js/a3713279.75a2e5b1.js rename to assets/js/a3713279.8b3d526e.js index 46c807f0..b1b53c64 100644 --- a/assets/js/a3713279.75a2e5b1.js +++ b/assets/js/a3713279.8b3d526e.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[9588],{91989(e,n,s){s.r(n),s.d(n,{assets:()=>t,contentTitle:()=>i,default:()=>a,frontMatter:()=>o,metadata:()=>r,toc:()=>c});const r=JSON.parse('{"id":"deployment","title":"Deployment","description":"Welcome to Dashy, so glad you\'re here :) Deployment is super easy, and there are several methods available depending on what type of system you\'re using. If you\'re self-hosting, then deploying with Docker (or similar container engine) is the recommended approach.","source":"@site/docs/deployment.md","sourceDirName":".","slug":"/deployment","permalink":"/docs/deployment","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/deployment.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Quick Start","permalink":"/docs/quick-start"},"next":{"title":"Configuring","permalink":"/docs/configuring"}}');var l=s(74848),d=s(28453);const o={},i="Deployment",t={},c=[{value:"Quick Start",id:"quick-start",level:2},{value:"Deployment Methods",id:"deployment-methods",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Architecture",id:"architecture",level:3},{value:"System Resources",id:"system-resources",level:3},{value:"Bare Metal",id:"bare-metal",level:3},{value:"CDN / Cloud Deploy",id:"cdn--cloud-deploy",level:3},{value:"Browser Support",id:"browser-support",level:3}];function h(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(n.header,{children:(0,l.jsx)(n.h1,{id:"deployment",children:"Deployment"})}),"\n",(0,l.jsx)(n.p,{children:"Welcome to Dashy, so glad you're here :) Deployment is super easy, and there are several methods available depending on what type of system you're using. If you're self-hosting, then deploying with Docker (or similar container engine) is the recommended approach."}),"\n",(0,l.jsx)(n.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,l.jsxs)(n.p,{children:["If you want to skip the fuss, and ",(0,l.jsx)(n.a,{href:"/docs/quick-start",children:"get straight down to it"}),", then you can spin up a new instance of Dashy by running:"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"docker run -p 8080:8080 lissy93/dashy\n"})}),"\n",(0,l.jsxs)(n.p,{children:["See ",(0,l.jsx)(n.a,{href:"/docs/management",children:"Management Docs"})," for info about securing, monitoring, updating, health checks, auto starting, web server configuration, etc"]}),"\n",(0,l.jsxs)(n.p,{children:["Once you've got Dashy up and running, you'll want to configure it with your own content, for this you can reference the ",(0,l.jsx)(n.a,{href:"/docs/configuring",children:"configuring docs"}),"."]}),"\n",(0,l.jsx)(n.h2,{id:"deployment-methods",children:"Deployment Methods"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/docker",children:"Docker"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/docker#docker-run",children:"Docker Run"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/docker#docker-compose",children:"Docker Compose"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/docker#podman",children:"Podman"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/bare-metal",children:"Bare Metal"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/bare-metal#build-from-source",children:"Build from Source"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/bare-metal#pre-built-release",children:"Pre-Built Release"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os",children:"Self-Hosted Operating Systems"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#truenas-scale",children:"TrueNAS SCALE"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#proxmox-ve",children:"Proxmox VE"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#unraid",children:"Unraid"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#synology-nas",children:"Synology NAS"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#nixos",children:"NixOS"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#kubernetes",children:"Kubernetes"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms",children:"Self-Hosted Platforms"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#portainer",children:"Portainer"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#coolify",children:"Coolify"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#1panel",children:"1Panel"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#runtipi",children:"Runtipi"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#cosmos-cloud",children:"Cosmos Cloud"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#casaos",children:"CasaOS"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#umbrel",children:"Umbrel"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#easypanel",children:"EasyPanel"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#saltbox",children:"Saltbox"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/cloud",children:"Cloud Services"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#static-hosting-providers",children:"Static Hosting Providers"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#netlify",children:"Netlify"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#vercel",children:"Vercel"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#edgeone-pages",children:"EdgeOne Pages"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#cloudflare-pages",children:"Cloudflare Pages"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#firebase-hosting",children:"Firebase Hosting"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#azure-static-web-apps",children:"Azure Static Web Apps"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#container-runtimes",children:"Container Runtimes"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#render",children:"Render"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#railway",children:"Railway"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#flyio",children:"Fly.io"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#koyeb",children:"Koyeb"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#northflank",children:"Northflank"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#digitalocean-app-platform",children:"DigitalOcean App Platform"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#azure-container-apps",children:"Azure Container Apps"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#google-cloud-run",children:"Google Cloud Run"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#managed-hosting",children:"Managed Hosting"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#hosting-with-any-cdn",children:"Hosting with any CDN"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#other-vps",children:"Other VPS"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.hr,{}),"\n",(0,l.jsx)(n.h2,{id:"requirements",children:"Requirements"}),"\n",(0,l.jsx)(n.h3,{id:"architecture",children:"Architecture"}),"\n",(0,l.jsxs)(n.p,{children:["The pre-built Docker image runs on ",(0,l.jsx)(n.code,{children:"amd64"}),", ",(0,l.jsx)(n.code,{children:"arm64"})," and ",(0,l.jsx)(n.code,{children:"armv7"})," (",(0,l.jsx)(n.code,{children:"armv6"})," is not supported)."]}),"\n",(0,l.jsx)(n.h3,{id:"system-resources",children:"System Resources"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:"CPU: any single core, x86-64 or ARM"}),"\n",(0,l.jsx)(n.li,{children:"RAM: Node server idles around ~80-120 MB; 256 MB is comfortable, works in less"}),"\n",(0,l.jsx)(n.li,{children:"Disk: ~250 MB for the image + whatever your config/icons need"}),"\n",(0,l.jsx)(n.li,{children:"Runs fine on a Pi 3 and up"}),"\n"]}),"\n",(0,l.jsx)(n.h3,{id:"bare-metal",children:"Bare Metal"}),"\n",(0,l.jsxs)(n.p,{children:["Requires ",(0,l.jsx)(n.a,{href:"https://nodejs.org/",children:"Node.js"})," (20+) and ",(0,l.jsx)(n.a,{href:"https://yarnpkg.com/",children:"Yarn"})]}),"\n",(0,l.jsx)(n.h3,{id:"cdn--cloud-deploy",children:"CDN / Cloud Deploy"}),"\n",(0,l.jsx)(n.p,{children:"No specific requirements. The built app (without the Node server) is very lightweight and can be served by any static host or CDN. If you're using custom icons or other assets, additional disk space will be needed."}),"\n",(0,l.jsx)(n.h3,{id:"browser-support",children:"Browser Support"}),"\n",(0,l.jsxs)(n.p,{children:["JavaScript is required. Dashy targets browsers with >1% global usage and the last 2 versions of each (via ",(0,l.jsx)(n.a,{href:"https://browsersl.ist/",children:"browserslist"}),"). In practice, any modern browser works fine. Internet Explorer is not supported."]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,l.jsxs)(n.table,{children:[(0,l.jsx)(n.thead,{children:(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.th,{children:"Browser"}),(0,l.jsx)(n.th,{children:"Minimum Version"}),(0,l.jsx)(n.th,{children:"Status"})]})}),(0,l.jsxs)(n.tbody,{children:[(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Chrome / Chromium"}),(0,l.jsx)(n.td,{children:"90+"}),(0,l.jsx)(n.td,{children:"Fully supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Firefox"}),(0,l.jsx)(n.td,{children:"90+"}),(0,l.jsx)(n.td,{children:"Fully supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Edge"}),(0,l.jsx)(n.td,{children:"90+"}),(0,l.jsx)(n.td,{children:"Fully supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Safari"}),(0,l.jsx)(n.td,{children:"14+"}),(0,l.jsx)(n.td,{children:"Mostly Supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Opera"}),(0,l.jsx)(n.td,{children:"76+"}),(0,l.jsx)(n.td,{children:"Supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Samsung Internet"}),(0,l.jsx)(n.td,{children:"15+"}),(0,l.jsx)(n.td,{children:"Supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Firefox ESR"}),(0,l.jsx)(n.td,{children:"Latest"}),(0,l.jsx)(n.td,{children:"Supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Internet Explorer"}),(0,l.jsx)(n.td,{children:"-"}),(0,l.jsx)(n.td,{children:"Not supported"})]})]})]})]})}function a(e={}){const{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,l.jsx)(n,{...e,children:(0,l.jsx)(h,{...e})}):h(e)}},28453(e,n,s){s.d(n,{R:()=>o,x:()=>i});var r=s(96540);const l={},d=r.createContext(l);function o(e){const n=r.useContext(d);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(l):e.components||l:o(e.components),r.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[9588],{91989(e,n,s){s.r(n),s.d(n,{assets:()=>t,contentTitle:()=>i,default:()=>a,frontMatter:()=>o,metadata:()=>r,toc:()=>c});const r=JSON.parse('{"id":"deployment","title":"Deployment","description":"Welcome to Dashy, so glad you\'re here :) Deployment is super easy, and there are several methods available depending on what type of system you\'re using. If you\'re self-hosting, then deploying with Docker (or similar container engine) is the recommended approach.","source":"@site/docs/deployment.md","sourceDirName":".","slug":"/deployment","permalink":"/docs/deployment","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/deployment.md","tags":[],"version":"current","lastUpdatedAt":1783185149000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Quick Start","permalink":"/docs/quick-start"},"next":{"title":"Configuring","permalink":"/docs/configuring"}}');var l=s(74848),d=s(28453);const o={},i="Deployment",t={},c=[{value:"Quick Start",id:"quick-start",level:2},{value:"Deployment Methods",id:"deployment-methods",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Architecture",id:"architecture",level:3},{value:"System Resources",id:"system-resources",level:3},{value:"Bare Metal",id:"bare-metal",level:3},{value:"CDN / Cloud Deploy",id:"cdn--cloud-deploy",level:3},{value:"Browser Support",id:"browser-support",level:3}];function h(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(n.header,{children:(0,l.jsx)(n.h1,{id:"deployment",children:"Deployment"})}),"\n",(0,l.jsx)(n.p,{children:"Welcome to Dashy, so glad you're here :) Deployment is super easy, and there are several methods available depending on what type of system you're using. If you're self-hosting, then deploying with Docker (or similar container engine) is the recommended approach."}),"\n",(0,l.jsx)(n.h2,{id:"quick-start",children:"Quick Start"}),"\n",(0,l.jsxs)(n.p,{children:["If you want to skip the fuss, and ",(0,l.jsx)(n.a,{href:"/docs/quick-start",children:"get straight down to it"}),", then you can spin up a new instance of Dashy by running:"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"docker run -p 8080:8080 lissy93/dashy\n"})}),"\n",(0,l.jsxs)(n.p,{children:["See ",(0,l.jsx)(n.a,{href:"/docs/management",children:"Management Docs"})," for info about securing, monitoring, updating, health checks, auto starting, web server configuration, etc"]}),"\n",(0,l.jsxs)(n.p,{children:["Once you've got Dashy up and running, you'll want to configure it with your own content, for this you can reference the ",(0,l.jsx)(n.a,{href:"/docs/configuring",children:"configuring docs"}),"."]}),"\n",(0,l.jsx)(n.h2,{id:"deployment-methods",children:"Deployment Methods"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/docker",children:"Docker"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/docker#docker-run",children:"Docker Run"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/docker#docker-compose",children:"Docker Compose"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/docker#podman",children:"Podman"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/bare-metal",children:"Bare Metal"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/bare-metal#build-from-source",children:"Build from Source"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/bare-metal#pre-built-release",children:"Pre-Built Release"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os",children:"Self-Hosted Operating Systems"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#truenas-scale",children:"TrueNAS SCALE"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#proxmox-ve",children:"Proxmox VE"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#unraid",children:"Unraid"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#synology-nas",children:"Synology NAS"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#nixos",children:"NixOS"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-os#kubernetes",children:"Kubernetes"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms",children:"Self-Hosted Platforms"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#portainer",children:"Portainer"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#coolify",children:"Coolify"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#1panel",children:"1Panel"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#runtipi",children:"Runtipi"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#cosmos-cloud",children:"Cosmos Cloud"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#casaos",children:"CasaOS"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#umbrel",children:"Umbrel"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#easypanel",children:"EasyPanel"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/self-hosted-platforms#saltbox",children:"Saltbox"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/cloud",children:"Cloud Services"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#static-hosting-providers",children:"Static Hosting Providers"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#netlify",children:"Netlify"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#vercel",children:"Vercel"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#edgeone-pages",children:"EdgeOne Pages"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#cloudflare-pages",children:"Cloudflare Pages"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#firebase-hosting",children:"Firebase Hosting"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#azure-static-web-apps",children:"Azure Static Web Apps"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#container-runtimes",children:"Container Runtimes"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#render",children:"Render"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#railway",children:"Railway"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#flyio",children:"Fly.io"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#koyeb",children:"Koyeb"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#northflank",children:"Northflank"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#digitalocean-app-platform",children:"DigitalOcean App Platform"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#azure-container-apps",children:"Azure Container Apps"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#google-cloud-run",children:"Google Cloud Run"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#managed-hosting",children:"Managed Hosting"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#hosting-with-any-cdn",children:"Hosting with any CDN"})}),"\n",(0,l.jsx)(n.li,{children:(0,l.jsx)(n.a,{href:"/docs/deployment/cloud#other-vps",children:"Other VPS"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.hr,{}),"\n",(0,l.jsx)(n.h2,{id:"requirements",children:"Requirements"}),"\n",(0,l.jsx)(n.h3,{id:"architecture",children:"Architecture"}),"\n",(0,l.jsxs)(n.p,{children:["The pre-built Docker image runs on ",(0,l.jsx)(n.code,{children:"amd64"}),", ",(0,l.jsx)(n.code,{children:"arm64"})," and ",(0,l.jsx)(n.code,{children:"armv7"})," (",(0,l.jsx)(n.code,{children:"armv6"})," is not supported)."]}),"\n",(0,l.jsx)(n.h3,{id:"system-resources",children:"System Resources"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:"CPU: any single core, x86-64 or ARM"}),"\n",(0,l.jsx)(n.li,{children:"RAM: Node server idles around ~80-120 MB; 256 MB is comfortable, works in less"}),"\n",(0,l.jsx)(n.li,{children:"Disk: ~250 MB for the image + whatever your config/icons need"}),"\n",(0,l.jsx)(n.li,{children:"Runs fine on a Pi 3 and up"}),"\n"]}),"\n",(0,l.jsx)(n.h3,{id:"bare-metal",children:"Bare Metal"}),"\n",(0,l.jsxs)(n.p,{children:["Requires ",(0,l.jsx)(n.a,{href:"https://nodejs.org/",children:"Node.js"})," (20+) and ",(0,l.jsx)(n.a,{href:"https://yarnpkg.com/",children:"Yarn"})]}),"\n",(0,l.jsx)(n.h3,{id:"cdn--cloud-deploy",children:"CDN / Cloud Deploy"}),"\n",(0,l.jsx)(n.p,{children:"No specific requirements. The built app (without the Node server) is very lightweight and can be served by any static host or CDN. If you're using custom icons or other assets, additional disk space will be needed."}),"\n",(0,l.jsx)(n.h3,{id:"browser-support",children:"Browser Support"}),"\n",(0,l.jsxs)(n.p,{children:["JavaScript is required. Dashy targets browsers with >1% global usage and the last 2 versions of each (via ",(0,l.jsx)(n.a,{href:"https://browsersl.ist/",children:"browserslist"}),"). In practice, any modern browser works fine. Internet Explorer is not supported."]}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,l.jsxs)(n.table,{children:[(0,l.jsx)(n.thead,{children:(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.th,{children:"Browser"}),(0,l.jsx)(n.th,{children:"Minimum Version"}),(0,l.jsx)(n.th,{children:"Status"})]})}),(0,l.jsxs)(n.tbody,{children:[(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Chrome / Chromium"}),(0,l.jsx)(n.td,{children:"90+"}),(0,l.jsx)(n.td,{children:"Fully supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Firefox"}),(0,l.jsx)(n.td,{children:"90+"}),(0,l.jsx)(n.td,{children:"Fully supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Edge"}),(0,l.jsx)(n.td,{children:"90+"}),(0,l.jsx)(n.td,{children:"Fully supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Safari"}),(0,l.jsx)(n.td,{children:"14+"}),(0,l.jsx)(n.td,{children:"Mostly Supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Opera"}),(0,l.jsx)(n.td,{children:"76+"}),(0,l.jsx)(n.td,{children:"Supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Samsung Internet"}),(0,l.jsx)(n.td,{children:"15+"}),(0,l.jsx)(n.td,{children:"Supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Firefox ESR"}),(0,l.jsx)(n.td,{children:"Latest"}),(0,l.jsx)(n.td,{children:"Supported"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"Internet Explorer"}),(0,l.jsx)(n.td,{children:"-"}),(0,l.jsx)(n.td,{children:"Not supported"})]})]})]})]})}function a(e={}){const{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,l.jsx)(n,{...e,children:(0,l.jsx)(h,{...e})}):h(e)}},28453(e,n,s){s.d(n,{R:()=>o,x:()=>i});var r=s(96540);const l={},d=r.createContext(l);function o(e){const n=r.useContext(d);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(l):e.components||l:o(e.components),r.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/a63906ba.2ca85396.js b/assets/js/a63906ba.1bcf6e0c.js similarity index 99% rename from assets/js/a63906ba.2ca85396.js rename to assets/js/a63906ba.1bcf6e0c.js index 83531a9d..1c65985f 100644 --- a/assets/js/a63906ba.2ca85396.js +++ b/assets/js/a63906ba.1bcf6e0c.js @@ -1 +1 @@ -"use strict";(globalThis.webpackChunkdashy=globalThis.webpackChunkdashy||[]).push([[6903],{72737(e,n,s){s.r(n),s.d(n,{assets:()=>d,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>i,toc:()=>l});const i=JSON.parse('{"id":"development-guides","title":"Development Guides","description":"A series of short tutorials, to guide you through the most common development tasks.","source":"@site/docs/development-guides.md","sourceDirName":".","slug":"/development-guides","permalink":"/docs/development-guides","draft":false,"unlisted":false,"editUrl":"https://github.com/Lissy93/dashy/edit/master/docs/docs/development-guides.md","tags":[],"version":"current","lastUpdatedAt":1783176042000,"frontMatter":{},"sidebar":"dashySidebar","previous":{"title":"Developing","permalink":"/docs/developing"},"next":{"title":"Privacy & Security","permalink":"/docs/privacy"}}');var t=s(74848),r=s(28453);const a={},o="Development Guides",d={},l=[{value:"Creating a new theme",id:"creating-a-new-theme",level:2},{value:"1. Add Theme Name",id:"1-add-theme-name",level:3},{value:"2. Write some Styles",id:"2-write-some-styles",level:3},{value:"Writing Translations",id:"writing-translations",level:2},{value:"1. Create a new Language File",id:"1-create-a-new-language-file",level:3},{value:"2. Translate",id:"2-translate",level:3},{value:"3. Add your file to the app",id:"3-add-your-file-to-the-app",level:3},{value:"Adding a new option in the config file",id:"adding-a-new-option-in-the-config-file",level:2},{value:"Updating Dependencies",id:"updating-dependencies",level:2},{value:"Developing Netlify Cloud Functions",id:"developing-netlify-cloud-functions",level:2},{value:"1. Run Netlify Dev Server",id:"1-run-netlify-dev-server",level:3},{value:"2. Create a lambda function",id:"2-create-a-lambda-function",level:3},{value:"3. Redirect the Node endpoint to the function",id:"3-redirect-the-node-endpoint-to-the-function",level:3},{value:"Hiding Page Furniture on Certain Routes",id:"hiding-page-furniture-on-certain-routes",level:2},{value:"1. Add the route name to the should hide array",id:"1-add-the-route-name-to-the-should-hide-array",level:3},{value:"2. Add the conditional to the structural component to hide",id:"2-add-the-conditional-to-the-structural-component-to-hide",level:3},{value:"Adding / Using Environmental Variables",id:"adding--using-environmental-variables",level:2},{value:"Building a Widget",id:"building-a-widget",level:2},{value:"Step 0 - Prerequisites",id:"step-0---prerequisites",level:3},{value:"Step 1 - Create Widget",id:"step-1---create-widget",level:3},{value:"Step 2 - Adding Functionality",id:"step-2---adding-functionality",level:3},{value:"Accessing User Options",id:"accessing-user-options",level:4},{value:"Adding an API Endpoint",id:"adding-an-api-endpoint",level:4},{value:"Making an API Request",id:"making-an-api-request",level:4},{value:"Processing Response",id:"processing-response",level:4},{value:"Rendering Response",id:"rendering-response",level:4},{value:"Styling",id:"styling",level:4},{value:"Step 3 - Register",id:"step-3---register",level:3},{value:"Step 4 - Docs",id:"step-4---docs",level:3},{value:"Respecting Config Permissions",id:"respecting-config-permissions",level:2}];function c(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",input:"input",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"development-guides",children:"Development Guides"})}),"\n",(0,t.jsx)(n.p,{children:"A series of short tutorials, to guide you through the most common development tasks."}),"\n",(0,t.jsx)(n.p,{children:"Sections:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#creating-a-new-theme",children:"Creating a new theme"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#writing-translations",children:"Writing Translations"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#adding-a-new-option-in-the-config-file",children:"Adding a new option in the config file"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#updating-dependencies",children:"Updating Dependencies"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#developing-netlify-cloud-functions",children:"Writing Netlify Cloud Functions"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#hiding-page-furniture-on-certain-routes",children:"Hiding Page Furniture"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#adding--using-environmental-variables",children:"Adding / Using Environmental Variables"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#building-a-widget",children:"Building a Widget"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"#respecting-config-permissions",children:"Respecting Config Permissions"})}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"creating-a-new-theme",children:"Creating a new theme"}),"\n",(0,t.jsx)(n.p,{children:"Adding a new theme is really easy. There're two things you need to do: Pass the theme name to Dashy, so that it can be added to the theme selector dropdown menu, and then write some styles!"}),"\n",(0,t.jsx)(n.h3,{id:"1-add-theme-name",children:"1. Add Theme Name"}),"\n",(0,t.jsxs)(n.p,{children:["Choose a snappy name for your theme, and add it to the ",(0,t.jsx)(n.code,{children:"builtInThemes"})," array inside ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/defaults.js#l27",children:(0,t.jsx)(n.code,{children:"defaults.js"})}),"."]}),"\n",(0,t.jsx)(n.h3,{id:"2-write-some-styles",children:"2. Write some Styles"}),"\n",(0,t.jsxs)(n.p,{children:["Put your theme styles inside ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/styles/color-themes.scss",children:(0,t.jsx)(n.code,{children:"color-themes.scss"})}),".\nCreate a new block, and make sure that ",(0,t.jsx)(n.code,{children:"data-theme"})," matches the theme name you chose above. For example:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-css",children:"html[data-theme='tiger'] {\n --primary: #f58233;\n --background: #0b1021;\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Then you can go ahead and write your own custom CSS. Although all CSS is supported here, the best way to define your theme is by setting the CSS variables. You can find a ",(0,t.jsx)(n.a,{href:"/docs/theming#css-variables",children:"list of all CSS variables, here"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["For a full guide on styling, see ",(0,t.jsx)(n.a,{href:"/docs/theming",children:"Theming Docs"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["Note that if your theme is just for yourself, and you're not submitting a PR, then you can instead just pass it under ",(0,t.jsx)(n.code,{children:"appConfig.cssThemes"})," inside your config file. And then put your theme in your own stylesheet, and pass it into the Docker container - ",(0,t.jsx)(n.a,{href:"/docs/theming#adding-your-own-theme",children:"see how"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"writing-translations",children:"Writing Translations"}),"\n",(0,t.jsxs)(n.p,{children:["For full docs about Dashy's multi-language support, see ",(0,t.jsx)(n.a,{href:"/docs/multi-language-support",children:"Multi-Language Support"})]}),"\n",(0,t.jsxs)(n.p,{children:["Dashy is using ",(0,t.jsx)(n.a,{href:"https://vue-i18n.intlify.dev/guide/",children:"vue-i18n"})," to manage multi-language support."]}),"\n",(0,t.jsx)(n.p,{children:"Adding a new language is pretty straightforward, with just three steps:"}),"\n",(0,t.jsx)(n.h3,{id:"1-create-a-new-language-file",children:"1. Create a new Language File"}),"\n",(0,t.jsxs)(n.p,{children:["Create a new JSON file in ",(0,t.jsx)(n.code,{children:"./src/assets/locales"})," name is a 2-digit ",(0,t.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes",children:"ISO-639 code"})," for your language, E.g. for German ",(0,t.jsx)(n.code,{children:"de.json"}),", French ",(0,t.jsx)(n.code,{children:"fr.json"})," or Spanish ",(0,t.jsx)(n.code,{children:"es.json"})," - You can find a list of all ISO codes at ",(0,t.jsx)(n.a,{href:"https://www.iso.org/obp/ui",children:"iso.org"}),"."]}),"\n",(0,t.jsx)(n.h3,{id:"2-translate",children:"2. Translate"}),"\n",(0,t.jsxs)(n.p,{children:["Using ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/src/assets/locales/en.json",children:(0,t.jsx)(n.code,{children:"en.json"})})," as an example, translate the JSON values to your language, while leaving the keys as they are. It's fine to leave out certain items, as if they're missing they will fall-back to English. If you see any attribute which include curly braces (",(0,t.jsx)(n.code,{children:"{xxx}"}),"), then leave the inner value of these braces as is, as this is for variables."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'{\n "theme-maker": {\n "export-button": "Benutzerdefinierte Variablen exportieren",\n "reset-button": "Stile zur\xfccksetzen f\xfcr",\n "show-all-button": "Alle Variablen anzeigen",\n "save-button": "Speichern",\n "cancel-button": "Abbrechen",\n "saved-toast": "{theme} Erfolgreich aktualisiert",\n "reset-toast": "Benutzerdefinierte Farben f\xfcr {theme} entfernt"\n },\n}\n'})}),"\n",(0,t.jsx)(n.h3,{id:"3-add-your-file-to-the-app",children:"3. Add your file to the app"}),"\n",(0,t.jsxs)(n.p,{children:["In ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/src/utils/languages.js",children:(0,t.jsx)(n.code,{children:"./src/utils/languages.js"})}),", you need to do 2 small things:"]}),"\n",(0,t.jsxs)(n.p,{children:["First import your new translation file, do this at the top of the page.\nE.g. ",(0,t.jsx)(n.code,{children:"import de from '@/assets/locales/de.json';"})]}),"\n",(0,t.jsx)(n.p,{children:"Second, add it to the array of languages, e.g:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-javascript",children:"export const languages = [\n {\n name: 'English',\n code: 'en',\n locale: en,\n flag: '\ud83c\uddec\ud83c\udde7',\n },\n {\n name: 'German', // The name of your language\n code: 'de', // The ISO code of your language\n locale: de, // The name of the file you imported (no quotes)\n flag: '\ud83c\udde9\ud83c\uddea', // An optional flag emoji\n },\n];\n"})}),"\n",(0,t.jsxs)(n.p,{children:["You can also add your new language to the readme file, under the ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy#language-switching",children:"Language Switching"})," section, and optionally include your name/ username if you'd like to be credited for your work. Done!"]}),"\n",(0,t.jsx)(n.p,{children:"If you are not comfortable with making pull requests, or do not want to modify the code, then feel free to instead send the translated file to me, and I can add it into the application. I will be sure to credit you appropriately."}),"\n",(0,t.jsx)(n.h2,{id:"adding-a-new-option-in-the-config-file",children:"Adding a new option in the config file"}),"\n",(0,t.jsx)(n.p,{children:"This section is for, adding a new setting to the config file."}),"\n",(0,t.jsxs)(n.p,{children:["All of the users config is specified in ",(0,t.jsx)(n.code,{children:"./user-data/conf.yml"})," - see ",(0,t.jsx)(n.a,{href:"/docs/configuring",children:"Configuring Docs"})," for info.\nIt's important to first ensure that there isn't a similar option already available, the new option is definitely necessary, and most importantly that it is fully backwards compatible."]}),"\n",(0,t.jsx)(n.p,{children:"Next choose the appropriate section to place it under"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Application settings should be located under ",(0,t.jsx)(n.code,{children:"appConfig"})]}),"\n",(0,t.jsxs)(n.li,{children:["Page info (such as text and metadata) should be under ",(0,t.jsx)(n.code,{children:"pageInfo"})]}),"\n",(0,t.jsxs)(n.li,{children:["Data relating to specific sections should be under ",(0,t.jsx)(n.code,{children:"section[n].displayData"})]}),"\n",(0,t.jsxs)(n.li,{children:["Settings applied to specific items or widgets, should be under ",(0,t.jsx)(n.code,{children:"item[n]"})," or ",(0,t.jsx)(n.code,{children:"widget[n]"})]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["For example, if your option is added under ",(0,t.jsx)(n.code,{children:"appConfig"}),", you can access it within your component using the ",(0,t.jsx)(n.code,{children:"$store"}),", this is typically placed in a computed property, e.g:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-javascript",children:"computed: {\n appConfig() {\n return this.$store.getters.appConfig;\n },\n ...\n},\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Then, where you want to get the users value within your component, use something like: ",(0,t.jsx)(n.code,{children:"this.appConfig.myProperty"}),". If the user hasn't specified the value, Don't forget to have a fallback or default for it."]}),"\n",(0,t.jsxs)(n.p,{children:["If you have a default fallback value, then this would typically be specified in the ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/defaults.js",children:(0,t.jsx)(n.code,{children:"defaults.js"})})," file."]}),"\n",(0,t.jsxs)(n.p,{children:["You will now need to add the definition of your new attribute into the ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json",children:"ConfigSchema"}),". This will make it available in the UI config editor, and also ensure that the config validation check doesn't fail.\nFor example:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'"fontAwesomeKey": {\n "type": "string",\n "pattern": "^[a-z0-9]{10}$",\n "description": "API key for font-awesome",\n "example": "0821c65656"\n}\n'})}),"\n",(0,t.jsx)(n.p,{children:"or"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'"iconSize": {\n "enum": [ "small", "medium", "large" ],\n "default": "medium",\n "description": "The size of each link item / icon"\n}\n'})}),"\n",(0,t.jsxs)(n.p,{children:["Finally, add your new property to the ",(0,t.jsx)(n.a,{href:"/docs/configuring",children:(0,t.jsx)(n.code,{children:"configuring.md"})})," API docs. Put it under the relevant section, and be sure to include field name, data type, a description and mention that it is optional. If your new feature needs more explanation, then you can also document it under the relevant section elsewhere in the documentation."]}),"\n",(0,t.jsx)(n.p,{children:"Checklist:"}),"\n",(0,t.jsxs)(n.ul,{className:"contains-task-list",children:["\n",(0,t.jsxs)(n.li,{className:"task-list-item",children:[(0,t.jsx)(n.input,{type:"checkbox",disabled:!0})," Ensure the new attribute is actually necessary, and nothing similar already exists"]}),"\n",(0,t.jsxs)(n.li,{className:"task-list-item",children:[(0,t.jsx)(n.input,{type:"checkbox",disabled:!0})," Update the ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/ConfigSchema.json",children:"Schema"})," with the parameters for your new option"]}),"\n",(0,t.jsxs)(n.li,{className:"task-list-item",children:[(0,t.jsx)(n.input,{type:"checkbox",disabled:!0})," If required, set a default or fallback value (usually in ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/defaults.js",children:(0,t.jsx)(n.code,{children:"defaults.js"})}),")"]}),"\n",(0,t.jsxs)(n.li,{className:"task-list-item",children:[(0,t.jsx)(n.input,{type:"checkbox",disabled:!0})," Document the new value in ",(0,t.jsx)(n.a,{href:"/docs/configuring",children:(0,t.jsx)(n.code,{children:"configuring.md"})}),", and if required under the relevant section in the docs"]}),"\n",(0,t.jsxs)(n.li,{className:"task-list-item",children:[(0,t.jsx)(n.input,{type:"checkbox",disabled:!0})," Ensure your changes are backwards compatible, and that nothing breaks if the attribute isn't specified"]}),"\n"]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"updating-dependencies",children:"Updating Dependencies"}),"\n",(0,t.jsxs)(n.p,{children:["Running ",(0,t.jsx)(n.code,{children:"yarn upgrade"})," will updated all dependencies based on the ranges specified in the ",(0,t.jsx)(n.code,{children:"package.json"}),". The ",(0,t.jsx)(n.code,{children:"yarn.lock"})," file will be updated, as will the contents of ",(0,t.jsx)(n.code,{children:"./node_modules"}),", for more info, see the ",(0,t.jsx)(n.a,{href:"https://classic.yarnpkg.com/en/docs/cli/upgrade/",children:"yarn upgrade documentation"}),". ",(0,t.jsx)(n.a,{href:"https://github.com/raineorshine/npm-check-updates",children:(0,t.jsx)(n.code,{children:"npm-check-updates"})})," is a useful tool to help with this.\nIt is important to thoroughly test after any big dependency updates."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"developing-netlify-cloud-functions",children:"Developing Netlify Cloud Functions"}),"\n",(0,t.jsx)(n.p,{children:"When Dashy is deployed to Netlify, it is effectively running as a static app, and therefore the server-side code for the Node.js endpoints is not available. However Netlify now supports serverless cloud lambda functions, which can be used to replace most functionality."}),"\n",(0,t.jsx)(n.h3,{id:"1-run-netlify-dev-server",children:"1. Run Netlify Dev Server"}),"\n",(0,t.jsxs)(n.p,{children:["First off all, install the Netlify CLI: ",(0,t.jsx)(n.code,{children:"npm install netlify-cli -g"}),"\nThen, from within the root of Dashy's directory, start the server, by running: ",(0,t.jsx)(n.code,{children:"netlify dev"})]}),"\n",(0,t.jsx)(n.h3,{id:"2-create-a-lambda-function",children:"2. Create a lambda function"}),"\n",(0,t.jsxs)(n.p,{children:["This should be saved in the ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/services/serverless-functions",children:(0,t.jsx)(n.code,{children:"./services/serverless-functions"})})," directory"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-javascript",children:"exports.handler = async () => ({\n statusCode: 200,\n body: 'Return some data here...',\n});\n"})}),"\n",(0,t.jsx)(n.h3,{id:"3-redirect-the-node-endpoint-to-the-function",children:"3. Redirect the Node endpoint to the function"}),"\n",(0,t.jsxs)(n.p,{children:["In the ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/FEATURE/serverless-functions/netlify.toml",children:(0,t.jsx)(n.code,{children:"netlify.toml"})})," file, add a 301 redirect, with the path to the original Node.js endpoint, and the name of your cloud function"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-toml",children:'[[redirects]]\n from = "/status-check"\n to = "/.netlify/functions/cloud-status-check"\n status = 301\n force = true\n'})}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"hiding-page-furniture-on-certain-routes",children:"Hiding Page Furniture on Certain Routes"}),"\n",(0,t.jsx)(n.p,{children:"For some pages (such as the login page, the minimal start page, etc) the basic page furniture, (like header, footer, nav, etc) is not needed. This section explains how you can hide furniture on a new view (step 1), or add a component that should be hidden on certain views (step 2)."}),"\n",(0,t.jsx)(n.h3,{id:"1-add-the-route-name-to-the-should-hide-array",children:"1. Add the route name to the should hide array"}),"\n",(0,t.jsxs)(n.p,{children:["In ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/defaults.js",children:(0,t.jsx)(n.code,{children:"./src/utils/config/defaults.js"})}),", there's an array called ",(0,t.jsx)(n.code,{children:"hideFurnitureOn"}),". Append the name of the route (the same as it appears in ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/router.js",children:(0,t.jsx)(n.code,{children:"router.js"})}),") here."]}),"\n",(0,t.jsx)(n.h3,{id:"2-add-the-conditional-to-the-structural-component-to-hide",children:"2. Add the conditional to the structural component to hide"}),"\n",(0,t.jsx)(n.p,{children:"First, import the helper function:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-javascript",children:"import { shouldBeVisible } from '@/utils/config/SectionHelpers';\n"})}),"\n",(0,t.jsx)(n.p,{children:"Then you can create a computed value, that calls this function, passing in the route name:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-javascript",children:"export default {\n ...\n computed: {\n ...\n isVisible() {\n return shouldBeVisible(this.$route.name);\n },\n },\n};\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Finally, in the markup of your component, just add a ",(0,t.jsx)(n.code,{children:"v-if"})," statement, referencing your computed value"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-vue",children:'
\n ...\n
\n'})}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"adding--using-environmental-variables",children:"Adding / Using Environmental Variables"}),"\n",(0,t.jsxs)(n.p,{children:["All environmental variables are optional. Currently there are not many environmental variables used, as most of the user preferences are stored under ",(0,t.jsx)(n.code,{children:"appConfig"})," in the ",(0,t.jsx)(n.code,{children:"conf.yml"})," file."]}),"\n",(0,t.jsxs)(n.p,{children:["You can set variables either in your environment, or using the ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/.env",children:(0,t.jsx)(n.code,{children:".env"})})," file."]}),"\n",(0,t.jsxs)(n.p,{children:["Any environmental variables used by the frontend are preceded with ",(0,t.jsx)(n.code,{children:"VITE_APP_"}),". Vite will merge the contents of your ",(0,t.jsx)(n.code,{children:".env"})," file into the app in a similar way to the ",(0,t.jsx)(n.a,{href:"https://github.com/motdotla/dotenv",children:"'dotenv'"})," package, where any variables that you set on your system will always take preference over the contents of any ",(0,t.jsx)(n.code,{children:".env"})," file."]}),"\n",(0,t.jsxs)(n.p,{children:["If add any new variables, ensure that there is always a fallback (define it in ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/utils/config/defaults.js",children:(0,t.jsx)(n.code,{children:"defaults.js"})}),"), so as to not cause breaking changes. Don't commit the contents of your ",(0,t.jsx)(n.code,{children:".env"})," file to git, but instead take a few moments to document what you've added under the appropriate section. Try and follow the concepts outlined in the ",(0,t.jsx)(n.a,{href:"https://12factor.net/config",children:"12 factor app"}),"."]}),"\n",(0,t.jsx)(n.hr,{}),"\n",(0,t.jsx)(n.h2,{id:"building-a-widget",children:"Building a Widget"}),"\n",(0,t.jsx)(n.h3,{id:"step-0---prerequisites",children:"Step 0 - Prerequisites"}),"\n",(0,t.jsxs)(n.p,{children:["If this is your first time working on Dashy, then the ",(0,t.jsx)(n.a,{href:"/docs/developing",children:"Developing Docs"})," instructions for project setup and running. In short, you just need to clone the project, cd into it, install dependencies (",(0,t.jsx)(n.code,{children:"yarn"}),") and then start the development server (",(0,t.jsx)(n.code,{children:"yarn dev"}),")."]}),"\n",(0,t.jsxs)(n.p,{children:["To build a widget, you'll also need some basic knowledge of Vue.js. The ",(0,t.jsx)(n.a,{href:"https://vuejs.org/guide/",children:"official Vue docs"})," provides a good starting point, as does ",(0,t.jsx)(n.a,{href:"https://www.taniarascia.com/getting-started-with-vue/",children:"this guide"})," by Tania Rascia"]}),"\n",(0,t.jsxs)(n.p,{children:["If you just want to jump straight in, then ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/commit/3da76ce2999f57f76a97454c0276301e39957b8e",children:"here"})," is a complete implementation of a new example widget, or take a look at the ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/blob/master/src/components/Widgets/XkcdComic.vue",children:(0,t.jsx)(n.code,{children:"XkcdComic.vue"})})," widget, which is pretty simple."]}),"\n",(0,t.jsx)(n.h3,{id:"step-1---create-widget",children:"Step 1 - Create Widget"}),"\n",(0,t.jsxs)(n.p,{children:["Firstly, create a new ",(0,t.jsx)(n.code,{children:".vue"})," file under ",(0,t.jsx)(n.a,{href:"https://github.com/Lissy93/dashy/tree/master/src/components/Widgets",children:(0,t.jsx)(n.code,{children:"./src/components/Widgets"})}),"."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-vue",children:"\n\n - - + + + - + \ No newline at end of file diff --git a/docs/alternate-views/index.html b/docs/alternate-views/index.html index 18ea83d4..276d57d5 100644 --- a/docs/alternate-views/index.html +++ b/docs/alternate-views/index.html @@ -11,15 +11,15 @@ - - - + + + -

Alternate Views & Opening Methods

+

Alternate Views & Opening Methods

Views

Dashy has three different views:

    @@ -55,6 +55,6 @@

    Even if the target is not set (or is set to sametab), you can still launch any given app in an alternative method. Either right-click to see all options, or use one of the keyboard shortcuts: Alt + Click will open the modal, and Ctrl + Click will open in a new tab.

    If you don't like the custom context menu, it can be disabled by setting appConfig.disableContextMenu: true.

    -

    If you get a 'Refused to Connect' error in the modal or workspace views, then the target app has it's X-Frame-Options HTTP set to block requests from embedded content. You can easily fix this by setting this header to ALLOW, for instructions on how to do so, see the Troubleshooting Docs.

+

If you get a 'Refused to Connect' error in the modal or workspace views, then the target app has it's X-Frame-Options HTTP set to block requests from embedded content. You can easily fix this by setting this header to ALLOW, for instructions on how to do so, see the Troubleshooting Docs.

\ No newline at end of file diff --git a/docs/api/index.html b/docs/api/index.html index 81ba3864..edd72dbf 100644 --- a/docs/api/index.html +++ b/docs/api/index.html @@ -11,15 +11,15 @@ - - - + + + -

REST API

+

REST API

Dashy includes an optional REST API, for reading and writing your config programmatically — from the command line, scripts or third-party applications. It covers whole config files, as well as individual sections and items.

note

The API is served by Dashy's Node server, so it's available with Docker and bare-metal deployments, but not on static hosting providers (Netlify, Vercel, EdgeOne, CDN).

Enabling the API

@@ -131,6 +131,6 @@
  • Writes to conf.yml are validated against the schema and rejected if invalid. Sub-page files are not schema-validated, since they may contain only a subset of fields
  • Config files are capped at 256 KB
  • Concurrent writes are last-write-wins; there is no locking or optimistic concurrency
  • -
    +
    \ No newline at end of file diff --git a/docs/authentication/authelia-oidc/index.html b/docs/authentication/authelia-oidc/index.html index c39d9d58..80b38833 100644 --- a/docs/authentication/authelia-oidc/index.html +++ b/docs/authentication/authelia-oidc/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Authelia OIDC

    +

    Authelia OIDC

    Dashy supports using Authelia as its OIDC provider.

    Authelia is an open source authentication and authorization server for self-hosted services. It speaks OIDC, supports MFA, and is configured via a single YAML file. Users live in a flat file, LDAP, or your own SQL store. It's a popular pairing with reverse proxies for forward-auth across many apps, and the same instance can act as your Dashy OIDC provider.

    Contents

    @@ -186,6 +186,6 @@ Solution: The server reads the auth config only at boot. Restart the Dashy conta
    Server-side request handling
    -
    +
    \ No newline at end of file diff --git a/docs/authentication/authentik/index.html b/docs/authentication/authentik/index.html index 7872f7dc..c0913b81 100644 --- a/docs/authentication/authentik/index.html +++ b/docs/authentication/authentik/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Authentik OIDC

    +

    Authentik OIDC

    Dashy supports using Authentik as its OIDC provider.

    Authentik is an open source identity provider that speaks OIDC, OAuth 2.0, SAML 2.0 and LDAP. It runs in Docker, has a polished admin UI, and supports MFA, social login, and per-application group policies, which makes it a good fit for self-hosted setups where you want a single login across many services.

    Contents

    @@ -250,6 +250,6 @@ Solution: The server reads the auth config only at boot. Restart the Dashy conta

    When the token expires you're bounced back through Authentik for a new one, which is usually instant since you still have a session there. With enableSilentRenew on, Dashy refreshes it in the background and you won't notice at all.

    To sign out, use Dashy's Logout control: it clears the stored token and sends you to Authentik's end-session endpoint (see Logout stuck on a consent screen if that asks for confirmation).

    -

    If you want the implementation details, the client side lives in src/utils/auth/OidcAuth.js and the server-side token verification in services/auth-oidc.js.

    +

    If you want the implementation details, the client side lives in src/utils/auth/OidcAuth.js and the server-side token verification in services/auth-oidc.js.

    \ No newline at end of file diff --git a/docs/authentication/built-in/index.html b/docs/authentication/built-in/index.html index 38840c7f..e8e02694 100644 --- a/docs/authentication/built-in/index.html +++ b/docs/authentication/built-in/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Built-In Auth

    +

    Built-In Auth

    Dashy ships with a simple username + password auth system baked in, as well as the option to enforce this server-side with standard HTTP auth.

    This is the quickest way to setup a login screen, without the need to run any other auth services. No identity provider to run, no external dependencies, just a list of users in conf.yml. Useful for keeping casual eyes of your dashboard within a LAN, but is probably not recommended if your instance is exposed to the public internet.

    This guide covers everything the built-in auth can do, from the lightest "show a login screen" mode to a server-enforced setup that 401s every unauthenticated request. If you need OIDC, SSO, or anything internet-facing, see the other guides in this folder. Built-in auth is convenient, not hardened.

    @@ -243,6 +243,6 @@ Solution: Reverse proxies sometimes strip the Authorization header
    End-to-end flow with ENABLE_HTTP_AUTH=true
    -
    +
    \ No newline at end of file diff --git a/docs/authentication/cloudflare-tunnel/index.html b/docs/authentication/cloudflare-tunnel/index.html index 87c4d60c..a53ea5f4 100644 --- a/docs/authentication/cloudflare-tunnel/index.html +++ b/docs/authentication/cloudflare-tunnel/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Cloudflare Tunnel

    +

    Cloudflare Tunnel

    Dashy works well behind Cloudflare Tunnel with Cloudflare Access doing auth at the edge. This is one of the most common ways to expose a homelab dashboard to the public internet without opening any inbound ports, without managing TLS, and without standing up your own identity provider.

    The flow: Cloudflare authenticates the user (via Google, GitHub, email OTP, etc.) at the edge, signs an identity into request headers, and forwards the request to a cloudflared daemon you run on your network. cloudflared proxies to Dashy. Dashy reads the email from the header and matches the user against its config.

    Contents

    @@ -169,6 +169,6 @@ Solution: In the Access application, add a Service Token, then create a Bypass p
    End-to-end authentication flow
    -
    +
    \ No newline at end of file diff --git a/docs/authentication/header-auth/index.html b/docs/authentication/header-auth/index.html index bbf5d6c0..028bd5fb 100644 --- a/docs/authentication/header-auth/index.html +++ b/docs/authentication/header-auth/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Header Authentication

    +

    Header Authentication

    Header authentication lets Dashy trust a reverse proxy in front of it to handle login. The proxy authenticates the user, then passes their username to Dashy in an HTTP header. Dashy reads that header and signs them in automatically, so there's no separate Dashy login page.

    Use this when you already run something like Authelia, Authentik, Traefik's forwardAuth, Caddy's forward_auth, or nginx's auth_request in front of your services, and you want Dashy to pick up the same session.

    Contents

    @@ -72,6 +72,6 @@
  • From there it's normal Dashy auth: isLoggedIn, the admin check, and the per-page, section and item visibility rules all work as usual
  • Server-side, the same proxy-whitelist middleware guards the config and API routes, so a request from an untrusted IP is rejected before it reaches anything. Writes (saving config) additionally require the matched user to be type: admin.

    -

    The relevant code is src/utils/auth/HeaderAuth.js on the frontend, and services/app.js with services/get-user.js on the server.

    +

    The relevant code is src/utils/auth/HeaderAuth.js on the frontend, and services/app.js with services/get-user.js on the server.

    \ No newline at end of file diff --git a/docs/authentication/index.html b/docs/authentication/index.html index 2b0935b8..27a80600 100644 --- a/docs/authentication/index.html +++ b/docs/authentication/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Authentication

    +

    Authentication

    • Built-In Auth
    • Keycloak
    • @@ -250,6 +250,6 @@ Never expose your Dashy instance to the public internet or untrusted users witho
      MethodTypeDescriptionComplexitySecurityBest for
      No AuthBuilt-inThis is the default state Dashy ships with🟢 Easy🔴 WeakInternal usage
      Built-In AuthBuilt-inUsername/password list in your config, optionally enforced server-side🟢 Easy🟠 MediumA quick login screen on a trusted LAN
      Header AuthBuilt-inTrusts a username header from a proxy that already did the login🟠 Medium🟠 MediumReusing an existing proxy or forward-auth session
      OIDC (generic)OIDCAny OpenID Connect provider, with server-side token checks and admin roles🟠 Medium🟢 StrongStandards-based SSO with any IdP
      AuthentikOIDCSelf-hosted IdP with a full admin UI, MFA and group policies🟠 Medium🟢 StrongOne login across many self-hosted apps
      AutheliaOIDCLightweight self-hosted IdP, configured from a single YAML file🟠 Medium🟢 StrongSelf-hosters who prefer file-based config
      KeycloakOIDCHeavyweight enterprise IdP with realms, roles and social login🔴 Hard🟢 StrongLarger or enterprise deployments
      Pocket IDOIDCMinimal passkey-only IdP, a single Go binary🟠 Medium🟢 StrongPasswordless homelab SSO
      ZitadelOIDCGo/Postgres IdP with project roles (needs an Action to map groups)🔴 Hard🟢 StrongRole-based access across projects
      Cloudflare TunnelTunnelOutbound tunnel to Cloudflare's edge, paired with Access for login🟠 Medium🟢 StrongPublic access with no open ports
      Tailscale / HeadscaleTunnelPrivate WireGuard mesh, with optional Funnel for public access🟢 Easy🟢 StrongPrivate remote access between your devices
      Reverse Proxy AuthProxyAn auth server (Authelia, Authentik, OAuth2 Proxy) in front via forward-auth🟠 Medium🟢 StrongProtecting many apps behind one proxy
      Web Server AuthProxyHTTP basic auth handled by your reverse proxy🟢 Easy🟠 MediumA fast password prompt over HTTPS
      Client Certificates (mTLS)ProxyRequire a client TLS certificate to connect, enforced at the proxy🔴 Hard🟢 StrongA small fixed set of trusted devices
      IP-Based AccessNetworkAllow only certain source IPs at the web server🟢 Easy🟠 MediumAn extra layer on a static IP or VPN
      VPNNetworkKeep Dashy off the internet, reach it over WireGuard, Tailscale or OpenVPN🟠 Medium🟢 StrongPrivate access with zero public exposure
      SSO / OAuth ProvidersOIDCCloud IdPs (Auth0, Okta, Google) wired in through Dashy's OIDC🟠 Medium🟢 StrongOffloading identity to a managed provider
      Cloud Hosting ProvidersPlatformPlatform-level auth (Cloudflare Access, Netlify, Vercel) outside Dashy🟢 Easy🟢 StrongDashboards hosted on a cloud platform
      -

      ⬆️ Back to Top

    +

    ⬆️ Back to Top

    \ No newline at end of file diff --git a/docs/authentication/keycloak/index.html b/docs/authentication/keycloak/index.html index 5480e7cb..e38b0073 100644 --- a/docs/authentication/keycloak/index.html +++ b/docs/authentication/keycloak/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Keycloak

    +
    +
    \ No newline at end of file diff --git a/docs/authentication/oidc/index.html b/docs/authentication/oidc/index.html index 7448a042..d339e1a7 100644 --- a/docs/authentication/oidc/index.html +++ b/docs/authentication/oidc/index.html @@ -11,15 +11,15 @@ - - - + + + -

    OIDC

    +

    OIDC

    Step-by-step Guides

    We have some provider-specific guides to walk you through configuring OIDC authentication with common services

      @@ -62,6 +62,6 @@
    • If a silent renewal fails for any reason (no refresh token issued, refresh token expired or revoked, the provider returns no fresh id_token, a provider error), Dashy falls back to the normal interactive sign-in. Renewal can save a round-trip, but the interactive flow always remains the safety net.
    • Renewal is driven by the access token's lifetime. If your provider issues an id_token with a much shorter lifetime than the access token, renewal may lag; keeping the two lifetimes equal (the common default) works best.
    • With multiple tabs open against a provider that rotates refresh tokens on use, tabs can briefly contend for the refresh token; the affected tab simply falls back to interactive sign-in. This is inherent to browser-based refresh tokens, not specific to Dashy.
    • -
    +
    \ No newline at end of file diff --git a/docs/authentication/other-auth-methods/index.html b/docs/authentication/other-auth-methods/index.html index 1cfb5902..d4d26782 100644 --- a/docs/authentication/other-auth-methods/index.html +++ b/docs/authentication/other-auth-methods/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Other Auth Methods

    +

    Other Auth Methods

    If OIDC or our built-in auth doesn't suit your needs, there's plenty of other ways of protecting your dashboard from unauthenticated access.

    +

    If you're running Dashy on a cloud platform, most have their own auth options you can enable without touching Dashy's config. See your provider's docs: Cloudflare Access, Netlify Password Protection, AWS Cognito, Azure App Service Authentication, and Vercel Password Protection.

    \ No newline at end of file diff --git a/docs/authentication/pocketid/index.html b/docs/authentication/pocketid/index.html index 764b2ea3..ac172060 100644 --- a/docs/authentication/pocketid/index.html +++ b/docs/authentication/pocketid/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Pocket ID OIDC

    +

    Pocket ID OIDC

    Dashy supports using Pocket ID as its OIDC provider.

    Pocket ID is a small open source identity provider built around passkeys. It's a single Go binary backed by SQLite (or Postgres if you want), no password storage, no email server needed for basic use. Popular in homelab setups where you want SSO across a handful of services without running Authentik or Keycloak.

    Contents

    @@ -208,6 +208,6 @@ Solution: The server reads the auth config only at boot. Restart the Dashy conta
    Server-side request handling
    -
    +
    \ No newline at end of file diff --git a/docs/authentication/tailscale/index.html b/docs/authentication/tailscale/index.html index 3fe2e357..4deae982 100644 --- a/docs/authentication/tailscale/index.html +++ b/docs/authentication/tailscale/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Tailscale

    +

    Tailscale

    Tailscale is a popular way to put Dashy on a private network you can reach from your phone, laptop, or any other device you've added to your tailnet, without exposing it to the public internet. With Tailscale Serve in front, requests reaching Dashy already carry the user's identity in HTTP headers, which plugs straight into Dashy's header auth for auto-login.

    Headscale is a self-hosted control plane that speaks the same protocol. Everything in this guide works with both. There's a short section near the end on what changes when you swap Tailscale's coordination server for your own Headscale instance.

    Contents

    @@ -168,6 +168,6 @@ Solution: The server reads the auth config only at boot. Restart the Dashy conta
    End-to-end authentication flow
    -
    +
    \ No newline at end of file diff --git a/docs/authentication/zitadel/index.html b/docs/authentication/zitadel/index.html index 77071922..0ae90355 100644 --- a/docs/authentication/zitadel/index.html +++ b/docs/authentication/zitadel/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Zitadel OIDC

    +

    Zitadel OIDC

    Dashy supports using Zitadel as its OIDC provider.

    Zitadel is an open source identity and access platform written in Go and backed by Postgres or CockroachDB. It speaks OIDC, OAuth 2.0, and SAML, supports MFA, social login, and per-project role management. The free self-hosted edition is fully featured; there's also a managed cloud option.

    important

    This guide targets Zitadel v3.4.x. Zitadel v4 removed the embedded-JS Actions API used to translate Zitadel's role claim format into a groups claim Dashy understands. The same flow on v4 needs a sidecar webhook target; see the v4 note in the Action step.

    @@ -247,6 +247,6 @@ Solution: Often a stale data/postgres directory left over from a pr
    Server-side request handling
    -
    +
    \ No newline at end of file diff --git a/docs/backup-restore/index.html b/docs/backup-restore/index.html index cb7c7ca6..465390a3 100644 --- a/docs/backup-restore/index.html +++ b/docs/backup-restore/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Cloud Backup and Restore

    +

    Cloud Backup and Restore

    Beyond the cloud backup/restore service, there are several other self-hosted options you can use to backup Dashy, and any other Docker container data. These are outlined in the Management docs, at: Docker Backup Options.

    Dashy has a built-in feature for securely backing up your config to a hosted cloud service, and then restoring it on another instance. This feature is totally optional, and if you do not enable it, then Dashy will not make any external network requests.

    This is useful not only for backing up your configuration off-site, but it also enables Dashy to be used without having write a YAML config file, and makes it possible to use a public hosted instance, without the need to self-host.

    @@ -100,6 +100,6 @@

    For more info, see the API Docs.

    -

    If you are using Postman, you may find this pre-made collection helpful in getting things setup.

    +

    If you are using Postman, you may find this pre-made collection helpful in getting things setup.

    \ No newline at end of file diff --git a/docs/configuring/index.html b/docs/configuring/index.html index 18d59287..4b4280b0 100644 --- a/docs/configuring/index.html +++ b/docs/configuring/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Configuring

    +
    +

    ⬆️ Back to Top

    \ No newline at end of file diff --git a/docs/contributing/index.html b/docs/contributing/index.html index 7f409dbb..3e96cbc4 100644 --- a/docs/contributing/index.html +++ b/docs/contributing/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Contributing

    +

    Contributing

    First off, thank you for considering contributing towards Dashy! 🙌 There are several ways that you can help out, and any contributions, however small will always be very much appreciated. You will be appropriately credited in the readme - huge thank you to everyone who has helped so far 💞

    @@ -100,6 +100,6 @@ All content is located either in the Credits Page

    Auto-generated contributors

    Star-Gazers Over Time

    -

    ](https://seladb.github.io/StarTrack-js/#/preload?r=lissy93,dashy)

    +

    ](https://seladb.github.io/StarTrack-js/#/preload?r=lissy93,dashy)

    \ No newline at end of file diff --git a/docs/credits/index.html b/docs/credits/index.html index 6b7ff05f..5941ad95 100644 --- a/docs/credits/index.html +++ b/docs/credits/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Credits

    +

    Credits

    Sponsors

    Sponsors

    Contributors

    @@ -45,6 +45,6 @@

    You

    -

    Would you like to be listed here? Whatever your skill set, Dashy needs people like you to help support future development. Check out the Contributing Page for ways that you can get involved. Huge thank you to everyone who has already contributed! 💖

    +

    Would you like to be listed here? Whatever your skill set, Dashy needs people like you to help support future development. Check out the Contributing Page for ways that you can get involved. Huge thank you to everyone who has already contributed! 💖

    \ No newline at end of file diff --git a/docs/deployment/bare-metal/index.html b/docs/deployment/bare-metal/index.html index 6ffa7c3f..78bcae39 100644 --- a/docs/deployment/bare-metal/index.html +++ b/docs/deployment/bare-metal/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Bare Metal

    +

    Bare Metal

    Build from Source

    Dashy can be compiled and run directly on your machine.
    For this, you will need both git and Node.js (v20 or newer) installed.

    @@ -84,6 +84,6 @@ It creates a dashy user, installs into /opt/dashy, ver
    #!/usr/bin/env bash
    set -euo pipefail

    REPO="Lissy93/dashy"
    INSTALL_DIR="${INSTALL_DIR:-/opt/dashy}"
    RUN_USER="${RUN_USER:-dashy}"
    PORT="${PORT:-4000}"
    VERSION="${DASHY_VERSION:-latest}"

    if [ "$(id -u)" -ne 0 ]; then
    echo "Please run this script as root"
    exit 1
    fi

    need_cmd() {
    command -v "$1" >/dev/null 2>&1 || {
    echo "Missing required command: $1"
    exit 1
    }
    }

    install_packages() {
    if command -v apt-get >/dev/null 2>&1; then
    apt-get update
    apt-get install -y curl ca-certificates tar gzip coreutils
    elif command -v dnf >/dev/null 2>&1; then
    dnf install -y curl ca-certificates tar gzip coreutils nodejs
    elif command -v yum >/dev/null 2>&1; then
    yum install -y curl ca-certificates tar gzip coreutils nodejs
    else
    echo "Please install curl, tar, sha256sum and Node.js v20 or newer, then re-run this script."
    exit 1
    fi
    }

    install_packages
    need_cmd curl
    need_cmd tar
    need_cmd sha256sum
    need_cmd runuser

    install_node_apt() {
    curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
    apt-get install -y nodejs
    }

    if ! command -v node >/dev/null 2>&1; then
    if command -v apt-get >/dev/null 2>&1; then
    install_node_apt
    else
    echo "Please install Node.js v20 or newer, then re-run this script."
    exit 1
    fi
    fi

    NODE_MAJOR="$(node -p "process.versions.node.split('.')[0]")"
    if [ "$NODE_MAJOR" -lt 20 ]; then
    if command -v apt-get >/dev/null 2>&1; then
    install_node_apt
    NODE_MAJOR="$(node -p "process.versions.node.split('.')[0]")"
    fi

    if [ "$NODE_MAJOR" -lt 20 ]; then
    echo "Node.js v20 or newer is required. Found: $(node -v)"
    exit 1
    fi
    fi

    corepack enable
    need_cmd yarn

    case "$INSTALL_DIR" in
    /*) ;;
    *) echo "INSTALL_DIR must be an absolute path"; exit 1 ;;
    esac

    if [ "$INSTALL_DIR" = "/" ]; then
    echo "INSTALL_DIR cannot be /"
    exit 1
    fi

    if [ "$VERSION" = "latest" ]; then
    RELEASE_URL="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/${REPO}/releases/latest")"
    VERSION="${RELEASE_URL##*/}"
    fi
    VERSION="${VERSION#v}"

    ASSET="dashy-${VERSION}.tar.gz"
    BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
    TMP_DIR="$(mktemp -d)"
    trap 'rm -rf "$TMP_DIR"' EXIT

    echo "Downloading Dashy ${VERSION}..."
    curl -fL "${BASE_URL}/${ASSET}" -o "${TMP_DIR}/${ASSET}"
    curl -fL "${BASE_URL}/${ASSET}.sha256" -o "${TMP_DIR}/${ASSET}.sha256"

    (
    cd "$TMP_DIR"
    sha256sum -c "${ASSET}.sha256"
    )

    if ! id "$RUN_USER" >/dev/null 2>&1; then
    useradd --system --home "$INSTALL_DIR" --shell /usr/sbin/nologin "$RUN_USER"
    fi

    if [ -d "$INSTALL_DIR/user-data" ]; then
    BACKUP_DIR="${INSTALL_DIR}.user-data.$(date +%Y%m%d%H%M%S)"
    cp -a "$INSTALL_DIR/user-data" "$BACKUP_DIR"
    echo "Backed up user-data to ${BACKUP_DIR}"
    fi

    NEW_DIR="${INSTALL_DIR}.new"
    OLD_DIR="${INSTALL_DIR}.old"

    rm -rf "$NEW_DIR" "$OLD_DIR"
    mkdir -p "$NEW_DIR"
    tar -xzf "${TMP_DIR}/${ASSET}" -C "$NEW_DIR"

    if [ -d "$INSTALL_DIR/user-data" ]; then
    rm -rf "${NEW_DIR}/user-data"
    cp -a "$INSTALL_DIR/user-data" "${NEW_DIR}/user-data"
    fi

    chown -R "$RUN_USER:$RUN_USER" "$NEW_DIR"

    runuser -u "$RUN_USER" -- bash -lc "cd '$NEW_DIR' && yarn install --production --frozen-lockfile"

    if [ -d "$INSTALL_DIR" ]; then
    mv "$INSTALL_DIR" "$OLD_DIR"
    fi

    mv "$NEW_DIR" "$INSTALL_DIR"
    rm -rf "$OLD_DIR"

    cat >/etc/systemd/system/dashy.service <<EOF
    [Unit]
    Description=Dashy
    After=network-online.target
    Wants=network-online.target

    [Service]
    Type=simple
    User=${RUN_USER}
    WorkingDirectory=${INSTALL_DIR}
    Environment=NODE_ENV=production
    Environment=PORT=${PORT}
    ExecStart=/usr/bin/env node server
    Restart=always
    RestartSec=10

    [Install]
    WantedBy=multi-user.target
    EOF

    systemctl daemon-reload
    systemctl enable dashy
    systemctl restart dashy

    echo "Dashy ${VERSION} is running on http://localhost:${PORT}"
    echo "Edit your config at ${INSTALL_DIR}/user-data/conf.yml"

    To install a specific version, run it with DASHY_VERSION=4.3.0. To use a different port, set PORT=8080.

    -
    + \ No newline at end of file diff --git a/docs/deployment/cloud/index.html b/docs/deployment/cloud/index.html index 664cc62b..6a797d4f 100644 --- a/docs/deployment/cloud/index.html +++ b/docs/deployment/cloud/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Cloud Services

    +

    Cloud Services

    Dashy is intended to be run locally, within your LAN/homelab. But it can also be deployed to the cloud for certain use cases.

    Where possible, I've included templates and 1-click install links, so you can deploy your own instance in under a minute.

    important
      @@ -83,6 +83,6 @@

      However without Dashy's node server, there are a couple of features that will be unavailable to you, including: writing config changes to disk through the UI, and application status checks. Everything else will work fine.


      Other VPS

      -

      Dashy can be deployed to almost any VPS provider by following our Docker guide.

    +

    Dashy can be deployed to almost any VPS provider by following our Docker guide.

    \ No newline at end of file diff --git a/docs/deployment/docker/index.html b/docs/deployment/docker/index.html index 1bcbfeef..a830b36b 100644 --- a/docs/deployment/docker/index.html +++ b/docs/deployment/docker/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Docker

    +

    Docker

    Docker is the recommended way of running Dashy. We have light-weight multi-arch (amd64, arm64 and arm/v7) images published to DockerHub (lissy93/dashy) and GHCR (ghcr.io/lissy93/dashy) with full semver tags.

    The container runs on port 8080 (can be overridden with PORT). Your config (conf.yml) and any other assets (icons, styles, themes, fonts, scripts, etc) will live in /app/user-data in the container.

    @@ -81,6 +81,6 @@ We have light-weight multi-arch (amd64, arm64 and arm/v7) images published to Do
  • Auto-Starting at System Boot
  • Verifying Releases
  • Container Security
  • -
    +
    \ No newline at end of file diff --git a/docs/deployment/index.html b/docs/deployment/index.html index 06dd5ea3..ed503e5a 100644 --- a/docs/deployment/index.html +++ b/docs/deployment/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Deployment

    +

    Deployment

    Welcome to Dashy, so glad you're here :) Deployment is super easy, and there are several methods available depending on what type of system you're using. If you're self-hosting, then deploying with Docker (or similar container engine) is the recommended approach.

    Quick Start

    If you want to skip the fuss, and get straight down to it, then you can spin up a new instance of Dashy by running:

    @@ -161,6 +161,6 @@ -
    BrowserMinimum VersionStatus
    Chrome / Chromium90+Fully supported
    Firefox90+Fully supported
    Edge90+Fully supported
    Safari14+Mostly Supported
    Opera76+Supported
    Samsung Internet15+Supported
    Firefox ESRLatestSupported
    Internet Explorer-Not supported
    +
    BrowserMinimum VersionStatus
    Chrome / Chromium90+Fully supported
    Firefox90+Fully supported
    Edge90+Fully supported
    Safari14+Mostly Supported
    Opera76+Supported
    Samsung Internet15+Supported
    Firefox ESRLatestSupported
    Internet Explorer-Not supported
    \ No newline at end of file diff --git a/docs/deployment/self-hosted-os/index.html b/docs/deployment/self-hosted-os/index.html index 9bae6cd6..28439235 100644 --- a/docs/deployment/self-hosted-os/index.html +++ b/docs/deployment/self-hosted-os/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Self-Hosted Operating Systems

    +

    Self-Hosted Operating Systems

    The following lists third-party/community-maintained apps and scripts for deploying Dashy on various device types. Note that these are unofficial. Please ensure you're using a recent version of Dashy (4.x.x +) before opening issues here.


    @@ -57,6 +57,6 @@ Note that these are unofficial. Please ensure you're using a recent version
    caution

    This integration may need re-review, to ensure that it's using / compatible with Dashy 4+

    This uses the Helm chart written by @vyrtualsynthese, which is available here

    # Add the self-hosted repo
    helm repo add self-hosted https://vyrtualsynthese.github.io/selfhosted-helmcharts/
    helm repo update

    # Install the dashy chart
    helm install [RELEASE_NAME] self-hosted/dashy
    -

    TrueCharts also publishes a Dashy Helm chart, aimed at Kubernetes and TrueNAS SCALE.

    +

    TrueCharts also publishes a Dashy Helm chart, aimed at Kubernetes and TrueNAS SCALE.

    \ No newline at end of file diff --git a/docs/deployment/self-hosted-platforms/index.html b/docs/deployment/self-hosted-platforms/index.html index eac0a767..d76e5c33 100644 --- a/docs/deployment/self-hosted-platforms/index.html +++ b/docs/deployment/self-hosted-platforms/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Self-Hosted Platforms

    +

    Self-Hosted Platforms

    Dashy has ready-made apps, templates, and one-click installs for a range of self-hosted control panels and home-server platforms. To deploy onto a specific operating system or NAS instead, see Self-Hosted Operating Systems.

    note
    • Please ensure the app you install is pinned to a recent or latest version of Dashy (e.g. 4.x.x upwards).
    • @@ -90,6 +90,6 @@

      Saltbox

      Saltbox (an Ansible-based server automation project) includes Dashy as a sandbox app. Once Saltbox is set up, install it with:

      sb install sandbox-dashy
      -

      See the Saltbox Dashy docs for details.

    +

    See the Saltbox Dashy docs for details.

    \ No newline at end of file diff --git a/docs/developing/index.html b/docs/developing/index.html index 8e6797dd..c6ebbb90 100644 --- a/docs/developing/index.html +++ b/docs/developing/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Developing

    +

    Developing

    This article outlines how to get Dashy running in a development environment, and outlines the basics of the architecture. If you're adding new features, you may want to check out the Development Guides docs, for tutorials covering basic tasks.

      @@ -215,6 +215,6 @@ For example, FEATURE/420_Awesome-feature or FIX/690_login-ser

      The easiest method of checking performance is to use Chromium's build in auditing tool, Lighthouse. To run the test, open Developer Tools (usually F12) --> Lighthouse and click on the 'Generate Report' button at the bottom.

      Dependencies - BundlePhobia

      BundlePhobia is a really useful app that lets you analyze the cost of adding any particular dependency to an application

      -

      WARN asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). - For the PWA to support Windows 10, a splash screen asset is required, and is quite large. This throws a warning, however PWA assets are not loaded until needed, so shouldn't have any impact on application performance. A similar warning is thrown for the Raleway font, and that is looking to be addressed.

    +

    WARN asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). - For the PWA to support Windows 10, a splash screen asset is required, and is quite large. This throws a warning, however PWA assets are not loaded until needed, so shouldn't have any impact on application performance. A similar warning is thrown for the Raleway font, and that is looking to be addressed.

    \ No newline at end of file diff --git a/docs/development-guides/index.html b/docs/development-guides/index.html index 606ab2dc..f718bb61 100644 --- a/docs/development-guides/index.html +++ b/docs/development-guides/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Development Guides

    +

    Development Guides

    A series of short tutorials, to guide you through the most common development tasks.

    Sections:

      @@ -207,6 +207,6 @@ First create a new computed property, like:

      If required, add a message showing that the component isn't available, using the AccessError component. E.g.

      import AccessError from '@/components/Configuration/AccessError';
      <AccessError v-else />
      -

      The $store.getters.permissions object also returns options for when and where config can be saved, using: allowWriteToDisk, and allowSaveLocally - both are booleans.

    +

    The $store.getters.permissions object also returns options for when and where config can be saved, using: allowWriteToDisk, and allowSaveLocally - both are booleans.

    \ No newline at end of file diff --git a/docs/icons/index.html b/docs/icons/index.html index fbeb5942..29e8d23e 100644 --- a/docs/icons/index.html +++ b/docs/icons/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Icons

    +

    Icons

    Both sections and items can have an icon, which is specified using the icon attribute. Using icons improves the aesthetics of your UI and makes the app more intuitive to use. Dashy supports multiple different icon providers, usage instructions for which are explained here.

    • @@ -147,6 +147,6 @@

      Notes

      If you are using icons from an external source, these will be fetched on initial page load automatically, if and when needed. But combining icons from multiple services may have a negative impact on performance.

      You can improve load speeds, by downloading your required icons, and serving them locally. Scaling icons to the minimum required dimensions (e.g. 128x128 or 64x64) will also greatly improve application load times.

      -

      For icons from external sources, please see the Privacy Policies and Licenses for that provider.

    +

    For icons from external sources, please see the Privacy Policies and Licenses for that provider.

    \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 5a93be2a..df7d5e72 100644 --- a/docs/index.html +++ b/docs/index.html @@ -11,14 +11,14 @@ - - - + + + -
    +
    \ No newline at end of file diff --git a/docs/license/index.html b/docs/license/index.html index 8d627de7..de355dac 100644 --- a/docs/license/index.html +++ b/docs/license/index.html @@ -11,15 +11,15 @@ - - - + + + -

    License

    +

    License

    MIT License

    Copyright (c) 2021 Alicia Sykes

    Permission is hereby granted, free of charge, to any person obtaining a copy @@ -38,6 +38,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


    -

    For more information about the MIT License, see: https://opensource.org/licenses/MIT

    +

    For more information about the MIT License, see: https://opensource.org/licenses/MIT

    \ No newline at end of file diff --git a/docs/management/index.html b/docs/management/index.html index d7c03185..6677b31a 100644 --- a/docs/management/index.html +++ b/docs/management/index.html @@ -11,15 +11,15 @@ - - - + + + -

    App Management

    +

    App Management

    The following article is a primer on managing self-hosted apps. It covers everything from keeping the Dashy (or any other app) up-to-date, secure, backed up, to other topics like auto-starting, monitoring, log management, web server configuration and using custom domains.

    Contents

      @@ -531,6 +531,6 @@ For example: scp -r ./dist/* [username]@[server_ip]:/var/www/dashy/htmlYou may wish to upload your image to a container registry for easier access. Note that if you choose to do this on a public registry, please name your container something other than just 'dashy', to avoid confusion with the official image. You can push your build image, by running: docker push ghcr.io/OWNER/IMAGE_NAME:latest. You will first need to authenticate, this can be done by running echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin, where CR_PAT is an environmental variable containing a token generated from your GitHub account. For more info, see the Container Registry Docs.

      ⬆️ Back to Top

      -
    +
    \ No newline at end of file diff --git a/docs/multi-language-support/index.html b/docs/multi-language-support/index.html index f4c21990..a51add8f 100644 --- a/docs/multi-language-support/index.html +++ b/docs/multi-language-support/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Internationalization

    +

    Internationalization

    Internationalization is the process of making an application available in other languages. This is important, as not everyone is a native English speaker. This page explains how you can switch languages, how to add a new language, and how to make text translatable when writing a new component.

    • Setting your Language
    • @@ -92,6 +92,6 @@ For example: alert(this.$t('my-widget.awesome-text')).

      In ./src/components/Settings/SearchBar.vue:

      <template>
      <form>
      <label for="search-input">{{ $t('search.search-label') }}</label>
      <input
      v-model="searchValue"
      :placeholder="$t('search.search-placeholder')"
      />
      </form>
      </template>

      Then in ./src/assets/locales/en.json:

      -
      {
      "search": {
      "search-label": "Search",
      "search-placeholder": "Start typing to filter",
      },
      ...
      }
    +
    {
    "search": {
    "search-label": "Search",
    "search-placeholder": "Start typing to filter",
    },
    ...
    }
    \ No newline at end of file diff --git a/docs/pages-and-sections/index.html b/docs/pages-and-sections/index.html index 91c46342..92a40413 100644 --- a/docs/pages-and-sections/index.html +++ b/docs/pages-and-sections/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Pages and Sections

    +

    Pages and Sections

    Page Metadata

    Set your dashboard's branding under pageInfo

    pageInfo:
    title: My Dashboard # Used for main h1 title, and browser tab text
    description: Home server links
    logo: /web-icons/my-logo.png # path/URL to optional logo to display next to title
    favicon: 'https://example.com/path/to/icon' # path/URL to a favicon (shows in browser tab)
    color: '#2a7cf0' # Hex color, to set the browser/address bar color on mobile (supported browsers only)
    footer: '© 2026 Me' # Optional text or HTML content, to display in the pages footer
    @@ -83,6 +83,6 @@ The config file must, of course be accessible from within Dashy. If your config
    - name: Coding
    icon: far fa-code
    items:
    - title: GitHub
    url: https://github.com/
    - title: StackOverflow
    url: http://stackoverflow.com/

    But items can also be grouped together, referred to as sub-items. This is useful for a group of less frequently used items, which you don't want to take up too much space.

    Item groups may also have an optional title.

    -
    - name: Coding
    icon: far fa-code
    items:
    - title: Normal Item 1
    - title: Normal Item 2
    - title: Languages
    subItems:
    - title: JavaScript
    url: https://developer.mozilla.org
    icon: si-javascript
    - title: TypeScript
    url: https://www.typescriptlang.org/docs
    icon: si-typescript
    - title: Svelte
    url: https://svelte.dev/docs
    icon: si-svelte
    - title: Go
    url: https://go.dev/doc
    icon: si-go
    +
    - name: Coding
    icon: far fa-code
    items:
    - title: Normal Item 1
    - title: Normal Item 2
    - title: Languages
    subItems:
    - title: JavaScript
    url: https://developer.mozilla.org
    icon: si-javascript
    - title: TypeScript
    url: https://www.typescriptlang.org/docs
    icon: si-typescript
    - title: Svelte
    url: https://svelte.dev/docs
    icon: si-svelte
    - title: Go
    url: https://go.dev/doc
    icon: si-go
    \ No newline at end of file diff --git a/docs/privacy/index.html b/docs/privacy/index.html index e77cf5ed..dbe92332 100644 --- a/docs/privacy/index.html +++ b/docs/privacy/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Privacy & Security

    +

    Privacy & Security

    Dashy was built with privacy in mind. Self-hosting your own apps and services is a great way to protect yourself from the mass data collection employed by big tech companies, and Dashy was designed to make self-hosting easier.

    Dashy operates on the premise, that:

    @@ -539,6 +539,6 @@ Dashy can use local storage to keep track of your preferences.


    Reporting a Security Issue

    Please see our Security.md doc for how to report issues. -We have an actively monitored security mailbox supporting PGP, as well as a GitHub Advisories vulnerability reporting program.

    +We have an actively monitored security mailbox supporting PGP, as well as a GitHub Advisories vulnerability reporting program.

    \ No newline at end of file diff --git a/docs/quick-start/index.html b/docs/quick-start/index.html index 18f06c65..1ffd3943 100644 --- a/docs/quick-start/index.html +++ b/docs/quick-start/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Quick Start

    + +
    \ No newline at end of file diff --git a/docs/release-workflow/index.html b/docs/release-workflow/index.html index a994a6c7..7a838cef 100644 --- a/docs/release-workflow/index.html +++ b/docs/release-workflow/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Releases and Workflows

    +

    Releases and Workflows

    This document outlines our release schedule, workflows/automations, repository conventions and standards

    @@ -354,6 +354,6 @@ E.g. feat/adds-awesome-feature, ref/language-deduplicationCreate a Pull Request, filling in the template
  • Follow up with any reviews on your code
  • Merge 🎉
  • -
    +
    \ No newline at end of file diff --git a/docs/searching/index.html b/docs/searching/index.html index 82f59f17..8b25d275 100644 --- a/docs/searching/index.html +++ b/docs/searching/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Search & Shortcuts

    +

    Search & Shortcuts

    Searching

    One of the primary purposes of Dashy is to allow you to quickly find and launch a given app. To make this as quick as possible, there is no need to touch the mouse, or press a certain key to begin searching - just start typing. Results will be filtered in real-time. No need to worry about case, special characters or small typos, these are taken care of, and your results should appear.

    @@ -78,6 +78,6 @@
    appConfig:
    webSearch:
    openUrlsDirectly: true

    You can clear your search term at any time, resting the UI to it's initial state, by pressing Esc. -This can also be used to close any open pop-up modals.

    +This can also be used to close any open pop-up modals.

    \ No newline at end of file diff --git a/docs/showcase/index.html b/docs/showcase/index.html index fe716fe2..027cf4e9 100644 --- a/docs/showcase/index.html +++ b/docs/showcase/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Dashy Showcase 🌟

    +

    Dashy Showcase 🌟

    @@ -215,6 +215,6 @@ Dashy is awesome!

    Template

    If you're submitting a pull request, please use a format similar to this:

    -
    ### [Dashboard Name] (required)

    > Submitted by [@username](https://github.com/user) (optional)

    [An optional text description, or any interesting details] (optional)

    ![dashboard-screenshot](https://example.com/url-to-screenshot.png) (required)

    ---

    +
    ### [Dashboard Name] (required)

    > Submitted by [@username](https://github.com/user) (optional)

    [An optional text description, or any interesting details] (optional)

    ![dashboard-screenshot](https://example.com/url-to-screenshot.png) (required)

    ---

    \ No newline at end of file diff --git a/docs/status-indicators/index.html b/docs/status-indicators/index.html index 5394d58f..932e270b 100644 --- a/docs/status-indicators/index.html +++ b/docs/status-indicators/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Status Indicators

    +

    Status Indicators

    Dashy has an optional feature that can display a small icon next to each of your running service or host, indicating it's current status (operation or online). This can be useful if you are using Dashy as your homelab's start page, as it gives you an overview of the health of each of your running services and hosts. The status and ping features will show response time, response code, online / offline check and if applicable, a relevant error message.

    Enabling Status Indicators

    @@ -65,6 +65,6 @@ For example, statusCheckHeaders: { 'X-Custom-Header': 'foob

    How it Works

    When the app is loaded, if appConfig.statusCheck: true or appConfig.pingCheckEnabled: true are set, or if any items have the statusCheck: true or pingCheckEnabled: true enabled, then Dashy will make a request, to https://[your-host-name]/status-check?&url=[address-or-servce] or https://[your-host-name]/ping-check?&host=[hostname-or-ip] (may also include GET params for headers and the secure flag for status check or ping count and timeout GET params for ping checks), which in turn will ping that running service or host, and respond with a status code. Response time is calculated from the difference between start and end time of the request for the status check of on the return of the ping command for the ping check.

    When the response completes, an indicator will display next to each item. The color denotes the status: Yellow while waiting for the response to return, green if request was successful, red if it failed, and grey if it was unable to make the request all together.

    -

    All requests are made straight from your server, there is no intermediary. So providing you are hosting Dashy yourself, and are checking the status of other self-hosted services, there shouldn't be any privacy concerns. Requests are made asynchronously, so this won't have any significant impact on page load speeds. However recurring requests (using statusCheckInterval or pingCheckInterval) may run more slowly if the interval between requests is very short.

    +

    All requests are made straight from your server, there is no intermediary. So providing you are hosting Dashy yourself, and are checking the status of other self-hosted services, there shouldn't be any privacy concerns. Requests are made asynchronously, so this won't have any significant impact on page load speeds. However recurring requests (using statusCheckInterval or pingCheckInterval) may run more slowly if the interval between requests is very short.

    \ No newline at end of file diff --git a/docs/theming/index.html b/docs/theming/index.html index 187dae7b..3480b6d8 100644 --- a/docs/theming/index.html +++ b/docs/theming/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Theming

    +
    +
    \ No newline at end of file diff --git a/docs/troubleshooting/index.html b/docs/troubleshooting/index.html index e50165bd..02b032e9 100644 --- a/docs/troubleshooting/index.html +++ b/docs/troubleshooting/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Troubleshooting

    +

    Troubleshooting

    This document contains common problems and their solutions.
    Please ensure your issue isn't listed here, before opening a new ticket.

    @@ -608,6 +608,6 @@ You will be notified on your ticket, when a fix has been released.

  • git config --global user.email johndoe@example.com
  • For more info, see Git First Time Setup Docs.

    -

    Note that only contributions to the master / main branch or a project are counted

    +

    Note that only contributions to the master / main branch or a project are counted

    \ No newline at end of file diff --git a/docs/widgets/index.html b/docs/widgets/index.html index 13fe250c..eed16b33 100644 --- a/docs/widgets/index.html +++ b/docs/widgets/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Widgets

    +

    Widgets

    Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets available out-of-the-box as well as support for custom widgets to display stats from almost any service with an API.

    Contents

    @@ -3944,9 +3945,35 @@ You'll need to enable the sensors plugin to use this widget, using: -

    Example

    - type: gl-cpu-temp
    options:
    hostname: http://192.168.130.2:61208
    units: C

    +

    GPU

    +

    Displays usage, memory and temperature for each NVIDIA GPU, plus fan speed where available.

    +

    Note: This widget uses the gpu plugin, which requires the py3nvml library and is limited to NVIDIA chipsets.

    +

    +

    Options

    + + + + + + + + + + + + + + + + + +
    FieldTypeRequiredDescription
    unitsstringOptionalUse C to display temperatures in Celsius or F to use Fahrenheit. Defaults to C.
    +

    Example

    +
    - type: gl-gpu
    options:
    hostname: http://192.168.130.2:61208
    units: C
    +

    Compact Metrics

    A multi-system overview widget that displays CPU, memory and disk usage for multiple Glances instances in a compact table. Click on a row to see detailed metrics for that system.

    -

    Options

    +

    Options

    @@ -3983,14 +4010,14 @@ You'll need to enable the sensors plugin to use this widget, using: -
    FieldTypeRequiredDescription
    systemsarrayRequiredAn array of systems to monitor, each with header (display name) and url (Glances base URL)
    apiVersionnumberOptionalGlances API version. Defaults to 4
    usernamestringOptionalIf Glances is password-protected, specify the username
    passwordstringOptionalIf Glances is password-protected, specify the password
    -

    Example

    +

    Example

    - type: gl-compact-metrics
    options:
    systems:
    - header: Server 1
    url: http://192.168.1.10:61208
    - header: Server 2
    url: http://192.168.1.11:61208
    - header: NAS
    url: http://192.168.1.20:61208

    Dynamic Widgets

    Iframe Widget

    Embed any webpage into your dashboard as a widget.

    -

    Options

    +

    Options

    @@ -4015,14 +4042,14 @@ You'll need to enable the sensors plugin to use this widget, using: -
    FieldTypeRequiredDescription
    urlstringRequiredThe URL to the webpage to embed
    frameHeightnumberOptionalIf needed, specify height of iframe in px. E.g. 400, defaults to auto
    -

    Example

    +

    Example

    - type: iframe
    options:
    url: https://fiatleak.com/

    HTML Embedded Widget

    Many websites and apps provide their own embeddable widgets. These can be used with Dashy using the Embed widget, which lets you dynamically embed and HTML, CSS or JavaScript contents.

    ⚠️ NOTE: Use with extreme caution. Embedding a script from an untrustworthy source may have serious unintended consequences.

    -

    Options

    +

    Options

    @@ -4059,7 +4086,7 @@ You'll need to enable the sensors plugin to use this widget, using: -
    FieldTypeRequiredDescription
    htmlstringOptionalHTML contents to render in the widget
    scriptstringOptionalRaw JavaScript code to execute (caution)
    scriptSrcstringOptionalA URL to JavaScript content (caution)
    cssstringOptionalAny stylings for widget contents
    -

    Example

    +

    Example

    - type: embed
    options:
    scriptSrc: https://cdn.speedcheck.org/basic/scbjs.min.js
    html: |
    <div id="sc-container">
    <div id="sc-branding" class="sc-bb">
    <a target="_blank" href="https://www.speedcheck.org/">
    <img src="https://cdn.speedcheck.org/branding/speedcheck-logo-18.png" alt="Speedcheck"/>
    </a>
    </div>
    </div>

    Or

    - type: embed
    options:
    css: '.coinmarketcap-currency-widget { color: var(--widget-text-color); }'
    html: '<div class="coinmarketcap-currency-widget" data-currencyid="1" data-base="USD" data-secondary="" data-ticker="true" data-rank="true" data-marketcap="true" data-volume="true" data-statsticker="true" data-stats="USD"></div>'
    scriptSrc: 'https://files.coinmarketcap.com/static/widget/currency.js'
    @@ -4069,7 +4096,7 @@ You'll need to enable the sensors plugin to use this widget, using: -

    API Response

    Display data from any HTTP/JSON API endpoint. Fetch a URL, then declaratively map response fields to labelled, formatted values. This was very much inspired by, and is broadly compatible with Homepage's customapi widget

    -

    Options

    +

    Options

    @@ -4209,7 +4236,7 @@ This was very much inspired by, and is broadly compatible with size returns the number of items in an array (or keys in an object) — pair it with an omitted field to count the response root
  • For APIs that don't send CORS headers (most self-hosted services), set the widget-level useProxy: true to route the request through Dashy's server-side proxy
  • -

    Example

    +

    Example

    - type: customapi
    options:
    url: https://api.github.com/repos/lissy93/dashy
    refreshInterval: 60000
    mappings:
    - field: stargazers_count
    label: Stars
    format: number
    - field: open_issues_count
    label: Open Issues
    format: number
    - field: pushed_at
    label: Last Push
    format: relativeDate

    Info

      @@ -4225,7 +4252,7 @@ This was very much inspired by, and is broadly compatible with Data Feed

      Show live data from an RSS-enabled service. The only required parameter is rssUrl, which is the URL to the ATOM feed. See RSS Widget for full list of available options.

      -

      Example

      +

      Example

      - type: rss-feed
      options:
      rssUrl: https://notes.aliciasykes.com/feed

      Usage & Customizations

      @@ -4344,6 +4371,6 @@ Add the Access-Control-Allow-Origin header, with the value of eithe

      For testing purposes, you can use an addon, which will disable the CORS checks. You can get the Allow-CORS extension for Chrome or Firefox, more details here


      Raising an Issue

      -

      If you need to submit a bug report for a failing widget, then please include the full console output (see how) as well as the relevant parts of your config file. Before sending the request, ensure you've read the docs. If you're new to GitHub, an haven't previously contributed to the project, then please fist star the repo to avoid your ticket being closed by the anti-spam bot.

    +

    If you need to submit a bug report for a failing widget, then please include the full console output (see how) as well as the relevant parts of your config file. Before sending the request, ensure you've read the docs. If you're new to GitHub, an haven't previously contributed to the project, then please fist star the repo to avoid your ticket being closed by the anti-spam bot.

    \ No newline at end of file diff --git a/index.html b/index.html index 019c830c..e9cfee23 100644 --- a/index.html +++ b/index.html @@ -11,14 +11,14 @@ - - - + + + -

    Dashy

    The Ultimate Homepage for your Homelab

    Dashy is an open source, highly customizable, easy to use, privacy-respecting dashboard app.Keep Reading...
    211Contributors
    25.7kGitHub Stars
    17.4MDocker Downloads
    5MDoc Site Views

    Theming

    With tons of built-in themes to choose form, plus a UI color palette editor, you can have a unique looking dashboard in no time. There is also support for custom CSS, and since all properties use CSS variables, it is easy to override.
    Learn more in the Docs Docs
    Demo of Theming feature in Dashy

    Icons

    Dashy can auto-fetch icons from the favicon of each of your apps/ services. There is also native support for Font Awesome, Material Design Icons, emoji icons and of course normal images.
    Learn more in the Docs Docs
    Demo of Icons feature in Dashy

    Status Indicators

    Get an instant overview of the health of each of your apps with status indicators. Once enabled, a small dot next to each app will show weather it is up and online, with more info like response time visible on hover.
    Learn more in the Docs Docs
    Demo of Status Indicators feature in Dashy

    Authentication

    Need to protect your dashboard, the simple auth feature is super quick to enable, and has support for multiple users with granular controls. Dashy also has built-in support for Keycloak and other SSO providers.
    Learn more in the Docs Docs
    Screenshot Coming Soon

    Widgets

    Display dynamic content from any API-enabled service. Dashy comes bundled with 50+ pre-built widgets for self-hosted services, productivity and monitoring.
    Learn more in the Docs Docs
    Demo of Widgets feature in Dashy

    Alternate Views

    As well as the default home, there is also a minimal view, which makes a great fast-loading browser startpage. Plus a workspace view useful for working on multiple apps at once, all without having to leave your dashboard.
    Learn more in the Docs Docs
    Demo of Alternate Views feature in Dashy

    Launching Methods

    Choose how to launch each of your apps by default, or right click for all options. Apps can be opened in a new tab, the same tab, a quick pop-up modal or in the workspace view.
    Learn more in the Docs Docs
    Demo of Launching Methods feature in Dashy

    Search & Shortcuts

    To search, just start typing, results will be filtered instantly. Use the arrow keys or tab to navigate through results, and press enter to launch. You can also create custom shortcuts for frequently used apps, or add custom tags for easier searching. Dashy can also be used to search the web using your favorite search engine.
    Learn more in the Docs Docs
    Demo of Search & Shortcuts feature in Dashy

    Cloud Backup & Sync

    There is an optional, end-to-end encrypted, free backup cloud service. This enables you to have your config backed up off-site, and to sync data between multiple instances easily.
    Learn more in the Docs Docs
    Screenshot Coming Soon

    Configuration

    Dashy's config is specified in a simple YAML file. But you can also configure the directly through the UI, and have changes written to, and backed up on disk. Real-time validation and hints are in place to help you.
    Learn more in the Docs Docs
    Demo of Configuration feature in Dashy

    Multi-Language Support

    Dashy's UI has been translated into several languages by several amazing contributors. Currently English, German, French, Dutch and Slovenian are supported. Your language should be applied automatically, or you can change it in the config menu.
    Learn more in the Docs Docs
    Screenshot Coming Soon

    Easy Deployment

    Although Dashy can be easily run on bare metal, the quickest method of getting started is with Docker. Just run `docker run -p 8080:80 lissy93/dashy` to pull, build and and run Dashy.
    Learn more in the Docs Docs
    Screenshot Coming Soon

    Customizable Layouts

    Structure your dashboard to fit your use case. From the UI, you can choose between different layouts, item sizes, show/ hide components, switch themes plus more. You can customize pretty much every area of your dashboard. There are config options for custom header, footer, nav bar links, title etc. You can also choose to hide any elements you don't need.
    Learn more in the Docs Docs
    Screenshot Coming Soon

    Powered by You

    Support us

    If you've found Dashy useful, consider contributing, or dropping us a star!

    Author

    Dashy was initially built by me, Alicia Sykes (@Lissy93 on GitHub), I build free and open source apps, focused on security, privacy, Linux and self-hosting.

    Sponsors

    Huge thanks to the following sponsors for supporting Dashy's development 💖

    Contributors

    Dashy was made possible, thanks to these wonderful contributors 🩵

    lissy93's avatarlissy93 (2963)liss-bot's avatarliss-bot (712)aspenyang's avataraspenyang (41)CrazyWolf13's avatarCrazyWolf13 (40)marekful's avatarmarekful (27)EVOTk's avatarEVOTk (24)snyk-bot's avatarsnyk-bot (24)azerioxal's avatarazerioxal (22)m42e's avatarm42e (19)pinarruiz's avatarpinarruiz (16)casmbu's avatarcasmbu (14)ricardodemauro's avatarricardodemauro imjimmeh's avatarimjimmeh kashif-se's avatarkashif-se lammersbjorn's avatarlammersbjorn conlan0's avatarconlan0 josuablejeru's avatarjosuablejeru ThrustVector's avatarThrustVector zcq100's avatarzcq100 albcp's avataralbcp Cereal916's avatarCereal916 lordpansar's avatarlordpansar jammo2k5's avatarjammo2k5 evroon's avatarevroon alayham's avataralayham alhazmy13's avataralhazmy13 altearius's avataraltearius LinuxSBC's avatarLinuxSBC UrekD's avatarUrekD shumittaher's avatarshumittaher hockwill's avatarhockwill z3r0l1nk's avatarz3r0l1nk wozboz's avatarwozboz he0119's avatarhe0119 dkadioglu's avatardkadioglu Totto16's avatarTotto16 toddejohnson's avatartoddejohnson remygrandin's avatarremygrandin dasunsrule32's avatardasunsrule32 aviolaris's avataraviolaris Tracreed's avatarTracreed rubenandre's avatarrubenandre rtm516's avatarrtm516 kayedspace's avatarkayedspace onedr0p's avataronedr0p stanly0726's avatarstanly0726 kt-alt's avatarkt-alt Bogyie's avatarBogyie webysther's avatarwebysther jtuzar's avatarjtuzar apgyorfi's avatarapgyorfi lucolvin's avatarlucolvin Kf637's avatarKf637 alucarddelta's avataralucarddelta berksmbl's avatarberksmbl JDB321Sailor's avatarJDB321Sailor dintho's avatardintho domportera's avatardomportera hockeymikey's avatarhockeymikey itsmejoeeey's avataritsmejoeeey k073l's avatark073l m42cel's avatarm42cel mmihaly's avatarmmihaly nulpek's avatarnulpek patrickheeney's avatarpatrickheeney rokiden's avatarrokiden zigotica's avatarzigotica ssrangisetti's avatarssrangisetti BySempron's avatarBySempron alexdelprete's avataralexdelprete synack1337's avatarsynack1337 a-mnich's avatara-mnich BOZG's avatarBOZG bgillet's avatarbgillet moemoeq's avatarmoemoeq daentech's avatardaentech deneor's avatardeneor GuilhermeLCS95's avatarGuilhermeLCS95 kit494way's avatarkit494way pwhelan's avatarpwhelan rnowotniak's avatarrnowotniak TJonesy's avatarTJonesy twsouthwick's avatartwsouthwick thomaswienecke's avatarthomaswienecke armageddon421's avatararmageddon421 turnrye's avatarturnrye sachahjkl's avatarsachahjkl shazzx's avatarshazzx ThinkSalat's avatarThinkSalat sxravan's avatarsxravan royshreyaa's avatarroyshreyaa Smexhy's avatarSmexhy TheZachAttack01's avatarTheZachAttack01 stavros-k's avatarstavros-k XenonR's avatarXenonR StevKast's avatarStevKast ThibautSnoeijs's avatarThibautSnoeijs vishwamartur's avatarvishwamartur rubjo's avatarrubjo RoubenRehman's avatarRoubenRehman rsb-developer's avatarrsb-developer RobLoach's avatarRobLoach PrynsTag's avatarPrynsTag PlusaN's avatarPlusaN onurege3467's avataronurege3467 olenoerby's avatarolenoerby oka4shi's avataroka4shi pedorich-n's avatarpedorich-n bubylou's avatarbubylou michaelfeinbier's avatarmichaelfeinbier stinkybernie's avatarstinkybernie AmadeusGraves's avatarAmadeusGraves click0's avatarclick0 vinceh121's avatarvinceh121 Troublesis's avatarTroublesis tazboyz16's avatartazboyz16 sur1v's avatarsur1v soaibsafi's avatarsoaibsafi sasetz's avatarsasetz s-weigand's avatars-weigand pablomalo's avatarpablomalo onwp's avataronwp nOw-Ay's avatarnOw-Ay markxoe's avatarmarkxoe ivyturner's avatarivyturner kxenoxx's avatarkxenoxx jrobles98's avatarjrobles98 jnach's avatarjnach imlonghao's avatarimlonghao icy-comet's avataricy-comet huangshaohuai's avatarhuangshaohuai flechaig's avatarflechaig baifengheixi's avatarbaifengheixi aypt's avataraypt allozavrr's avatarallozavrr XertDev's avatarXertDev willbrowningme's avatarwillbrowningme miclav's avatarmiclav pvillaverde's avatarpvillaverde emiran-orange's avataremiran-orange edugof's avataredugof Dylan-Bs's avatarDylan-Bs dyauss's avatardyauss ddenev's avatarddenev dougaldhub's avatardougaldhub dkyeremeh's avatardkyeremeh deepsourcebot's avatardeepsourcebot DawidPietrykowski's avatarDawidPietrykowski skaarj1989's avatarskaarj1989 The127's avatarThe127 yeralin's avataryeralin danfein's avatardanfein clsty's avatarclsty bskim45's avatarbskim45 BhasherBEL's avatarBhasherBEL BeginCI's avatarBeginCI BRAVO68WEB's avatarBRAVO68WEB AaronPorts's avatarAaronPorts silicattinho's avatarsilicattinho AndreasdeReus's avatarAndreasdeReus alydemah's avataralydemah Alexis-BX's avatarAlexis-BX 5idereal's avatar5idereal 0n1cOn3's avatar0n1cOn3 imsakg's avatarimsakg maximemoreillon's avatarmaximemoreillon Glitch3dPenguin's avatarGlitch3dPenguin pho3nixf1re's avatarpho3nixf1re MatesMotionless's avatarMatesMotionless markusdd's avatarmarkusdd asenov's avatarasenov luispabon's avatarluispabon LeoColman's avatarLeoColman larsreinhardt's avatarlarsreinhardt KierenConnell's avatarKierenConnell kieraneglin's avatarkieraneglin SharpCoder's avatarSharpCoder jonas-schulze's avatarjonas-schulze johnhorton2010's avatarjohnhorton2010 Singebob's avatarSingebob Hellhium's avatarHellhium jjmung's avatarjjmung b1thunt3r's avatarb1thunt3r Compunctus's avatarCompunctus nealian's avatarnealian ip2location-com's avatarip2location-com FraglyG's avatarFraglyG hubortje's avatarhubortje gbrown09's avatargbrown09 FormatToday's avatarFormatToday
    +

    Dashy

    The Ultimate Homepage for your Homelab

    Dashy is an open source, highly customizable, easy to use, privacy-respecting dashboard app.Keep Reading...
    211Contributors
    25.7kGitHub Stars
    17.4MDocker Downloads
    5MDoc Site Views

    Theming

    With tons of built-in themes to choose form, plus a UI color palette editor, you can have a unique looking dashboard in no time. There is also support for custom CSS, and since all properties use CSS variables, it is easy to override.
    Learn more in the Docs Docs
    Demo of Theming feature in Dashy

    Icons

    Dashy can auto-fetch icons from the favicon of each of your apps/ services. There is also native support for Font Awesome, Material Design Icons, emoji icons and of course normal images.
    Learn more in the Docs Docs
    Demo of Icons feature in Dashy

    Status Indicators

    Get an instant overview of the health of each of your apps with status indicators. Once enabled, a small dot next to each app will show weather it is up and online, with more info like response time visible on hover.
    Learn more in the Docs Docs
    Demo of Status Indicators feature in Dashy

    Authentication

    Need to protect your dashboard, the simple auth feature is super quick to enable, and has support for multiple users with granular controls. Dashy also has built-in support for Keycloak and other SSO providers.
    Learn more in the Docs Docs
    Screenshot Coming Soon

    Widgets

    Display dynamic content from any API-enabled service. Dashy comes bundled with 50+ pre-built widgets for self-hosted services, productivity and monitoring.
    Learn more in the Docs Docs
    Demo of Widgets feature in Dashy

    Alternate Views

    As well as the default home, there is also a minimal view, which makes a great fast-loading browser startpage. Plus a workspace view useful for working on multiple apps at once, all without having to leave your dashboard.
    Learn more in the Docs Docs
    Demo of Alternate Views feature in Dashy

    Launching Methods

    Choose how to launch each of your apps by default, or right click for all options. Apps can be opened in a new tab, the same tab, a quick pop-up modal or in the workspace view.
    Learn more in the Docs Docs
    Demo of Launching Methods feature in Dashy

    Search & Shortcuts

    To search, just start typing, results will be filtered instantly. Use the arrow keys or tab to navigate through results, and press enter to launch. You can also create custom shortcuts for frequently used apps, or add custom tags for easier searching. Dashy can also be used to search the web using your favorite search engine.
    Learn more in the Docs Docs
    Demo of Search & Shortcuts feature in Dashy

    Cloud Backup & Sync

    There is an optional, end-to-end encrypted, free backup cloud service. This enables you to have your config backed up off-site, and to sync data between multiple instances easily.
    Learn more in the Docs Docs
    Screenshot Coming Soon

    Configuration

    Dashy's config is specified in a simple YAML file. But you can also configure the directly through the UI, and have changes written to, and backed up on disk. Real-time validation and hints are in place to help you.
    Learn more in the Docs Docs
    Demo of Configuration feature in Dashy

    Multi-Language Support

    Dashy's UI has been translated into several languages by several amazing contributors. Currently English, German, French, Dutch and Slovenian are supported. Your language should be applied automatically, or you can change it in the config menu.
    Learn more in the Docs Docs
    Screenshot Coming Soon

    Easy Deployment

    Although Dashy can be easily run on bare metal, the quickest method of getting started is with Docker. Just run `docker run -p 8080:80 lissy93/dashy` to pull, build and and run Dashy.
    Learn more in the Docs Docs
    Screenshot Coming Soon

    Customizable Layouts

    Structure your dashboard to fit your use case. From the UI, you can choose between different layouts, item sizes, show/ hide components, switch themes plus more. You can customize pretty much every area of your dashboard. There are config options for custom header, footer, nav bar links, title etc. You can also choose to hide any elements you don't need.
    Learn more in the Docs Docs
    Screenshot Coming Soon

    Powered by You

    Support us

    If you've found Dashy useful, consider contributing, or dropping us a star!

    Author

    Dashy was initially built by me, Alicia Sykes (@Lissy93 on GitHub), I build free and open source apps, focused on security, privacy, Linux and self-hosting.

    Sponsors

    Huge thanks to the following sponsors for supporting Dashy's development 💖

    Contributors

    Dashy was made possible, thanks to these wonderful contributors 🩵

    lissy93's avatarlissy93 (2971)liss-bot's avatarliss-bot (712)aspenyang's avataraspenyang (41)CrazyWolf13's avatarCrazyWolf13 (40)marekful's avatarmarekful (27)EVOTk's avatarEVOTk (24)snyk-bot's avatarsnyk-bot (24)azerioxal's avatarazerioxal (22)m42e's avatarm42e (19)pinarruiz's avatarpinarruiz (16)casmbu's avatarcasmbu (14)ricardodemauro's avatarricardodemauro imjimmeh's avatarimjimmeh kashif-se's avatarkashif-se lammersbjorn's avatarlammersbjorn conlan0's avatarconlan0 josuablejeru's avatarjosuablejeru ThrustVector's avatarThrustVector zcq100's avatarzcq100 albcp's avataralbcp Cereal916's avatarCereal916 lordpansar's avatarlordpansar jammo2k5's avatarjammo2k5 evroon's avatarevroon alayham's avataralayham alhazmy13's avataralhazmy13 altearius's avataraltearius LinuxSBC's avatarLinuxSBC UrekD's avatarUrekD shumittaher's avatarshumittaher hockwill's avatarhockwill z3r0l1nk's avatarz3r0l1nk wozboz's avatarwozboz he0119's avatarhe0119 dkadioglu's avatardkadioglu Totto16's avatarTotto16 toddejohnson's avatartoddejohnson remygrandin's avatarremygrandin dasunsrule32's avatardasunsrule32 aviolaris's avataraviolaris Tracreed's avatarTracreed rubenandre's avatarrubenandre rtm516's avatarrtm516 kayedspace's avatarkayedspace onedr0p's avataronedr0p stanly0726's avatarstanly0726 kt-alt's avatarkt-alt Bogyie's avatarBogyie webysther's avatarwebysther jtuzar's avatarjtuzar apgyorfi's avatarapgyorfi lucolvin's avatarlucolvin Kf637's avatarKf637 alucarddelta's avataralucarddelta berksmbl's avatarberksmbl JDB321Sailor's avatarJDB321Sailor dintho's avatardintho domportera's avatardomportera hockeymikey's avatarhockeymikey itsmejoeeey's avataritsmejoeeey k073l's avatark073l m42cel's avatarm42cel mmihaly's avatarmmihaly nulpek's avatarnulpek patrickheeney's avatarpatrickheeney rokiden's avatarrokiden zigotica's avatarzigotica ssrangisetti's avatarssrangisetti BySempron's avatarBySempron alexdelprete's avataralexdelprete synack1337's avatarsynack1337 a-mnich's avatara-mnich BOZG's avatarBOZG bgillet's avatarbgillet moemoeq's avatarmoemoeq daentech's avatardaentech deneor's avatardeneor GuilhermeLCS95's avatarGuilhermeLCS95 kit494way's avatarkit494way pwhelan's avatarpwhelan rnowotniak's avatarrnowotniak TJonesy's avatarTJonesy twsouthwick's avatartwsouthwick thomaswienecke's avatarthomaswienecke armageddon421's avatararmageddon421 turnrye's avatarturnrye sachahjkl's avatarsachahjkl shazzx's avatarshazzx ThinkSalat's avatarThinkSalat sxravan's avatarsxravan royshreyaa's avatarroyshreyaa Smexhy's avatarSmexhy TheZachAttack01's avatarTheZachAttack01 stavros-k's avatarstavros-k XenonR's avatarXenonR StevKast's avatarStevKast ThibautSnoeijs's avatarThibautSnoeijs vishwamartur's avatarvishwamartur rubjo's avatarrubjo RoubenRehman's avatarRoubenRehman rsb-developer's avatarrsb-developer RobLoach's avatarRobLoach PrynsTag's avatarPrynsTag PlusaN's avatarPlusaN onurege3467's avataronurege3467 olenoerby's avatarolenoerby oka4shi's avataroka4shi pedorich-n's avatarpedorich-n bubylou's avatarbubylou michaelfeinbier's avatarmichaelfeinbier stinkybernie's avatarstinkybernie AmadeusGraves's avatarAmadeusGraves click0's avatarclick0 vinceh121's avatarvinceh121 Troublesis's avatarTroublesis tazboyz16's avatartazboyz16 sur1v's avatarsur1v soaibsafi's avatarsoaibsafi sasetz's avatarsasetz s-weigand's avatars-weigand pablomalo's avatarpablomalo onwp's avataronwp nOw-Ay's avatarnOw-Ay markxoe's avatarmarkxoe ivyturner's avatarivyturner kxenoxx's avatarkxenoxx jrobles98's avatarjrobles98 jnach's avatarjnach imlonghao's avatarimlonghao icy-comet's avataricy-comet huangshaohuai's avatarhuangshaohuai flechaig's avatarflechaig baifengheixi's avatarbaifengheixi aypt's avataraypt allozavrr's avatarallozavrr XertDev's avatarXertDev willbrowningme's avatarwillbrowningme miclav's avatarmiclav pvillaverde's avatarpvillaverde emiran-orange's avataremiran-orange edugof's avataredugof Dylan-Bs's avatarDylan-Bs dyauss's avatardyauss ddenev's avatarddenev dougaldhub's avatardougaldhub dkyeremeh's avatardkyeremeh deepsourcebot's avatardeepsourcebot DawidPietrykowski's avatarDawidPietrykowski skaarj1989's avatarskaarj1989 The127's avatarThe127 yeralin's avataryeralin danfein's avatardanfein clsty's avatarclsty bskim45's avatarbskim45 BhasherBEL's avatarBhasherBEL BeginCI's avatarBeginCI BRAVO68WEB's avatarBRAVO68WEB AaronPorts's avatarAaronPorts silicattinho's avatarsilicattinho AndreasdeReus's avatarAndreasdeReus alydemah's avataralydemah Alexis-BX's avatarAlexis-BX 5idereal's avatar5idereal 0n1cOn3's avatar0n1cOn3 imsakg's avatarimsakg maximemoreillon's avatarmaximemoreillon Glitch3dPenguin's avatarGlitch3dPenguin pho3nixf1re's avatarpho3nixf1re MatesMotionless's avatarMatesMotionless markusdd's avatarmarkusdd asenov's avatarasenov luispabon's avatarluispabon LeoColman's avatarLeoColman larsreinhardt's avatarlarsreinhardt KierenConnell's avatarKierenConnell kieraneglin's avatarkieraneglin SharpCoder's avatarSharpCoder jonas-schulze's avatarjonas-schulze johnhorton2010's avatarjohnhorton2010 Singebob's avatarSingebob Hellhium's avatarHellhium jjmung's avatarjjmung b1thunt3r's avatarb1thunt3r Compunctus's avatarCompunctus nealian's avatarnealian ip2location-com's avatarip2location-com FraglyG's avatarFraglyG hubortje's avatarhubortje gbrown09's avatargbrown09 FormatToday's avatarFormatToday
    \ No newline at end of file diff --git a/markdown-page/index.html b/markdown-page/index.html index 9b1ae509..301ce0d9 100644 --- a/markdown-page/index.html +++ b/markdown-page/index.html @@ -11,15 +11,15 @@ - - - + + + -

    Markdown page example

    + \ No newline at end of file diff --git a/rss.xml b/rss.xml index 8a48f624..9130c325 100644 --- a/rss.xml +++ b/rss.xml @@ -6,8 +6,16 @@ New releases and version tags for Dashy, the self-hosted dashboard for your homelab. en - Sat, 04 Jul 2026 14:41:25 GMT + Sat, 04 Jul 2026 17:13:21 GMT dashy-docs github-data plugin + + 4.3.15 + https://github.com/lissy93/dashy/releases/tag/4.3.15 + https://github.com/lissy93/dashy/releases/tag/4.3.15 + Sat, 04 Jul 2026 17:09:26 GMT + Tag + Tag 4.3.15 + 4.3.14 https://github.com/lissy93/dashy/releases/tag/4.3.14 diff --git a/search-index.json b/search-index.json index f2db3823..0640453a 100644 --- a/search-index.json +++ b/search-index.json @@ -1 +1 @@ -[{"documents":[{"i":1,"t":"","u":"/docs/","b":["Documentation"]},{"i":2,"t":"Alternate Views & Opening Methods","u":"/docs/alternate-views","b":["Documentation","Feature Docs"]},{"i":13,"t":"REST API","u":"/docs/api","b":["Documentation"]},{"i":31,"t":"Authentication","u":"/docs/authentication","b":["Documentation","Feature Docs","Authentication"]},{"i":47,"t":"Authelia OIDC","u":"/docs/authentication/authelia-oidc","b":["Documentation","Feature Docs","Authentication"]},{"i":78,"t":"Authentik OIDC","u":"/docs/authentication/authentik","b":["Documentation","Feature Docs","Authentication"]},{"i":109,"t":"Built-In Auth","u":"/docs/authentication/built-in","b":["Documentation","Feature Docs","Authentication"]},{"i":148,"t":"Cloudflare Tunnel","u":"/docs/authentication/cloudflare-tunnel","b":["Documentation","Feature Docs","Authentication"]},{"i":169,"t":"Header Authentication","u":"/docs/authentication/header-auth","b":["Documentation","Feature Docs","Authentication"]},{"i":183,"t":"Keycloak","u":"/docs/authentication/keycloak","b":["Documentation","Feature Docs","Authentication"]},{"i":222,"t":"OIDC","u":"/docs/authentication/oidc","b":["Documentation","Feature Docs","Authentication"]},{"i":239,"t":"Other Auth Methods","u":"/docs/authentication/other-auth-methods","b":["Documentation","Feature Docs","Authentication"]},{"i":259,"t":"Pocket ID OIDC","u":"/docs/authentication/pocketid","b":["Documentation","Feature Docs","Authentication"]},{"i":292,"t":"Tailscale","u":"/docs/authentication/tailscale","b":["Documentation","Feature Docs","Authentication"]},{"i":315,"t":"Zitadel OIDC","u":"/docs/authentication/zitadel","b":["Documentation","Feature Docs","Authentication"]},{"i":353,"t":"Cloud Backup and Restore","u":"/docs/backup-restore","b":["Documentation","Feature Docs"]},{"i":376,"t":"Configuring","u":"/docs/configuring","b":["Documentation","Running Dashy"]},{"i":433,"t":"Contributing","u":"/docs/contributing","b":["Documentation","Community"]},{"i":465,"t":"Credits","u":"/docs/credits","b":["Documentation","Misc"]},{"i":479,"t":"Deployment","u":"/docs/deployment","b":["Documentation","Running Dashy"]},{"i":496,"t":"Bare Metal","u":"/docs/deployment/bare-metal","b":["Documentation"]},{"i":509,"t":"Cloud Services","u":"/docs/deployment/cloud","b":["Documentation"]},{"i":548,"t":"Docker","u":"/docs/deployment/docker","b":["Documentation"]},{"i":558,"t":"Self-Hosted Operating Systems","u":"/docs/deployment/self-hosted-os","b":["Documentation"]},{"i":572,"t":"Self-Hosted Platforms","u":"/docs/deployment/self-hosted-platforms","b":["Documentation"]},{"i":592,"t":"Developing","u":"/docs/developing","b":["Documentation","Community"]},{"i":626,"t":"Development Guides","u":"/docs/development-guides","b":["Documentation","Community"]},{"i":675,"t":"Icons","u":"/docs/icons","b":["Documentation","Feature Docs"]},{"i":705,"t":"License","u":"/docs/license","b":["Documentation","Misc"]},{"i":708,"t":"App Management","u":"/docs/management","b":["Documentation","Running Dashy"]},{"i":836,"t":"Internationalization","u":"/docs/multi-language-support","b":["Documentation","Feature Docs"]},{"i":864,"t":"Pages and Sections","u":"/docs/pages-and-sections","b":["Documentation","Feature Docs"]},{"i":885,"t":"Privacy & Security","u":"/docs/privacy","b":["Documentation","Misc"]},{"i":950,"t":"Quick Start","u":"/docs/quick-start","b":["Quick Start","Running Dashy"]},{"i":970,"t":"Releases and Workflows","u":"/docs/release-workflow","b":["Documentation","Misc"]},{"i":1001,"t":"Search & Shortcuts","u":"/docs/searching","b":["Documentation","Feature Docs"]},{"i":1030,"t":"Dashy Showcase 🌟","u":"/docs/showcase","b":["Documentation","Community"]},{"i":1091,"t":"Dashy Showcase 🌟","u":"/docs/showcase/","b":["Documentation","Community"]},{"i":1152,"t":"Status Indicators","u":"/docs/status-indicators","b":["Documentation","Feature Docs"]},{"i":1172,"t":"Theming","u":"/docs/theming","b":["Documentation","Feature Docs"]},{"i":1205,"t":"Troubleshooting","u":"/docs/troubleshooting","b":["Documentation","Running Dashy"]},{"i":1366,"t":"Widgets","u":"/docs/widgets","b":["Documentation","Feature Docs"]}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/1",[]],["t/2",[0,1.983,1,1.983,2,1.215,3,1.983,4,1.681]],["t/13",[5,3.253,6,3.253]],["t/31",[7,3.507]],["t/47",[8,3.253,9,1.994]],["t/78",[9,1.994,10,3.253]],["t/109",[11,3.253,12,2.758]],["t/148",[13,3.253,14,3.253]],["t/169",[7,2.758,15,3.253]],["t/183",[16,4.137]],["t/222",[9,2.535]],["t/239",[4,2.758,12,2.758]],["t/259",[9,1.643,17,2.681,18,2.681]],["t/292",[19,4.137]],["t/315",[9,1.994,20,3.253]],["t/353",[21,2.273,22,2.681,23,2.681]],["t/376",[24,4.137]],["t/433",[25,4.137]],["t/465",[26,4.137]],["t/479",[27,4.137]],["t/496",[28,3.253,29,3.253]],["t/509",[21,2.758,30,3.253]],["t/548",[31,4.137]],["t/558",[32,1.933,33,1.933,34,2.28,35,2.28]],["t/572",[32,2.273,33,2.273,36,2.681]],["t/592",[37,3.507]],["t/626",[37,2.758,38,3.253]],["t/675",[39,4.137]],["t/705",[40,4.137]],["t/708",[41,3.253,42,3.253]],["t/836",[43,4.137]],["t/864",[44,3.253,45,3.253]],["t/885",[2,1.643,46,2.681,47,2.681]],["t/950",[48,3.253,49,3.253]],["t/970",[50,3.253,51,3.253]],["t/1001",[2,1.643,52,2.681,53,2.681]],["t/1030",[2,1.643,54,2.273,55,2.273]],["t/1091",[2,1.643,54,2.273,55,2.273]],["t/1152",[56,3.253,57,3.253]],["t/1172",[58,4.137]],["t/1205",[59,4.137]],["t/1366",[60,4.137]]],"invertedIndex":[["",{"_index":2,"t":{"2":{"position":[[16,1]]},"885":{"position":[[8,1]]},"1001":{"position":[[7,1]]},"1030":{"position":[[15,2]]},"1091":{"position":[[15,2]]}}}],["altern",{"_index":0,"t":{"2":{"position":[[0,9]]}}}],["api",{"_index":6,"t":{"13":{"position":[[5,3]]}}}],["app",{"_index":41,"t":{"708":{"position":[[0,3]]}}}],["auth",{"_index":12,"t":{"109":{"position":[[9,4]]},"239":{"position":[[6,4]]}}}],["authelia",{"_index":8,"t":{"47":{"position":[[0,8]]}}}],["authent",{"_index":7,"t":{"31":{"position":[[0,14]]},"169":{"position":[[7,14]]}}}],["authentik",{"_index":10,"t":{"78":{"position":[[0,9]]}}}],["backup",{"_index":22,"t":{"353":{"position":[[6,6]]}}}],["bare",{"_index":28,"t":{"496":{"position":[[0,4]]}}}],["built",{"_index":11,"t":{"109":{"position":[[0,5]]}}}],["cloud",{"_index":21,"t":{"353":{"position":[[0,5]]},"509":{"position":[[0,5]]}}}],["cloudflar",{"_index":13,"t":{"148":{"position":[[0,10]]}}}],["configur",{"_index":24,"t":{"376":{"position":[[0,11]]}}}],["contribut",{"_index":25,"t":{"433":{"position":[[0,12]]}}}],["credit",{"_index":26,"t":{"465":{"position":[[0,7]]}}}],["dashi",{"_index":54,"t":{"1030":{"position":[[0,5]]},"1091":{"position":[[0,5]]}}}],["deploy",{"_index":27,"t":{"479":{"position":[[0,10]]}}}],["develop",{"_index":37,"t":{"592":{"position":[[0,10]]},"626":{"position":[[0,11]]}}}],["docker",{"_index":31,"t":{"548":{"position":[[0,6]]}}}],["guid",{"_index":38,"t":{"626":{"position":[[12,6]]}}}],["header",{"_index":15,"t":{"169":{"position":[[0,6]]}}}],["host",{"_index":33,"t":{"558":{"position":[[5,6]]},"572":{"position":[[5,6]]}}}],["icon",{"_index":39,"t":{"675":{"position":[[0,5]]}}}],["id",{"_index":18,"t":{"259":{"position":[[7,2]]}}}],["indic",{"_index":57,"t":{"1152":{"position":[[7,10]]}}}],["internation",{"_index":43,"t":{"836":{"position":[[0,20]]}}}],["keycloak",{"_index":16,"t":{"183":{"position":[[0,8]]}}}],["licens",{"_index":40,"t":{"705":{"position":[[0,7]]}}}],["manag",{"_index":42,"t":{"708":{"position":[[4,10]]}}}],["metal",{"_index":29,"t":{"496":{"position":[[5,5]]}}}],["method",{"_index":4,"t":{"2":{"position":[[26,7]]},"239":{"position":[[11,7]]}}}],["oidc",{"_index":9,"t":{"47":{"position":[[9,4]]},"78":{"position":[[10,4]]},"222":{"position":[[0,4]]},"259":{"position":[[10,4]]},"315":{"position":[[8,4]]}}}],["open",{"_index":3,"t":{"2":{"position":[[18,7]]}}}],["oper",{"_index":34,"t":{"558":{"position":[[12,9]]}}}],["page",{"_index":44,"t":{"864":{"position":[[0,5]]}}}],["platform",{"_index":36,"t":{"572":{"position":[[12,9]]}}}],["pocket",{"_index":17,"t":{"259":{"position":[[0,6]]}}}],["privaci",{"_index":46,"t":{"885":{"position":[[0,7]]}}}],["quick",{"_index":48,"t":{"950":{"position":[[0,5]]}}}],["releas",{"_index":50,"t":{"970":{"position":[[0,8]]}}}],["rest",{"_index":5,"t":{"13":{"position":[[0,4]]}}}],["restor",{"_index":23,"t":{"353":{"position":[[17,7]]}}}],["search",{"_index":52,"t":{"1001":{"position":[[0,6]]}}}],["section",{"_index":45,"t":{"864":{"position":[[10,8]]}}}],["secur",{"_index":47,"t":{"885":{"position":[[10,8]]}}}],["self",{"_index":32,"t":{"558":{"position":[[0,4]]},"572":{"position":[[0,4]]}}}],["servic",{"_index":30,"t":{"509":{"position":[[6,8]]}}}],["shortcut",{"_index":53,"t":{"1001":{"position":[[9,9]]}}}],["showcas",{"_index":55,"t":{"1030":{"position":[[6,8]]},"1091":{"position":[[6,8]]}}}],["start",{"_index":49,"t":{"950":{"position":[[6,5]]}}}],["statu",{"_index":56,"t":{"1152":{"position":[[0,6]]}}}],["system",{"_index":35,"t":{"558":{"position":[[22,7]]}}}],["tailscal",{"_index":19,"t":{"292":{"position":[[0,9]]}}}],["theme",{"_index":58,"t":{"1172":{"position":[[0,7]]}}}],["troubleshoot",{"_index":59,"t":{"1205":{"position":[[0,15]]}}}],["tunnel",{"_index":14,"t":{"148":{"position":[[11,6]]}}}],["view",{"_index":1,"t":{"2":{"position":[[10,5]]}}}],["widget",{"_index":60,"t":{"1366":{"position":[[0,7]]}}}],["workflow",{"_index":51,"t":{"970":{"position":[[13,9]]}}}],["zitadel",{"_index":20,"t":{"315":{"position":[[0,7]]}}}]],"pipeline":["stemmer"]}},{"documents":[{"i":3,"t":"Views","u":"/docs/alternate-views","h":"#views","p":2},{"i":5,"t":"Default","u":"/docs/alternate-views","h":"#default","p":2},{"i":7,"t":"Workspace","u":"/docs/alternate-views","h":"#workspace","p":2},{"i":9,"t":"Minimal View","u":"/docs/alternate-views","h":"#minimal-view","p":2},{"i":11,"t":"Opening Methods","u":"/docs/alternate-views","h":"#opening-methods","p":2},{"i":15,"t":"Enabling the API","u":"/docs/api","h":"#enabling-the-api","p":13},{"i":17,"t":"Authentication","u":"/docs/api","h":"#authentication","p":13},{"i":19,"t":"API token","u":"/docs/api","h":"#api-token","p":13},{"i":21,"t":"Endpoints","u":"/docs/api","h":"#endpoints","p":13},{"i":23,"t":"Addressing Sections and Items","u":"/docs/api","h":"#addressing-sections-and-items","p":13},{"i":25,"t":"Updating","u":"/docs/api","h":"#updating","p":13},{"i":27,"t":"Examples","u":"/docs/api","h":"#examples","p":13},{"i":29,"t":"Limitations & Notes","u":"/docs/api","h":"#limitations--notes","p":13},{"i":33,"t":"Built-In Auth","u":"/docs/authentication","h":"#built-in-auth","p":31},{"i":35,"t":"Keycloak","u":"/docs/authentication","h":"#keycloak","p":31},{"i":37,"t":"Header Authentication","u":"/docs/authentication","h":"#header-authentication","p":31},{"i":39,"t":"OIDC","u":"/docs/authentication","h":"#oidc","p":31},{"i":41,"t":"Zero-Trust Tunnels","u":"/docs/authentication","h":"#zero-trust-tunnels","p":31},{"i":43,"t":"Alternative Authentication Methods","u":"/docs/authentication","h":"#alternative-authentication-methods","p":31},{"i":45,"t":"Comparison of Auth Options","u":"/docs/authentication","h":"#comparison-of-auth-options","p":31},{"i":49,"t":"Contents","u":"/docs/authentication/authelia-oidc","h":"#contents","p":47},{"i":51,"t":"1. Deploy Authelia","u":"/docs/authentication/authelia-oidc","h":"#1-deploy-authelia","p":47},{"i":53,"t":"2. Configure Authelia","u":"/docs/authentication/authelia-oidc","h":"#2-configure-authelia","p":47},{"i":54,"t":"Generate secrets and keys","u":"/docs/authentication/authelia-oidc","h":"#generate-secrets-and-keys","p":47},{"i":56,"t":"Users database","u":"/docs/authentication/authelia-oidc","h":"#users-database","p":47},{"i":58,"t":"Main configuration","u":"/docs/authentication/authelia-oidc","h":"#main-configuration","p":47},{"i":60,"t":"Summary","u":"/docs/authentication/authelia-oidc","h":"#summary","p":47},{"i":62,"t":"3. Enabling Authelia in Dashy","u":"/docs/authentication/authelia-oidc","h":"#3-enabling-authelia-in-dashy","p":47},{"i":64,"t":"4. Groups and Visibility","u":"/docs/authentication/authelia-oidc","h":"#4-groups-and-visibility","p":47},{"i":66,"t":"5. Silent token renewal (optional)","u":"/docs/authentication/authelia-oidc","h":"#5-silent-token-renewal-optional","p":47},{"i":68,"t":"Troubleshooting common Authelia Issues","u":"/docs/authentication/authelia-oidc","h":"#troubleshooting-common-authelia-issues","p":47},{"i":70,"t":"How it Works","u":"/docs/authentication/authelia-oidc","h":"#how-it-works","p":47},{"i":72,"t":"Client side","u":"/docs/authentication/authelia-oidc","h":"#client-side","p":47},{"i":74,"t":"Server side","u":"/docs/authentication/authelia-oidc","h":"#server-side","p":47},{"i":76,"t":"Visual Overview","u":"/docs/authentication/authelia-oidc","h":"#visual-overview","p":47},{"i":80,"t":"Contents","u":"/docs/authentication/authentik","h":"#contents","p":78},{"i":82,"t":"1. Deploy Authentik","u":"/docs/authentication/authentik","h":"#1-deploy-authentik","p":78},{"i":84,"t":"2. Configure Authentik","u":"/docs/authentication/authentik","h":"#2-configure-authentik","p":78},{"i":85,"t":"Create the groups scope","u":"/docs/authentication/authentik","h":"#create-the-groups-scope","p":78},{"i":87,"t":"Create the OIDC provider","u":"/docs/authentication/authentik","h":"#create-the-oidc-provider","p":78},{"i":89,"t":"Create the application","u":"/docs/authentication/authentik","h":"#create-the-application","p":78},{"i":91,"t":"Create the admin group","u":"/docs/authentication/authentik","h":"#create-the-admin-group","p":78},{"i":93,"t":"Create test users","u":"/docs/authentication/authentik","h":"#create-test-users","p":78},{"i":95,"t":"Restrict who can access Dashy (optional)","u":"/docs/authentication/authentik","h":"#restrict-who-can-access-dashy-optional","p":78},{"i":97,"t":"3. Enabling Authentik in Dashy","u":"/docs/authentication/authentik","h":"#3-enabling-authentik-in-dashy","p":78},{"i":99,"t":"4. Groups and Visibility","u":"/docs/authentication/authentik","h":"#4-groups-and-visibility","p":78},{"i":101,"t":"5. Silent token renewal (optional)","u":"/docs/authentication/authentik","h":"#5-silent-token-renewal-optional","p":78},{"i":103,"t":"Troubleshooting common Authentik Issues","u":"/docs/authentication/authentik","h":"#troubleshooting-common-authentik-issues","p":78},{"i":105,"t":"Config Example","u":"/docs/authentication/authentik","h":"#config-example","p":78},{"i":107,"t":"How it Works","u":"/docs/authentication/authentik","h":"#how-it-works","p":78},{"i":111,"t":"Contents","u":"/docs/authentication/built-in","h":"#contents","p":109},{"i":113,"t":"Quick Start","u":"/docs/authentication/built-in","h":"#quick-start","p":109},{"i":115,"t":"Defining Users","u":"/docs/authentication/built-in","h":"#defining-users","p":109},{"i":117,"t":"Generating a password hash","u":"/docs/authentication/built-in","h":"#generating-a-password-hash","p":109},{"i":119,"t":"Using an env var instead of an inline hash","u":"/docs/authentication/built-in","h":"#using-an-env-var-instead-of-an-inline-hash","p":109},{"i":121,"t":"Choosing a Protection Mode","u":"/docs/authentication/built-in","h":"#choosing-a-protection-mode","p":109},{"i":123,"t":"Client-side login","u":"/docs/authentication/built-in","h":"#client-side-login","p":109},{"i":125,"t":"Server-enforced via conf.yml users","u":"/docs/authentication/built-in","h":"#server-enforced-via-confyml-users","p":109},{"i":127,"t":"Static credentials (no Dashy login page)","u":"/docs/authentication/built-in","h":"#static-credentials-no-dashy-login-page","p":109},{"i":129,"t":"Sessions, Guest Access & Logout","u":"/docs/authentication/built-in","h":"#sessions-guest-access--logout","p":109},{"i":131,"t":"Guest access","u":"/docs/authentication/built-in","h":"#guest-access","p":109},{"i":133,"t":"User Roles & Visibility","u":"/docs/authentication/built-in","h":"#user-roles--visibility","p":109},{"i":135,"t":"Per-section and per-item visibility","u":"/docs/authentication/built-in","h":"#per-section-and-per-item-visibility","p":109},{"i":137,"t":"Auto-Authenticating the Frontend","u":"/docs/authentication/built-in","h":"#auto-authenticating-the-frontend","p":109},{"i":139,"t":"Troubleshooting common built-in auth issues","u":"/docs/authentication/built-in","h":"#troubleshooting-common-built-in-auth-issues","p":109},{"i":141,"t":"Security Considerations","u":"/docs/authentication/built-in","h":"#security-considerations","p":109},{"i":143,"t":"How it Works","u":"/docs/authentication/built-in","h":"#how-it-works","p":109},{"i":144,"t":"The login flow (client-side mode)","u":"/docs/authentication/built-in","h":"#the-login-flow-client-side-mode","p":109},{"i":146,"t":"Server-enforced mode","u":"/docs/authentication/built-in","h":"#server-enforced-mode","p":109},{"i":150,"t":"Contents","u":"/docs/authentication/cloudflare-tunnel","h":"#contents","p":148},{"i":152,"t":"1. How it fits together","u":"/docs/authentication/cloudflare-tunnel","h":"#1-how-it-fits-together","p":148},{"i":154,"t":"2. Create the tunnel in Cloudflare","u":"/docs/authentication/cloudflare-tunnel","h":"#2-create-the-tunnel-in-cloudflare","p":148},{"i":156,"t":"3. Run cloudflared","u":"/docs/authentication/cloudflare-tunnel","h":"#3-run-cloudflared","p":148},{"i":158,"t":"4. Add a Cloudflare Access policy","u":"/docs/authentication/cloudflare-tunnel","h":"#4-add-a-cloudflare-access-policy","p":148},{"i":160,"t":"5. Configure Dashy","u":"/docs/authentication/cloudflare-tunnel","h":"#5-configure-dashy","p":148},{"i":162,"t":"6. Best practices","u":"/docs/authentication/cloudflare-tunnel","h":"#6-best-practices","p":148},{"i":164,"t":"Troubleshooting common Cloudflare Tunnel issues","u":"/docs/authentication/cloudflare-tunnel","h":"#troubleshooting-common-cloudflare-tunnel-issues","p":148},{"i":166,"t":"How it Works","u":"/docs/authentication/cloudflare-tunnel","h":"#how-it-works","p":148},{"i":167,"t":"Server side","u":"/docs/authentication/cloudflare-tunnel","h":"#server-side","p":148},{"i":171,"t":"Contents","u":"/docs/authentication/header-auth","h":"#contents","p":169},{"i":173,"t":"Configure Dashy","u":"/docs/authentication/header-auth","h":"#configure-dashy","p":169},{"i":175,"t":"Configure your proxy","u":"/docs/authentication/header-auth","h":"#configure-your-proxy","p":169},{"i":177,"t":"Troubleshooting","u":"/docs/authentication/header-auth","h":"#troubleshooting","p":169},{"i":179,"t":"Security notes","u":"/docs/authentication/header-auth","h":"#security-notes","p":169},{"i":181,"t":"How it works","u":"/docs/authentication/header-auth","h":"#how-it-works","p":169},{"i":185,"t":"Contents","u":"/docs/authentication/keycloak","h":"#contents","p":183},{"i":187,"t":"1. Deploy Keycloak","u":"/docs/authentication/keycloak","h":"#1-deploy-keycloak","p":183},{"i":189,"t":"2. Configure Keycloak","u":"/docs/authentication/keycloak","h":"#2-configure-keycloak","p":183},{"i":190,"t":"Create the Keycloak realm","u":"/docs/authentication/keycloak","h":"#create-the-keycloak-realm","p":183},{"i":192,"t":"Allow Dashy's local origin","u":"/docs/authentication/keycloak","h":"#allow-dashys-local-origin","p":183},{"i":194,"t":"Create the Dashy client","u":"/docs/authentication/keycloak","h":"#create-the-dashy-client","p":183},{"i":196,"t":"Add the realm-role mapper","u":"/docs/authentication/keycloak","h":"#add-the-realm-role-mapper","p":183},{"i":198,"t":"Create the Dashy admin role","u":"/docs/authentication/keycloak","h":"#create-the-dashy-admin-role","p":183},{"i":200,"t":"(Alternative) Use a group for admin","u":"/docs/authentication/keycloak","h":"#alternative-use-a-group-for-admin","p":183},{"i":202,"t":"Create test users","u":"/docs/authentication/keycloak","h":"#create-test-users","p":183},{"i":204,"t":"Summary","u":"/docs/authentication/keycloak","h":"#summary","p":183},{"i":206,"t":"3. Enabling Keycloak in Dashy","u":"/docs/authentication/keycloak","h":"#3-enabling-keycloak-in-dashy","p":183},{"i":208,"t":"4. Groups and Roles","u":"/docs/authentication/keycloak","h":"#4-groups-and-roles","p":183},{"i":210,"t":"Troubleshooting common Keycloak Issues","u":"/docs/authentication/keycloak","h":"#troubleshooting-common-keycloak-issues","p":183},{"i":212,"t":"How it Works","u":"/docs/authentication/keycloak","h":"#how-it-works","p":183},{"i":214,"t":"Client side","u":"/docs/authentication/keycloak","h":"#client-side","p":183},{"i":216,"t":"Server side","u":"/docs/authentication/keycloak","h":"#server-side","p":183},{"i":218,"t":"Why the mapper matters","u":"/docs/authentication/keycloak","h":"#why-the-mapper-matters","p":183},{"i":220,"t":"Visual Overview","u":"/docs/authentication/keycloak","h":"#visual-overview","p":183},{"i":223,"t":"Step-by-step Guides","u":"/docs/authentication/oidc","h":"#step-by-step-guides","p":222},{"i":225,"t":"About OIDC","u":"/docs/authentication/oidc","h":"#about-oidc","p":222},{"i":227,"t":"Admin access","u":"/docs/authentication/oidc","h":"#admin-access","p":222},{"i":229,"t":"Multi-tenant providers","u":"/docs/authentication/oidc","h":"#multi-tenant-providers","p":222},{"i":231,"t":"Guest access","u":"/docs/authentication/oidc","h":"#guest-access","p":222},{"i":233,"t":"Using with a PWA","u":"/docs/authentication/oidc","h":"#using-with-a-pwa","p":222},{"i":235,"t":"How server-side enforcement works","u":"/docs/authentication/oidc","h":"#how-server-side-enforcement-works","p":222},{"i":237,"t":"Silent token renewal","u":"/docs/authentication/oidc","h":"#silent-token-renewal","p":222},{"i":241,"t":"Reverse proxy auth","u":"/docs/authentication/other-auth-methods","h":"#reverse-proxy-auth","p":239},{"i":243,"t":"Zero-trust tunnels","u":"/docs/authentication/other-auth-methods","h":"#zero-trust-tunnels","p":239},{"i":245,"t":"Service worker & offline use","u":"/docs/authentication/other-auth-methods","h":"#service-worker--offline-use","p":239},{"i":247,"t":"VPN","u":"/docs/authentication/other-auth-methods","h":"#vpn","p":239},{"i":249,"t":"IP-based access","u":"/docs/authentication/other-auth-methods","h":"#ip-based-access","p":239},{"i":251,"t":"Web server authentication","u":"/docs/authentication/other-auth-methods","h":"#web-server-authentication","p":239},{"i":253,"t":"Client certificates (mTLS)","u":"/docs/authentication/other-auth-methods","h":"#client-certificates-mtls","p":239},{"i":255,"t":"SSO / OAuth providers","u":"/docs/authentication/other-auth-methods","h":"#sso--oauth-providers","p":239},{"i":257,"t":"Cloud hosting providers","u":"/docs/authentication/other-auth-methods","h":"#cloud-hosting-providers","p":239},{"i":261,"t":"Contents","u":"/docs/authentication/pocketid","h":"#contents","p":259},{"i":263,"t":"1. Deploy Pocket ID","u":"/docs/authentication/pocketid","h":"#1-deploy-pocket-id","p":259},{"i":265,"t":"2. Configure Pocket ID","u":"/docs/authentication/pocketid","h":"#2-configure-pocket-id","p":259},{"i":266,"t":"First-time admin setup","u":"/docs/authentication/pocketid","h":"#first-time-admin-setup","p":259},{"i":268,"t":"Create the OIDC client","u":"/docs/authentication/pocketid","h":"#create-the-oidc-client","p":259},{"i":270,"t":"Create the admin group","u":"/docs/authentication/pocketid","h":"#create-the-admin-group","p":259},{"i":272,"t":"Create test users","u":"/docs/authentication/pocketid","h":"#create-test-users","p":259},{"i":274,"t":"Summary","u":"/docs/authentication/pocketid","h":"#summary","p":259},{"i":276,"t":"3. Enabling Pocket ID in Dashy","u":"/docs/authentication/pocketid","h":"#3-enabling-pocket-id-in-dashy","p":259},{"i":278,"t":"4. Groups and Visibility","u":"/docs/authentication/pocketid","h":"#4-groups-and-visibility","p":259},{"i":280,"t":"5. Silent token renewal (optional)","u":"/docs/authentication/pocketid","h":"#5-silent-token-renewal-optional","p":259},{"i":282,"t":"Troubleshooting common Pocket ID issues","u":"/docs/authentication/pocketid","h":"#troubleshooting-common-pocket-id-issues","p":259},{"i":284,"t":"How it Works","u":"/docs/authentication/pocketid","h":"#how-it-works","p":259},{"i":286,"t":"Client side","u":"/docs/authentication/pocketid","h":"#client-side","p":259},{"i":288,"t":"Server side","u":"/docs/authentication/pocketid","h":"#server-side","p":259},{"i":290,"t":"Visual Overview","u":"/docs/authentication/pocketid","h":"#visual-overview","p":259},{"i":294,"t":"Contents","u":"/docs/authentication/tailscale","h":"#contents","p":292},{"i":296,"t":"How it fits together","u":"/docs/authentication/tailscale","h":"#how-it-fits-together","p":292},{"i":298,"t":"Prepare Tailscale","u":"/docs/authentication/tailscale","h":"#prepare-tailscale","p":292},{"i":300,"t":"Run Tailscale + Dashy together","u":"/docs/authentication/tailscale","h":"#run-tailscale--dashy-together","p":292},{"i":302,"t":"Configure Dashy","u":"/docs/authentication/tailscale","h":"#configure-dashy","p":292},{"i":304,"t":"Funnel for public access (optional)","u":"/docs/authentication/tailscale","h":"#funnel-for-public-access-optional","p":292},{"i":306,"t":"Using Headscale instead","u":"/docs/authentication/tailscale","h":"#using-headscale-instead","p":292},{"i":308,"t":"Best practices","u":"/docs/authentication/tailscale","h":"#best-practices","p":292},{"i":310,"t":"Troubleshooting common Tailscale issues","u":"/docs/authentication/tailscale","h":"#troubleshooting-common-tailscale-issues","p":292},{"i":312,"t":"How it Works","u":"/docs/authentication/tailscale","h":"#how-it-works","p":292},{"i":313,"t":"Server side","u":"/docs/authentication/tailscale","h":"#server-side","p":292},{"i":317,"t":"Contents","u":"/docs/authentication/zitadel","h":"#contents","p":315},{"i":319,"t":"1. Deploy Zitadel","u":"/docs/authentication/zitadel","h":"#1-deploy-zitadel","p":315},{"i":321,"t":"2. Configure Zitadel","u":"/docs/authentication/zitadel","h":"#2-configure-zitadel","p":315},{"i":323,"t":"Create the project","u":"/docs/authentication/zitadel","h":"#create-the-project","p":315},{"i":325,"t":"Create the OIDC application","u":"/docs/authentication/zitadel","h":"#create-the-oidc-application","p":315},{"i":327,"t":"Create the admin role","u":"/docs/authentication/zitadel","h":"#create-the-admin-role","p":315},{"i":329,"t":"Grant the role to a user","u":"/docs/authentication/zitadel","h":"#grant-the-role-to-a-user","p":315},{"i":331,"t":"Map roles to a groups claim","u":"/docs/authentication/zitadel","h":"#map-roles-to-a-groups-claim","p":315},{"i":333,"t":"Create test users","u":"/docs/authentication/zitadel","h":"#create-test-users","p":315},{"i":335,"t":"Summary","u":"/docs/authentication/zitadel","h":"#summary","p":315},{"i":337,"t":"3. Enabling Zitadel in Dashy","u":"/docs/authentication/zitadel","h":"#3-enabling-zitadel-in-dashy","p":315},{"i":339,"t":"4. Groups and Visibility","u":"/docs/authentication/zitadel","h":"#4-groups-and-visibility","p":315},{"i":341,"t":"5. Silent token renewal (optional)","u":"/docs/authentication/zitadel","h":"#5-silent-token-renewal-optional","p":315},{"i":343,"t":"Troubleshooting common Zitadel Issues","u":"/docs/authentication/zitadel","h":"#troubleshooting-common-zitadel-issues","p":315},{"i":345,"t":"How it Works","u":"/docs/authentication/zitadel","h":"#how-it-works","p":315},{"i":347,"t":"Client side","u":"/docs/authentication/zitadel","h":"#client-side","p":315},{"i":349,"t":"Server side","u":"/docs/authentication/zitadel","h":"#server-side","p":315},{"i":351,"t":"Visual Overview","u":"/docs/authentication/zitadel","h":"#visual-overview","p":315},{"i":355,"t":"How it Works","u":"/docs/backup-restore","h":"#how-it-works","p":353},{"i":357,"t":"Creating a Backup","u":"/docs/backup-restore","h":"#creating-a-backup","p":353},{"i":359,"t":"Restoring a Backup","u":"/docs/backup-restore","h":"#restoring-a-backup","p":353},{"i":361,"t":"Privacy & Security","u":"/docs/backup-restore","h":"#privacy--security","p":353},{"i":363,"t":"Fair Use Policy","u":"/docs/backup-restore","h":"#fair-use-policy","p":353},{"i":365,"t":"Self-Hosting the Backup Server","u":"/docs/backup-restore","h":"#self-hosting-the-backup-server","p":353},{"i":366,"t":"Quick Start","u":"/docs/backup-restore","h":"#quick-start","p":353},{"i":368,"t":"Populate wrangler.toml","u":"/docs/backup-restore","h":"#populate-wranglertoml","p":353},{"i":370,"t":"Complete index.js","u":"/docs/backup-restore","h":"#complete-indexjs","p":353},{"i":372,"t":"Commands","u":"/docs/backup-restore","h":"#commands","p":353},{"i":374,"t":"API","u":"/docs/backup-restore","h":"#api","p":353},{"i":378,"t":"There are three ways to edit the config","u":"/docs/configuring","h":"#there-are-three-ways-to-edit-the-config","p":376},{"i":380,"t":"Tips","u":"/docs/configuring","h":"#tips","p":376},{"i":382,"t":"Contents","u":"/docs/configuring","h":"#contents","p":376},{"i":384,"t":"Top-Level Fields","u":"/docs/configuring","h":"#top-level-fields","p":376},{"i":386,"t":"PageInfo","u":"/docs/configuring","h":"#pageinfo","p":376},{"i":388,"t":"pageInfo.navLinks (optional)","u":"/docs/configuring","h":"#pageinfonavlinks-optional","p":376},{"i":390,"t":"pages[] (optional)","u":"/docs/configuring","h":"#pages-optional","p":376},{"i":392,"t":"appConfig (optional)","u":"/docs/configuring","h":"#appconfig-optional","p":376},{"i":394,"t":"appConfig.auth (optional)","u":"/docs/configuring","h":"#appconfigauth-optional","p":376},{"i":396,"t":"appConfig.auth.users (optional)","u":"/docs/configuring","h":"#appconfigauthusers-optional","p":376},{"i":398,"t":"appConfig.auth.keycloak (optional)","u":"/docs/configuring","h":"#appconfigauthkeycloak-optional","p":376},{"i":400,"t":"appConfig.auth.headerAuth (optional)","u":"/docs/configuring","h":"#appconfigauthheaderauth-optional","p":376},{"i":402,"t":"appConfig.auth.oidc (optional)","u":"/docs/configuring","h":"#appconfigauthoidc-optional","p":376},{"i":404,"t":"appConfig.webSearch (optional)","u":"/docs/configuring","h":"#appconfigwebsearch-optional","p":376},{"i":406,"t":"appConfig.hideComponents (optional)","u":"/docs/configuring","h":"#appconfighidecomponents-optional","p":376},{"i":408,"t":"section","u":"/docs/configuring","h":"#section","p":376},{"i":410,"t":"section.item","u":"/docs/configuring","h":"#sectionitem","p":376},{"i":412,"t":"item.displayData (optional)","u":"/docs/configuring","h":"#itemdisplaydata-optional","p":376},{"i":414,"t":"section.widgets (optional)","u":"/docs/configuring","h":"#sectionwidgets-optional","p":376},{"i":416,"t":"section.displayData (optional)","u":"/docs/configuring","h":"#sectiondisplaydata-optional","p":376},{"i":418,"t":"section.icon and section.item.icon","u":"/docs/configuring","h":"#sectionicon-and-sectionitemicon","p":376},{"i":420,"t":"section.displayData.hideForKeycloakUsers, section.displayData.showForKeycloakUsers, item.displayData.hideForKeycloakUsers and item.displayData.showForKeycloakUsers","u":"/docs/configuring","h":"#sectiondisplaydatahideforkeycloakusers-sectiondisplaydatashowforkeycloakusers-itemdisplaydatahideforkeycloakusers-and-itemdisplaydatashowforkeycloakusers","p":376},{"i":422,"t":"Notes","u":"/docs/configuring","h":"#notes","p":376},{"i":423,"t":"Editing Config through the UI","u":"/docs/configuring","h":"#editing-config-through-the-ui","p":376},{"i":425,"t":"About YAML","u":"/docs/configuring","h":"#about-yaml","p":376},{"i":427,"t":"Config Saving Methods","u":"/docs/configuring","h":"#config-saving-methods","p":376},{"i":429,"t":"Preventing Changes","u":"/docs/configuring","h":"#preventing-changes","p":376},{"i":431,"t":"Example","u":"/docs/configuring","h":"#example","p":376},{"i":435,"t":"Take a 2-minute survey","u":"/docs/contributing","h":"#take-a-2-minute-survey","p":433},{"i":437,"t":"Share your dashboard","u":"/docs/contributing","h":"#share-your-dashboard","p":433},{"i":439,"t":"Make a small donation","u":"/docs/contributing","h":"#make-a-small-donation","p":433},{"i":441,"t":"Enable Anonymous Bug Reports","u":"/docs/contributing","h":"#enable-anonymous-bug-reports","p":433},{"i":443,"t":"Add Translations","u":"/docs/contributing","h":"#add-translations","p":433},{"i":445,"t":"Submit a PR","u":"/docs/contributing","h":"#submit-a-pr","p":433},{"i":447,"t":"Improve the Docs","u":"/docs/contributing","h":"#improve-the-docs","p":433},{"i":449,"t":"Raise a bug","u":"/docs/contributing","h":"#raise-a-bug","p":433},{"i":451,"t":"Join the discussion","u":"/docs/contributing","h":"#join-the-discussion","p":433},{"i":453,"t":"Request a feature via BountySource","u":"/docs/contributing","h":"#request-a-feature-via-bountysource","p":433},{"i":455,"t":"Spread the word","u":"/docs/contributing","h":"#spread-the-word","p":433},{"i":457,"t":"Star, Upvote or Leave a Review","u":"/docs/contributing","h":"#star-upvote-or-leave-a-review","p":433},{"i":459,"t":"Follow for More","u":"/docs/contributing","h":"#follow-for-more","p":433},{"i":461,"t":"Contributors","u":"/docs/contributing","h":"#contributors","p":433},{"i":463,"t":"Star-Gazers Over Time","u":"/docs/contributing","h":"#star-gazers-over-time","p":433},{"i":466,"t":"Sponsors","u":"/docs/credits","h":"#sponsors","p":465},{"i":467,"t":"Contributors","u":"/docs/credits","h":"#contributors","p":465},{"i":468,"t":"Newest Stargazers","u":"/docs/credits","h":"#newest-stargazers","p":465},{"i":469,"t":"Dependencies","u":"/docs/credits","h":"#dependencies","p":465},{"i":471,"t":"Core","u":"/docs/credits","h":"#core","p":465},{"i":473,"t":"Utilities","u":"/docs/credits","h":"#utilities","p":465},{"i":475,"t":"Frontend Components","u":"/docs/credits","h":"#frontend-components","p":465},{"i":477,"t":"You","u":"/docs/credits","h":"#you","p":465},{"i":481,"t":"Quick Start","u":"/docs/deployment","h":"#quick-start","p":479},{"i":483,"t":"Deployment Methods","u":"/docs/deployment","h":"#deployment-methods","p":479},{"i":485,"t":"Requirements","u":"/docs/deployment","h":"#requirements","p":479},{"i":486,"t":"Architecture","u":"/docs/deployment","h":"#architecture","p":479},{"i":488,"t":"System Resources","u":"/docs/deployment","h":"#system-resources","p":479},{"i":490,"t":"Bare Metal","u":"/docs/deployment","h":"#bare-metal","p":479},{"i":492,"t":"CDN / Cloud Deploy","u":"/docs/deployment","h":"#cdn--cloud-deploy","p":479},{"i":494,"t":"Browser Support","u":"/docs/deployment","h":"#browser-support","p":479},{"i":497,"t":"Build from Source","u":"/docs/deployment/bare-metal","h":"#build-from-source","p":496},{"i":499,"t":"Pre-Built Release","u":"/docs/deployment/bare-metal","h":"#pre-built-release","p":496},{"i":501,"t":"Systemd Service","u":"/docs/deployment/bare-metal","h":"#systemd-service","p":496},{"i":503,"t":"Checksum Verification","u":"/docs/deployment/bare-metal","h":"#checksum-verification","p":496},{"i":505,"t":"Provenance Attestation","u":"/docs/deployment/bare-metal","h":"#provenance-attestation","p":496},{"i":507,"t":"Install Script","u":"/docs/deployment/bare-metal","h":"#install-script","p":496},{"i":511,"t":"Static Hosting Providers","u":"/docs/deployment/cloud","h":"#static-hosting-providers","p":509},{"i":512,"t":"Netlify","u":"/docs/deployment/cloud","h":"#netlify","p":509},{"i":514,"t":"Vercel","u":"/docs/deployment/cloud","h":"#vercel","p":509},{"i":516,"t":"EdgeOne Pages","u":"/docs/deployment/cloud","h":"#edgeone-pages","p":509},{"i":518,"t":"Cloudflare Pages","u":"/docs/deployment/cloud","h":"#cloudflare-pages","p":509},{"i":520,"t":"Firebase Hosting","u":"/docs/deployment/cloud","h":"#firebase-hosting","p":509},{"i":522,"t":"Azure Static Web Apps","u":"/docs/deployment/cloud","h":"#azure-static-web-apps","p":509},{"i":524,"t":"Container Runtimes","u":"/docs/deployment/cloud","h":"#container-runtimes","p":509},{"i":526,"t":"Render","u":"/docs/deployment/cloud","h":"#render","p":509},{"i":528,"t":"Railway","u":"/docs/deployment/cloud","h":"#railway","p":509},{"i":530,"t":"Fly.io","u":"/docs/deployment/cloud","h":"#flyio","p":509},{"i":532,"t":"Koyeb","u":"/docs/deployment/cloud","h":"#koyeb","p":509},{"i":534,"t":"Northflank","u":"/docs/deployment/cloud","h":"#northflank","p":509},{"i":536,"t":"DigitalOcean App Platform","u":"/docs/deployment/cloud","h":"#digitalocean-app-platform","p":509},{"i":538,"t":"Azure Container Apps","u":"/docs/deployment/cloud","h":"#azure-container-apps","p":509},{"i":540,"t":"Google Cloud Run","u":"/docs/deployment/cloud","h":"#google-cloud-run","p":509},{"i":542,"t":"Managed Hosting","u":"/docs/deployment/cloud","h":"#managed-hosting","p":509},{"i":544,"t":"Hosting with any CDN","u":"/docs/deployment/cloud","h":"#hosting-with-any-cdn","p":509},{"i":546,"t":"Other VPS","u":"/docs/deployment/cloud","h":"#other-vps","p":509},{"i":550,"t":"Docker Run","u":"/docs/deployment/docker","h":"#docker-run","p":548},{"i":552,"t":"Docker Compose","u":"/docs/deployment/docker","h":"#docker-compose","p":548},{"i":554,"t":"Podman","u":"/docs/deployment/docker","h":"#podman","p":548},{"i":556,"t":"Read Next","u":"/docs/deployment/docker","h":"#read-next","p":548},{"i":560,"t":"TrueNAS SCALE","u":"/docs/deployment/self-hosted-os","h":"#truenas-scale","p":558},{"i":562,"t":"Proxmox VE","u":"/docs/deployment/self-hosted-os","h":"#proxmox-ve","p":558},{"i":564,"t":"Unraid","u":"/docs/deployment/self-hosted-os","h":"#unraid","p":558},{"i":566,"t":"Synology NAS","u":"/docs/deployment/self-hosted-os","h":"#synology-nas","p":558},{"i":568,"t":"NixOS","u":"/docs/deployment/self-hosted-os","h":"#nixos","p":558},{"i":570,"t":"Kubernetes","u":"/docs/deployment/self-hosted-os","h":"#kubernetes","p":558},{"i":574,"t":"Portainer","u":"/docs/deployment/self-hosted-platforms","h":"#portainer","p":572},{"i":576,"t":"Coolify","u":"/docs/deployment/self-hosted-platforms","h":"#coolify","p":572},{"i":578,"t":"1Panel","u":"/docs/deployment/self-hosted-platforms","h":"#1panel","p":572},{"i":580,"t":"Runtipi","u":"/docs/deployment/self-hosted-platforms","h":"#runtipi","p":572},{"i":582,"t":"Cosmos Cloud","u":"/docs/deployment/self-hosted-platforms","h":"#cosmos-cloud","p":572},{"i":584,"t":"CasaOS","u":"/docs/deployment/self-hosted-platforms","h":"#casaos","p":572},{"i":586,"t":"Umbrel","u":"/docs/deployment/self-hosted-platforms","h":"#umbrel","p":572},{"i":588,"t":"EasyPanel","u":"/docs/deployment/self-hosted-platforms","h":"#easypanel","p":572},{"i":590,"t":"Saltbox","u":"/docs/deployment/self-hosted-platforms","h":"#saltbox","p":572},{"i":594,"t":"Setting up the Dev Environment","u":"/docs/developing","h":"#setting-up-the-dev-environment","p":592},{"i":595,"t":"Prerequisites","u":"/docs/developing","h":"#prerequisites","p":592},{"i":597,"t":"Running the Project","u":"/docs/developing","h":"#running-the-project","p":592},{"i":599,"t":"Project Commands","u":"/docs/developing","h":"#project-commands","p":592},{"i":601,"t":"Environmental Variables","u":"/docs/developing","h":"#environmental-variables","p":592},{"i":603,"t":"Environment Modes","u":"/docs/developing","h":"#environment-modes","p":592},{"i":605,"t":"Git Strategy","u":"/docs/developing","h":"#git-strategy","p":592},{"i":606,"t":"Git Flow","u":"/docs/developing","h":"#git-flow","p":592},{"i":608,"t":"Git Branch Naming","u":"/docs/developing","h":"#git-branch-naming","p":592},{"i":610,"t":"Commit Emojis","u":"/docs/developing","h":"#commit-emojis","p":592},{"i":612,"t":"PR Guidelines","u":"/docs/developing","h":"#pr-guidelines","p":592},{"i":614,"t":"Resources for Beginners","u":"/docs/developing","h":"#resources-for-beginners","p":592},{"i":616,"t":"App Info","u":"/docs/developing","h":"#app-info","p":592},{"i":617,"t":"Style Guide","u":"/docs/developing","h":"#style-guide","p":592},{"i":619,"t":"Application Structure","u":"/docs/developing","h":"#application-structure","p":592},{"i":621,"t":"Development Tools","u":"/docs/developing","h":"#development-tools","p":592},{"i":622,"t":"Performance - Lighthouse","u":"/docs/developing","h":"#performance---lighthouse","p":592},{"i":624,"t":"Dependencies - BundlePhobia","u":"/docs/developing","h":"#dependencies---bundlephobia","p":592},{"i":628,"t":"Creating a new theme","u":"/docs/development-guides","h":"#creating-a-new-theme","p":626},{"i":630,"t":"1. Add Theme Name","u":"/docs/development-guides","h":"#1-add-theme-name","p":626},{"i":632,"t":"2. Write some Styles","u":"/docs/development-guides","h":"#2-write-some-styles","p":626},{"i":634,"t":"Writing Translations","u":"/docs/development-guides","h":"#writing-translations","p":626},{"i":636,"t":"1. Create a new Language File","u":"/docs/development-guides","h":"#1-create-a-new-language-file","p":626},{"i":638,"t":"2. Translate","u":"/docs/development-guides","h":"#2-translate","p":626},{"i":640,"t":"3. Add your file to the app","u":"/docs/development-guides","h":"#3-add-your-file-to-the-app","p":626},{"i":642,"t":"Adding a new option in the config file","u":"/docs/development-guides","h":"#adding-a-new-option-in-the-config-file","p":626},{"i":644,"t":"Updating Dependencies","u":"/docs/development-guides","h":"#updating-dependencies","p":626},{"i":646,"t":"Developing Netlify Cloud Functions","u":"/docs/development-guides","h":"#developing-netlify-cloud-functions","p":626},{"i":648,"t":"1. Run Netlify Dev Server","u":"/docs/development-guides","h":"#1-run-netlify-dev-server","p":626},{"i":650,"t":"2. Create a lambda function","u":"/docs/development-guides","h":"#2-create-a-lambda-function","p":626},{"i":652,"t":"3. Redirect the Node endpoint to the function","u":"/docs/development-guides","h":"#3-redirect-the-node-endpoint-to-the-function","p":626},{"i":654,"t":"Hiding Page Furniture on Certain Routes","u":"/docs/development-guides","h":"#hiding-page-furniture-on-certain-routes","p":626},{"i":656,"t":"1. Add the route name to the should hide array","u":"/docs/development-guides","h":"#1-add-the-route-name-to-the-should-hide-array","p":626},{"i":658,"t":"2. Add the conditional to the structural component to hide","u":"/docs/development-guides","h":"#2-add-the-conditional-to-the-structural-component-to-hide","p":626},{"i":660,"t":"Adding / Using Environmental Variables","u":"/docs/development-guides","h":"#adding--using-environmental-variables","p":626},{"i":662,"t":"Building a Widget","u":"/docs/development-guides","h":"#building-a-widget","p":626},{"i":663,"t":"Step 0 - Prerequisites","u":"/docs/development-guides","h":"#step-0---prerequisites","p":626},{"i":665,"t":"Step 1 - Create Widget","u":"/docs/development-guides","h":"#step-1---create-widget","p":626},{"i":667,"t":"Step 2 - Adding Functionality","u":"/docs/development-guides","h":"#step-2---adding-functionality","p":626},{"i":669,"t":"Step 3 - Register","u":"/docs/development-guides","h":"#step-3---register","p":626},{"i":671,"t":"Step 4 - Docs","u":"/docs/development-guides","h":"#step-4---docs","p":626},{"i":673,"t":"Respecting Config Permissions","u":"/docs/development-guides","h":"#respecting-config-permissions","p":626},{"i":677,"t":"Favicons","u":"/docs/icons","h":"#favicons","p":675},{"i":679,"t":"Font Awesome","u":"/docs/icons","h":"#font-awesome","p":675},{"i":681,"t":"Simple Icons","u":"/docs/icons","h":"#simple-icons","p":675},{"i":683,"t":"Generative Icons","u":"/docs/icons","h":"#generative-icons","p":675},{"i":685,"t":"Emoji Icons","u":"/docs/icons","h":"#emoji-icons","p":675},{"i":687,"t":"selfh.st Icons","u":"/docs/icons","h":"#selfhst-icons","p":675},{"i":689,"t":"Home-Lab Icons","u":"/docs/icons","h":"#home-lab-icons","p":675},{"i":691,"t":"Material Design Icons","u":"/docs/icons","h":"#material-design-icons","p":675},{"i":693,"t":"Icons by URL","u":"/docs/icons","h":"#icons-by-url","p":675},{"i":695,"t":"Local Icons","u":"/docs/icons","h":"#local-icons","p":675},{"i":697,"t":"Default Icon","u":"/docs/icons","h":"#default-icon","p":675},{"i":699,"t":"No Icon","u":"/docs/icons","h":"#no-icon","p":675},{"i":701,"t":"Icon Collections and Resources","u":"/docs/icons","h":"#icon-collections-and-resources","p":675},{"i":703,"t":"Notes","u":"/docs/icons","h":"#notes","p":675},{"i":706,"t":"MIT License","u":"/docs/license","h":"#mit-license","p":705},{"i":710,"t":"Contents","u":"/docs/management","h":"#contents","p":708},{"i":712,"t":"Providing Assets","u":"/docs/management","h":"#providing-assets","p":708},{"i":714,"t":"File Ownership and Permissions","u":"/docs/management","h":"#file-ownership-and-permissions","p":708},{"i":716,"t":"Running Commands","u":"/docs/management","h":"#running-commands","p":708},{"i":718,"t":"Healthchecks","u":"/docs/management","h":"#healthchecks","p":708},{"i":720,"t":"HTTP Healthcheck Endpoint","u":"/docs/management","h":"#http-healthcheck-endpoint","p":708},{"i":722,"t":"Logs and Performance","u":"/docs/management","h":"#logs-and-performance","p":708},{"i":723,"t":"Container Logs","u":"/docs/management","h":"#container-logs","p":708},{"i":725,"t":"Container Performance","u":"/docs/management","h":"#container-performance","p":708},{"i":727,"t":"Management Apps","u":"/docs/management","h":"#management-apps","p":708},{"i":729,"t":"Advanced Logging and Monitoring","u":"/docs/management","h":"#advanced-logging-and-monitoring","p":708},{"i":731,"t":"Auto-Starting at System Boot","u":"/docs/management","h":"#auto-starting-at-system-boot","p":708},{"i":733,"t":"Updating","u":"/docs/management","h":"#updating","p":708},{"i":735,"t":"Updating Docker Container","u":"/docs/management","h":"#updating-docker-container","p":708},{"i":737,"t":"Automatic Docker Updates","u":"/docs/management","h":"#automatic-docker-updates","p":708},{"i":739,"t":"Updating Dashy from Source","u":"/docs/management","h":"#updating-dashy-from-source","p":708},{"i":741,"t":"Verifying a Release Download","u":"/docs/management","h":"#verifying-a-release-download","p":708},{"i":743,"t":"Backing Up","u":"/docs/management","h":"#backing-up","p":708},{"i":744,"t":"Backing Up Containers","u":"/docs/management","h":"#backing-up-containers","p":708},{"i":746,"t":"Backing Up Volumes","u":"/docs/management","h":"#backing-up-volumes","p":708},{"i":748,"t":"Dashy-Specific Backup","u":"/docs/management","h":"#dashy-specific-backup","p":708},{"i":750,"t":"Scheduling","u":"/docs/management","h":"#scheduling","p":708},{"i":752,"t":"SSL Certificates","u":"/docs/management","h":"#ssl-certificates","p":708},{"i":754,"t":"Auto-SSL","u":"/docs/management","h":"#auto-ssl","p":708},{"i":756,"t":"Getting a Self-Signed SSL Certificate","u":"/docs/management","h":"#getting-a-self-signed-ssl-certificate","p":708},{"i":758,"t":"Passing a Self-Signed Certificate to Dashy","u":"/docs/management","h":"#passing-a-self-signed-certificate-to-dashy","p":708},{"i":760,"t":"Authentication","u":"/docs/management","h":"#authentication","p":708},{"i":762,"t":"Network Exposure","u":"/docs/management","h":"#network-exposure","p":708},{"i":764,"t":"Managing Containers with Docker Compose","u":"/docs/management","h":"#managing-containers-with-docker-compose","p":708},{"i":766,"t":"Passing in Environmental Variables","u":"/docs/management","h":"#passing-in-environmental-variables","p":708},{"i":768,"t":"Setting Headers","u":"/docs/management","h":"#setting-headers","p":708},{"i":770,"t":"Example Headers","u":"/docs/management","h":"#example-headers","p":708},{"i":772,"t":"Remote Access","u":"/docs/management","h":"#remote-access","p":708},{"i":774,"t":"WireGuard","u":"/docs/management","h":"#wireguard","p":708},{"i":776,"t":"Reverse SSH Tunnel","u":"/docs/management","h":"#reverse-ssh-tunnel","p":708},{"i":778,"t":"TCP Tunnel","u":"/docs/management","h":"#tcp-tunnel","p":708},{"i":780,"t":"Custom Domain","u":"/docs/management","h":"#custom-domain","p":708},{"i":782,"t":"Using DNS","u":"/docs/management","h":"#using-dns","p":708},{"i":784,"t":"Using NGINX","u":"/docs/management","h":"#using-nginx","p":708},{"i":786,"t":"Verifying Releases","u":"/docs/management","h":"#verifying-releases","p":708},{"i":788,"t":"Container Security","u":"/docs/management","h":"#container-security","p":708},{"i":790,"t":"Keep Docker Up-To-Date","u":"/docs/management","h":"#keep-docker-up-to-date","p":708},{"i":792,"t":"Set Resource Quotas","u":"/docs/management","h":"#set-resource-quotas","p":708},{"i":794,"t":"Don't Run as Root","u":"/docs/management","h":"#dont-run-as-root","p":708},{"i":796,"t":"Specify a User","u":"/docs/management","h":"#specify-a-user","p":708},{"i":798,"t":"Limit capabilities","u":"/docs/management","h":"#limit-capabilities","p":708},{"i":800,"t":"Prevent new Privileges being Added","u":"/docs/management","h":"#prevent-new-privileges-being-added","p":708},{"i":802,"t":"Disable Inter-Container Communication","u":"/docs/management","h":"#disable-inter-container-communication","p":708},{"i":804,"t":"Don't Expose the Docker Daemon Socket","u":"/docs/management","h":"#dont-expose-the-docker-daemon-socket","p":708},{"i":806,"t":"Use Read-Only Volumes","u":"/docs/management","h":"#use-read-only-volumes","p":708},{"i":808,"t":"Set the Logging Level","u":"/docs/management","h":"#set-the-logging-level","p":708},{"i":810,"t":"Verify Image before Pulling","u":"/docs/management","h":"#verify-image-before-pulling","p":708},{"i":812,"t":"Specify the Tag","u":"/docs/management","h":"#specify-the-tag","p":708},{"i":814,"t":"Container Security Scanning","u":"/docs/management","h":"#container-security-scanning","p":708},{"i":816,"t":"Registry Security","u":"/docs/management","h":"#registry-security","p":708},{"i":818,"t":"Security Modules","u":"/docs/management","h":"#security-modules","p":708},{"i":820,"t":"Web Server Configuration","u":"/docs/management","h":"#web-server-configuration","p":708},{"i":822,"t":"NGINX","u":"/docs/management","h":"#nginx-1","p":708},{"i":824,"t":"Apache","u":"/docs/management","h":"#apache-1","p":708},{"i":826,"t":"Caddy","u":"/docs/management","h":"#caddy-1","p":708},{"i":828,"t":"Firebase Hosting","u":"/docs/management","h":"#firebase-hosting","p":708},{"i":830,"t":"cPanel","u":"/docs/management","h":"#cpanel","p":708},{"i":832,"t":"Running a Modified Version of the App","u":"/docs/management","h":"#running-a-modified-version-of-the-app","p":708},{"i":834,"t":"Building your Own Container","u":"/docs/management","h":"#building-your-own-container","p":708},{"i":838,"t":"Setting Language","u":"/docs/multi-language-support","h":"#setting-language","p":836},{"i":840,"t":"In the UI","u":"/docs/multi-language-support","h":"#in-the-ui","p":836},{"i":842,"t":"Config File","u":"/docs/multi-language-support","h":"#config-file","p":836},{"i":844,"t":"Available Languages","u":"/docs/multi-language-support","h":"#available-languages","p":836},{"i":846,"t":"Adding a new Language","u":"/docs/multi-language-support","h":"#adding-a-new-language","p":836},{"i":848,"t":"1. Create a new Language File","u":"/docs/multi-language-support","h":"#1-create-a-new-language-file","p":836},{"i":850,"t":"2. Translate","u":"/docs/multi-language-support","h":"#2-translate","p":836},{"i":852,"t":"3. Add your file to the app","u":"/docs/multi-language-support","h":"#3-add-your-file-to-the-app","p":836},{"i":854,"t":"Checking Translation Coverage","u":"/docs/multi-language-support","h":"#checking-translation-coverage","p":836},{"i":856,"t":"Adding New Text to a Component","u":"/docs/multi-language-support","h":"#adding-new-text-to-a-component","p":836},{"i":858,"t":"1. Add Translated Text","u":"/docs/multi-language-support","h":"#1-add-translated-text","p":836},{"i":860,"t":"2. Use Text within Component","u":"/docs/multi-language-support","h":"#2-use-text-within-component","p":836},{"i":862,"t":"Basic Example","u":"/docs/multi-language-support","h":"#basic-example","p":836},{"i":865,"t":"Page Metadata","u":"/docs/pages-and-sections","h":"#page-metadata","p":864},{"i":867,"t":"Multi-Page Support","u":"/docs/pages-and-sections","h":"#multi-page-support","p":864},{"i":869,"t":"Using Local Sub-Pages","u":"/docs/pages-and-sections","h":"#using-local-sub-pages","p":864},{"i":871,"t":"Using Remote Sub-Pages","u":"/docs/pages-and-sections","h":"#using-remote-sub-pages","p":864},{"i":873,"t":"Restrictions","u":"/docs/pages-and-sections","h":"#restrictions","p":864},{"i":875,"t":"URL Structure","u":"/docs/pages-and-sections","h":"#url-structure","p":864},{"i":877,"t":"Layout","u":"/docs/pages-and-sections","h":"#layout","p":864},{"i":879,"t":"Making a section wider or taller","u":"/docs/pages-and-sections","h":"#making-a-section-wider-or-taller","p":864},{"i":881,"t":"Items inside a section","u":"/docs/pages-and-sections","h":"#items-inside-a-section","p":864},{"i":883,"t":"Sub-Items","u":"/docs/pages-and-sections","h":"#sub-items","p":864},{"i":887,"t":"Contents","u":"/docs/privacy","h":"#contents","p":885},{"i":889,"t":"Browser Storage","u":"/docs/privacy","h":"#browser-storage","p":885},{"i":891,"t":"Cookies","u":"/docs/privacy","h":"#cookies","p":885},{"i":893,"t":"Session Storage","u":"/docs/privacy","h":"#session-storage","p":885},{"i":895,"t":"Local Storage","u":"/docs/privacy","h":"#local-storage","p":885},{"i":897,"t":"Deleting Stored Data","u":"/docs/privacy","h":"#deleting-stored-data","p":885},{"i":899,"t":"External Requests","u":"/docs/privacy","h":"#external-requests","p":885},{"i":901,"t":"Icons","u":"/docs/privacy","h":"#icons","p":885},{"i":903,"t":"Themes","u":"/docs/privacy","h":"#themes","p":885},{"i":905,"t":"Status Checking","u":"/docs/privacy","h":"#status-checking","p":885},{"i":907,"t":"Update Checks","u":"/docs/privacy","h":"#update-checks","p":885},{"i":909,"t":"Cloud Backup","u":"/docs/privacy","h":"#cloud-backup","p":885},{"i":911,"t":"Web Search","u":"/docs/privacy","h":"#web-search","p":885},{"i":913,"t":"Initialization Page","u":"/docs/privacy","h":"#initialization-page","p":885},{"i":915,"t":"Anonymous Error Reporting","u":"/docs/privacy","h":"#anonymous-error-reporting","p":885},{"i":917,"t":"Widgets","u":"/docs/privacy","h":"#widgets","p":885},{"i":919,"t":"Dependencies","u":"/docs/privacy","h":"#dependencies","p":885},{"i":921,"t":"Securing your Environment","u":"/docs/privacy","h":"#securing-your-environment","p":885},{"i":923,"t":"Security Features","u":"/docs/privacy","h":"#security-features","p":885},{"i":924,"t":"Subresource Integrity","u":"/docs/privacy","h":"#subresource-integrity","p":885},{"i":926,"t":"SSL","u":"/docs/privacy","h":"#ssl","p":885},{"i":928,"t":"Authentication","u":"/docs/privacy","h":"#authentication","p":885},{"i":930,"t":"Configuration Lockdown","u":"/docs/privacy","h":"#configuration-lockdown","p":885},{"i":932,"t":"Disabling Features","u":"/docs/privacy","h":"#disabling-features","p":885},{"i":934,"t":"Threat Model","u":"/docs/privacy","h":"#threat-model","p":885},{"i":936,"t":"Intended Deployment","u":"/docs/privacy","h":"#intended-deployment","p":885},{"i":938,"t":"Trust Boundaries","u":"/docs/privacy","h":"#trust-boundaries","p":885},{"i":940,"t":"Assets","u":"/docs/privacy","h":"#assets","p":885},{"i":942,"t":"When Dashy is NOT the Right Choice","u":"/docs/privacy","h":"#when-dashy-is-not-the-right-choice","p":885},{"i":944,"t":"Known Limitations","u":"/docs/privacy","h":"#known-limitations","p":885},{"i":946,"t":"Update & Patch Policy","u":"/docs/privacy","h":"#update--patch-policy","p":885},{"i":948,"t":"Reporting a Security Issue","u":"/docs/privacy","h":"#reporting-a-security-issue","p":885},{"i":952,"t":"1. Prerequisites","u":"/docs/quick-start","h":"#1-prerequisites","p":950},{"i":954,"t":"2. Installation","u":"/docs/quick-start","h":"#2-installation","p":950},{"i":956,"t":"3. User Data Directory","u":"/docs/quick-start","h":"#3-user-data-directory","p":950},{"i":958,"t":"4. Configure","u":"/docs/quick-start","h":"#4-configure","p":950},{"i":960,"t":"5. Further Customisation","u":"/docs/quick-start","h":"#5-further-customisation","p":950},{"i":962,"t":"6. Final Note","u":"/docs/quick-start","h":"#6-final-note","p":950},{"i":964,"t":"Alternative Deployment Method 1 - From Source","u":"/docs/quick-start","h":"#alternative-deployment-method-1---from-source","p":950},{"i":966,"t":"Alternative Deployment Method 2 - Netlify","u":"/docs/quick-start","h":"#alternative-deployment-method-2---netlify","p":950},{"i":968,"t":"Alternative Deployment Method 3 - Cloud Services","u":"/docs/quick-start","h":"#alternative-deployment-method-3---cloud-services","p":950},{"i":972,"t":"Versioning and Releases","u":"/docs/release-workflow","h":"#versioning-and-releases","p":970},{"i":973,"t":"High-level tag process","u":"/docs/release-workflow","h":"#high-level-tag-process","p":970},{"i":975,"t":"Creating a Release","u":"/docs/release-workflow","h":"#creating-a-release","p":970},{"i":977,"t":"Versioning","u":"/docs/release-workflow","h":"#versioning","p":970},{"i":979,"t":"Changelog","u":"/docs/release-workflow","h":"#changelog","p":970},{"i":981,"t":"Workflows","u":"/docs/release-workflow","h":"#workflows","p":970},{"i":982,"t":"CI","u":"/docs/release-workflow","h":"#ci","p":970},{"i":984,"t":"Docker","u":"/docs/release-workflow","h":"#docker","p":970},{"i":986,"t":"Release","u":"/docs/release-workflow","h":"#release","p":970},{"i":988,"t":"Tag","u":"/docs/release-workflow","h":"#tag","p":970},{"i":990,"t":"Mirror","u":"/docs/release-workflow","h":"#mirror","p":970},{"i":992,"t":"Docs","u":"/docs/release-workflow","h":"#docs","p":970},{"i":994,"t":"Git Strategy","u":"/docs/release-workflow","h":"#git-strategy","p":970},{"i":995,"t":"Commits","u":"/docs/release-workflow","h":"#commits","p":970},{"i":997,"t":"Branches","u":"/docs/release-workflow","h":"#branches","p":970},{"i":999,"t":"Pull Requests","u":"/docs/release-workflow","h":"#pull-requests","p":970},{"i":1002,"t":"Searching","u":"/docs/searching","h":"#searching","p":1001},{"i":1004,"t":"Navigating","u":"/docs/searching","h":"#navigating","p":1001},{"i":1006,"t":"Launching Apps","u":"/docs/searching","h":"#launching-apps","p":1001},{"i":1008,"t":"Tags","u":"/docs/searching","h":"#tags","p":1001},{"i":1010,"t":"Hiding Items From The Homepage","u":"/docs/searching","h":"#hiding-items-from-the-homepage","p":1001},{"i":1012,"t":"Custom Hotkeys","u":"/docs/searching","h":"#custom-hotkeys","p":1001},{"i":1014,"t":"Web Search","u":"/docs/searching","h":"#web-search","p":1001},{"i":1016,"t":"Setting Search Engine","u":"/docs/searching","h":"#setting-search-engine","p":1001},{"i":1018,"t":"Using Custom Search Engine","u":"/docs/searching","h":"#using-custom-search-engine","p":1001},{"i":1020,"t":"Setting Opening Method","u":"/docs/searching","h":"#setting-opening-method","p":1001},{"i":1022,"t":"Using Bangs","u":"/docs/searching","h":"#using-bangs","p":1001},{"i":1024,"t":"Disabling Web Search","u":"/docs/searching","h":"#disabling-web-search","p":1001},{"i":1026,"t":"Opening URLs Directly","u":"/docs/searching","h":"#opening-urls-directly","p":1001},{"i":1028,"t":"Clearing Search","u":"/docs/searching","h":"#clearing-search","p":1001},{"i":1032,"t":"MNDashboard","u":"/docs/showcase","h":"#mndashboard","p":1030},{"i":1034,"t":"Home Lab 2.0","u":"/docs/showcase","h":"#home-lab-20","p":1030},{"i":1035,"t":"Dipan's Dash","u":"/docs/showcase","h":"#dipans-dash","p":1030},{"i":1037,"t":"Ratty222","u":"/docs/showcase","h":"#ratty222","p":1030},{"i":1039,"t":"Hugalafutro Dashy","u":"/docs/showcase","h":"#hugalafutro-dashy","p":1030},{"i":1041,"t":"NAS Home Dashboard","u":"/docs/showcase","h":"#nas-home-dashboard","p":1030},{"i":1043,"t":"Brewhack","u":"/docs/showcase","h":"#brewhack","p":1030},{"i":1045,"t":"The Dragons Lair","u":"/docs/showcase","h":"#the-dragons-lair","p":1030},{"i":1047,"t":"Homelab & VPS dashboard","u":"/docs/showcase","h":"#homelab--vps-dashboard","p":1030},{"i":1049,"t":"Raspberry PI Docker Dashboard","u":"/docs/showcase","h":"#raspberry-pi-docker-dashboard","p":1030},{"i":1051,"t":"First Week of Self-Hosting","u":"/docs/showcase","h":"#first-week-of-self-hosting","p":1030},{"i":1053,"t":"EVO Dashboard","u":"/docs/showcase","h":"#evo-dashboard","p":1030},{"i":1055,"t":"The Private Dashboard","u":"/docs/showcase","h":"#the-private-dashboard","p":1030},{"i":1057,"t":"Networking Services","u":"/docs/showcase","h":"#networking-services","p":1030},{"i":1059,"t":"Dashy Live","u":"/docs/showcase","h":"#dashy-live","p":1030},{"i":1061,"t":"System Monitor","u":"/docs/showcase","h":"#system-monitor","p":1030},{"i":1063,"t":"Browser Startpage","u":"/docs/showcase","h":"#browser-startpage","p":1030},{"i":1064,"t":"CFT Toolbox","u":"/docs/showcase","h":"#cft-toolbox","p":1030},{"i":1065,"t":"Bookmarks","u":"/docs/showcase","h":"#bookmarks","p":1030},{"i":1066,"t":"Project Management","u":"/docs/showcase","h":"#project-management","p":1030},{"i":1067,"t":"Dashy Example","u":"/docs/showcase","h":"#dashy-example","p":1030},{"i":1069,"t":"HomeLAb 3.0","u":"/docs/showcase","h":"#homelab-30","p":1030},{"i":1071,"t":"Ground Control","u":"/docs/showcase","h":"#ground-control","p":1030},{"i":1073,"t":"Croco_Grievous","u":"/docs/showcase","h":"#croco_grievous","p":1030},{"i":1075,"t":"Crypto Dash","u":"/docs/showcase","h":"#crypto-dash","p":1030},{"i":1077,"t":"Stefantigro","u":"/docs/showcase","h":"#stefantigro","p":1030},{"i":1079,"t":"HomeLab 3.0","u":"/docs/showcase","h":"#homelab-30-1","p":1030},{"i":1081,"t":"Morning Dashboard","u":"/docs/showcase","h":"#morning-dashboard","p":1030},{"i":1083,"t":"Yet Another Homelab","u":"/docs/showcase","h":"#yet-another-homelab","p":1030},{"i":1084,"t":"Submitting your Dashboard","u":"/docs/showcase","h":"#submitting-your-dashboard","p":1030},{"i":1085,"t":"How to Submit","u":"/docs/showcase","h":"#how-to-submit","p":1030},{"i":1087,"t":"What to Include","u":"/docs/showcase","h":"#what-to-include","p":1030},{"i":1089,"t":"Template","u":"/docs/showcase","h":"#template","p":1030},{"i":1093,"t":"MNDashboard","u":"/docs/showcase/","h":"#mndashboard","p":1091},{"i":1095,"t":"Home Lab 2.0","u":"/docs/showcase/","h":"#home-lab-20","p":1091},{"i":1096,"t":"Dipan's Dash","u":"/docs/showcase/","h":"#dipans-dash","p":1091},{"i":1098,"t":"Ratty222","u":"/docs/showcase/","h":"#ratty222","p":1091},{"i":1100,"t":"Hugalafutro Dashy","u":"/docs/showcase/","h":"#hugalafutro-dashy","p":1091},{"i":1102,"t":"NAS Home Dashboard","u":"/docs/showcase/","h":"#nas-home-dashboard","p":1091},{"i":1104,"t":"Brewhack","u":"/docs/showcase/","h":"#brewhack","p":1091},{"i":1106,"t":"The Dragons Lair","u":"/docs/showcase/","h":"#the-dragons-lair","p":1091},{"i":1108,"t":"Homelab & VPS dashboard","u":"/docs/showcase/","h":"#homelab--vps-dashboard","p":1091},{"i":1110,"t":"Raspberry PI Docker Dashboard","u":"/docs/showcase/","h":"#raspberry-pi-docker-dashboard","p":1091},{"i":1112,"t":"First Week of Self-Hosting","u":"/docs/showcase/","h":"#first-week-of-self-hosting","p":1091},{"i":1114,"t":"EVO Dashboard","u":"/docs/showcase/","h":"#evo-dashboard","p":1091},{"i":1116,"t":"The Private Dashboard","u":"/docs/showcase/","h":"#the-private-dashboard","p":1091},{"i":1118,"t":"Networking Services","u":"/docs/showcase/","h":"#networking-services","p":1091},{"i":1120,"t":"Dashy Live","u":"/docs/showcase/","h":"#dashy-live","p":1091},{"i":1122,"t":"System Monitor","u":"/docs/showcase/","h":"#system-monitor","p":1091},{"i":1124,"t":"Browser Startpage","u":"/docs/showcase/","h":"#browser-startpage","p":1091},{"i":1125,"t":"CFT Toolbox","u":"/docs/showcase/","h":"#cft-toolbox","p":1091},{"i":1126,"t":"Bookmarks","u":"/docs/showcase/","h":"#bookmarks","p":1091},{"i":1127,"t":"Project Management","u":"/docs/showcase/","h":"#project-management","p":1091},{"i":1128,"t":"Dashy Example","u":"/docs/showcase/","h":"#dashy-example","p":1091},{"i":1130,"t":"HomeLAb 3.0","u":"/docs/showcase/","h":"#homelab-30","p":1091},{"i":1132,"t":"Ground Control","u":"/docs/showcase/","h":"#ground-control","p":1091},{"i":1134,"t":"Croco_Grievous","u":"/docs/showcase/","h":"#croco_grievous","p":1091},{"i":1136,"t":"Crypto Dash","u":"/docs/showcase/","h":"#crypto-dash","p":1091},{"i":1138,"t":"Stefantigro","u":"/docs/showcase/","h":"#stefantigro","p":1091},{"i":1140,"t":"HomeLab 3.0","u":"/docs/showcase/","h":"#homelab-30-1","p":1091},{"i":1142,"t":"Morning Dashboard","u":"/docs/showcase/","h":"#morning-dashboard","p":1091},{"i":1144,"t":"Yet Another Homelab","u":"/docs/showcase/","h":"#yet-another-homelab","p":1091},{"i":1145,"t":"Submitting your Dashboard","u":"/docs/showcase/","h":"#submitting-your-dashboard","p":1091},{"i":1146,"t":"How to Submit","u":"/docs/showcase/","h":"#how-to-submit","p":1091},{"i":1148,"t":"What to Include","u":"/docs/showcase/","h":"#what-to-include","p":1091},{"i":1150,"t":"Template","u":"/docs/showcase/","h":"#template","p":1091},{"i":1154,"t":"Enabling Status Indicators","u":"/docs/status-indicators","h":"#enabling-status-indicators","p":1152},{"i":1156,"t":"Continuous Checking","u":"/docs/status-indicators","h":"#continuous-checking","p":1152},{"i":1158,"t":"Using a Different Endpoint","u":"/docs/status-indicators","h":"#using-a-different-endpoint","p":1152},{"i":1160,"t":"Setting Custom Headers","u":"/docs/status-indicators","h":"#setting-custom-headers","p":1152},{"i":1162,"t":"Disabling Security","u":"/docs/status-indicators","h":"#disabling-security","p":1152},{"i":1164,"t":"Allowing Alternative Status Codes","u":"/docs/status-indicators","h":"#allowing-alternative-status-codes","p":1152},{"i":1166,"t":"Troubleshooting Failing Status Checks","u":"/docs/status-indicators","h":"#troubleshooting-failing-status-checks","p":1152},{"i":1168,"t":"Color-Blind Accessibility","u":"/docs/status-indicators","h":"#color-blind-accessibility","p":1152},{"i":1170,"t":"How it Works","u":"/docs/status-indicators","h":"#how-it-works","p":1152},{"i":1174,"t":"How Theme-Switching Works","u":"/docs/theming","h":"#how-theme-switching-works","p":1172},{"i":1176,"t":"Adding Your Own Theme","u":"/docs/theming","h":"#adding-your-own-theme","p":1172},{"i":1177,"t":"Option 1: Colors-only, in conf.yml","u":"/docs/theming","h":"#option-1-colors-only-in-confyml","p":1172},{"i":1179,"t":"Option 2: Full CSS file","u":"/docs/theming","h":"#option-2-full-css-file","p":1172},{"i":1181,"t":"Modifying Theme Colors","u":"/docs/theming","h":"#modifying-theme-colors","p":1172},{"i":1183,"t":"Setting Custom CSS in the UI","u":"/docs/theming","h":"#setting-custom-css-in-the-ui","p":1172},{"i":1185,"t":"Page-Specific Styles","u":"/docs/theming","h":"#page-specific-styles","p":1172},{"i":1187,"t":"Loading External Stylesheets","u":"/docs/theming","h":"#loading-external-stylesheets","p":1172},{"i":1189,"t":"Hard-Coding Section or Item Colors","u":"/docs/theming","h":"#hard-coding-section-or-item-colors","p":1172},{"i":1191,"t":"Typography","u":"/docs/theming","h":"#typography","p":1172},{"i":1193,"t":"Changing browser address bar / tab color","u":"/docs/theming","h":"#changing-browser-address-bar--tab-color","p":1172},{"i":1195,"t":"CSS Variables","u":"/docs/theming","h":"#css-variables","p":1172},{"i":1197,"t":"Top-Level Variables","u":"/docs/theming","h":"#top-level-variables","p":1172},{"i":1199,"t":"Targeted Color Variables","u":"/docs/theming","h":"#targeted-color-variables","p":1172},{"i":1201,"t":"Non-Color Variables","u":"/docs/theming","h":"#non-color-variables","p":1172},{"i":1203,"t":"Action Colors","u":"/docs/theming","h":"#action-colors","p":1172},{"i":1207,"t":"Contents","u":"/docs/troubleshooting","h":"#contents","p":1205},{"i":1209,"t":"Config not saving","u":"/docs/troubleshooting","h":"#config-not-saving","p":1205},{"i":1211,"t":"Permission denied or read-only filesystem (EACCES, EROFS)","u":"/docs/troubleshooting","h":"#permission-denied-or-read-only-filesystem-eacces-erofs","p":1205},{"i":1213,"t":"Kubernetes ConfigMap mount is read-only","u":"/docs/troubleshooting","h":"#kubernetes-configmap-mount-is-read-only","p":1205},{"i":1215,"t":"SELinux or AppArmor blocks the write","u":"/docs/troubleshooting","h":"#selinux-or-apparmor-blocks-the-write","p":1205},{"i":1217,"t":"Backup step fails so save aborts","u":"/docs/troubleshooting","h":"#backup-step-fails-so-save-aborts","p":1205},{"i":1219,"t":"Save button is missing or returns 403 Forbidden","u":"/docs/troubleshooting","h":"#save-button-is-missing-or-returns-403-forbidden","p":1205},{"i":1221,"t":"Save unavailable on Vercel, Netlify or other static hosts","u":"/docs/troubleshooting","h":"#save-unavailable-on-vercel-netlify-or-other-static-hosts","p":1205},{"i":1223,"t":"/config-manager/save returns 404 or HTML","u":"/docs/troubleshooting","h":"#config-managersave-returns-404-or-html","p":1205},{"i":1225,"t":"\"Invalid filename\" when saving a sub-page","u":"/docs/troubleshooting","h":"#invalid-filename-when-saving-a-sub-page","p":1205},{"i":1227,"t":"\"Cannot save to an external URL\"","u":"/docs/troubleshooting","h":"#cannot-save-to-an-external-url","p":1205},{"i":1229,"t":"Saved successfully but the UI shows the old config","u":"/docs/troubleshooting","h":"#saved-successfully-but-the-ui-shows-the-old-config","p":1205},{"i":1231,"t":"Container crashes or restart loop after saving (3.1.0 and 3.1.1 only)","u":"/docs/troubleshooting","h":"#container-crashes-or-restart-loop-after-saving-310-and-311-only","p":1205},{"i":1233,"t":"Intentionally read-only mode","u":"/docs/troubleshooting","h":"#intentionally-read-only-mode","p":1205},{"i":1235,"t":"Refused to Connect in Modal or Workspace View","u":"/docs/troubleshooting","h":"#refused-to-connect-in-modal-or-workspace-view","p":1205},{"i":1237,"t":"NGINX","u":"/docs/troubleshooting","h":"#nginx","p":1205},{"i":1239,"t":"Caddy","u":"/docs/troubleshooting","h":"#caddy","p":1205},{"i":1241,"t":"Apache","u":"/docs/troubleshooting","h":"#apache","p":1205},{"i":1243,"t":"LightHttpd","u":"/docs/troubleshooting","h":"#lighthttpd","p":1205},{"i":1245,"t":"404 / Routing issues","u":"/docs/troubleshooting","h":"#404--routing-issues","p":1205},{"i":1246,"t":"404 On Static Hosting","u":"/docs/troubleshooting","h":"#404-on-static-hosting","p":1205},{"i":1248,"t":"404 after Launch from Mobile Home Screen","u":"/docs/troubleshooting","h":"#404-after-launch-from-mobile-home-screen","p":1205},{"i":1250,"t":"404 On Multi-Page Apps","u":"/docs/troubleshooting","h":"#404-on-multi-page-apps","p":1205},{"i":1252,"t":"Dashy hosted at a sub-path (e.g. example.com/dashy)","u":"/docs/troubleshooting","h":"#dashy-hosted-at-a-sub-path-eg-examplecomdashy","p":1205},{"i":1254,"t":"Sub-pages","u":"/docs/troubleshooting","h":"#sub-pages","p":1205},{"i":1255,"t":"Sub-page shows \"Unable to find config for ...\"","u":"/docs/troubleshooting","h":"#sub-page-shows-unable-to-find-config-for-","p":1205},{"i":1257,"t":"Sub-page missing from nav, or won't open when clicked","u":"/docs/troubleshooting","h":"#sub-page-missing-from-nav-or-wont-open-when-clicked","p":1205},{"i":1259,"t":"Sub-page ignores its theme, layout or appConfig","u":"/docs/troubleshooting","h":"#sub-page-ignores-its-theme-layout-or-appconfig","p":1205},{"i":1261,"t":"Sub-config files return 404","u":"/docs/troubleshooting","h":"#sub-config-files-return-404","p":1205},{"i":1263,"t":"Remote Config Not Loading","u":"/docs/troubleshooting","h":"#remote-config-not-loading","p":1205},{"i":1265,"t":"Build & memory errors","u":"/docs/troubleshooting","h":"#build--memory-errors","p":1205},{"i":1266,"t":"Yarn Error","u":"/docs/troubleshooting","h":"#yarn-error","p":1205},{"i":1268,"t":"yarn build fails inside the container","u":"/docs/troubleshooting","h":"#yarn-build-fails-inside-the-container","p":1205},{"i":1270,"t":"High CPU or RAM Usage on Startup","u":"/docs/troubleshooting","h":"#high-cpu-or-ram-usage-on-startup","p":1205},{"i":1272,"t":"Ineffective mark-compacts near heap limit Allocation failed","u":"/docs/troubleshooting","h":"#ineffective-mark-compacts-near-heap-limit-allocation-failed","p":1205},{"i":1274,"t":"Command failed with signal \"SIGKILL\"","u":"/docs/troubleshooting","h":"#command-failed-with-signal-sigkill","p":1205},{"i":1276,"t":"Node Sass does not yet support your current environment","u":"/docs/troubleshooting","h":"#node-sass-does-not-yet-support-your-current-environment","p":1205},{"i":1278,"t":"Unreachable Code Error","u":"/docs/troubleshooting","h":"#unreachable-code-error","p":1205},{"i":1280,"t":"Error: Cannot find module './_baseValues'","u":"/docs/troubleshooting","h":"#error-cannot-find-module-_basevalues","p":1205},{"i":1282,"t":"Auth & OIDC","u":"/docs/troubleshooting","h":"#auth--oidc","p":1205},{"i":1283,"t":"Auth Validation Error: \"should be object\"","u":"/docs/troubleshooting","h":"#auth-validation-error-should-be-object","p":1205},{"i":1285,"t":"Keycloak Redirect Error","u":"/docs/troubleshooting","h":"#keycloak-redirect-error","p":1205},{"i":1287,"t":"OIDC or Keycloak failure on numeric client IDs","u":"/docs/troubleshooting","h":"#oidc-or-keycloak-failure-on-numeric-client-ids","p":1205},{"i":1289,"t":"Redirect loop after login","u":"/docs/troubleshooting","h":"#redirect-loop-after-login","p":1205},{"i":1291,"t":"invalid_redirect_uri","u":"/docs/troubleshooting","h":"#invalid_redirect_uri","p":1205},{"i":1293,"t":"Login works in the browser but the dashboard refuses to save anything (403)","u":"/docs/troubleshooting","h":"#login-works-in-the-browser-but-the-dashboard-refuses-to-save-anything-403","p":1205},{"i":1295,"t":"Logged in but no admin controls","u":"/docs/troubleshooting","h":"#logged-in-but-no-admin-controls","p":1205},{"i":1297,"t":"Login works but Dashy errors on the callback with \"OIDC signinCallback returned no user\"","u":"/docs/troubleshooting","h":"#login-works-but-dashy-errors-on-the-callback-with-oidc-signincallback-returned-no-user","p":1205},{"i":1299,"t":"Sign-out leaves you stuck on Authentik","u":"/docs/troubleshooting","h":"#sign-out-leaves-you-stuck-on-authentik","p":1205},{"i":1301,"t":"Untrusted certificate from Authentik","u":"/docs/troubleshooting","h":"#untrusted-certificate-from-authentik","p":1205},{"i":1303,"t":"Numeric client_id getting truncated","u":"/docs/troubleshooting","h":"#numeric-client_id-getting-truncated","p":1205},{"i":1305,"t":"Header auth: \"Unauthorized - not from trusted proxy\"","u":"/docs/troubleshooting","h":"#header-auth-unauthorized---not-from-trusted-proxy","p":1205},{"i":1307,"t":"Header auth: \"Unauthorized - missing user header\"","u":"/docs/troubleshooting","h":"#header-auth-unauthorized---missing-user-header","p":1205},{"i":1309,"t":"\"Invalid user object\" warning on startup with an all-digit hash or placeholder","u":"/docs/troubleshooting","h":"#invalid-user-object-warning-on-startup-with-an-all-digit-hash-or-placeholder","p":1205},{"i":1311,"t":"OIDC login looks like a CORS error in devtools, but the IdP is configured for CORS correctly","u":"/docs/troubleshooting","h":"#oidc-login-looks-like-a-cors-error-in-devtools-but-the-idp-is-configured-for-cors-correctly","p":1205},{"i":1313,"t":"Docker & image issues","u":"/docs/troubleshooting","h":"#docker--image-issues","p":1205},{"i":1314,"t":"App Not Starting After Update to 2.0.4","u":"/docs/troubleshooting","h":"#app-not-starting-after-update-to-204","p":1205},{"i":1316,"t":"Mount Type Mismatch","u":"/docs/troubleshooting","h":"#mount-type-mismatch","p":1205},{"i":1318,"t":"DockerHub toomanyrequests","u":"/docs/troubleshooting","h":"#dockerhub-toomanyrequests","p":1205},{"i":1320,"t":"Old image tags fail to pull","u":"/docs/troubleshooting","h":"#old-image-tags-fail-to-pull","p":1205},{"i":1322,"t":"Healthcheck Failing in Docker","u":"/docs/troubleshooting","h":"#healthcheck-failing-in-docker","p":1205},{"i":1324,"t":"Docker Login Fails on Ubuntu","u":"/docs/troubleshooting","h":"#docker-login-fails-on-ubuntu","p":1205},{"i":1326,"t":"Styles and Assets not Updating","u":"/docs/troubleshooting","h":"#styles-and-assets-not-updating","p":1205},{"i":1328,"t":"Config Validation Errors","u":"/docs/troubleshooting","h":"#config-validation-errors","p":1205},{"i":1330,"t":"Invalid Host Header while running through ngrok","u":"/docs/troubleshooting","h":"#invalid-host-header-while-running-through-ngrok","p":1205},{"i":1332,"t":"Warnings in the Console during deploy","u":"/docs/troubleshooting","h":"#warnings-in-the-console-during-deploy","p":1205},{"i":1334,"t":"Status Checks Failing","u":"/docs/troubleshooting","h":"#status-checks-failing","p":1205},{"i":1336,"t":"Widgets","u":"/docs/troubleshooting","h":"#widgets","p":1205},{"i":1337,"t":"Widget Errors","u":"/docs/troubleshooting","h":"#widget-errors","p":1205},{"i":1339,"t":"Widget CORS Errors","u":"/docs/troubleshooting","h":"#widget-cors-errors","p":1205},{"i":1341,"t":"CORS Proxy connect ECONNREFUSED ... or getaddrinfo ENOTFOUND ...","u":"/docs/troubleshooting","h":"#cors-proxy-connect-econnrefused--or-getaddrinfo-enotfound-","p":1205},{"i":1343,"t":"CORS Proxy Target-URL host '...' is blocked / must use http:// or https://","u":"/docs/troubleshooting","h":"#cors-proxy-target-url-host--is-blocked--must-use-http-or-https","p":1205},{"i":1345,"t":"Widget Shows Error Incorrectly","u":"/docs/troubleshooting","h":"#widget-shows-error-incorrectly","p":1205},{"i":1347,"t":"Weather Forecast Widget 401","u":"/docs/troubleshooting","h":"#weather-forecast-widget-401","p":1205},{"i":1349,"t":"Widget Displaying Inaccurate Data","u":"/docs/troubleshooting","h":"#widget-displaying-inaccurate-data","p":1205},{"i":1351,"t":"Public IP Widget not working for ipinfo or ipquery providers","u":"/docs/troubleshooting","h":"#public-ip-widget-not-working-for-ipinfo-or-ipquery-providers","p":1205},{"i":1353,"t":"Font Awesome Icons not Displaying","u":"/docs/troubleshooting","h":"#font-awesome-icons-not-displaying","p":1205},{"i":1355,"t":"Copy to Clipboard not Working","u":"/docs/troubleshooting","h":"#copy-to-clipboard-not-working","p":1205},{"i":1357,"t":"How-To / Reference","u":"/docs/troubleshooting","h":"#how-to--reference","p":1205},{"i":1358,"t":"How to Reset Local Settings","u":"/docs/troubleshooting","h":"#how-to-reset-local-settings","p":1205},{"i":1360,"t":"How to make a bug report","u":"/docs/troubleshooting","h":"#how-to-make-a-bug-report","p":1205},{"i":1362,"t":"How-To Open Browser Console","u":"/docs/troubleshooting","h":"#how-to-open-browser-console","p":1205},{"i":1364,"t":"Git Contributions not Displaying","u":"/docs/troubleshooting","h":"#git-contributions-not-displaying","p":1205},{"i":1368,"t":"Contents","u":"/docs/widgets","h":"#contents","p":1366},{"i":1370,"t":"General Widgets","u":"/docs/widgets","h":"#general-widgets","p":1366},{"i":1371,"t":"Clock","u":"/docs/widgets","h":"#clock","p":1366},{"i":1373,"t":"Weather","u":"/docs/widgets","h":"#weather","p":1366},{"i":1375,"t":"Weather Forecast","u":"/docs/widgets","h":"#weather-forecast","p":1366},{"i":1377,"t":"RSS Feed","u":"/docs/widgets","h":"#rss-feed","p":1366},{"i":1379,"t":"Image","u":"/docs/widgets","h":"#image","p":1366},{"i":1381,"t":"Public IP","u":"/docs/widgets","h":"#public-ip","p":1366},{"i":1383,"t":"IP Blacklist","u":"/docs/widgets","h":"#ip-blacklist","p":1366},{"i":1385,"t":"Domain Monitor","u":"/docs/widgets","h":"#domain-monitor","p":1366},{"i":1387,"t":"Crypto Watch List","u":"/docs/widgets","h":"#crypto-watch-list","p":1366},{"i":1389,"t":"Crypto Token Price History","u":"/docs/widgets","h":"#crypto-token-price-history","p":1366},{"i":1391,"t":"Wallet Balance","u":"/docs/widgets","h":"#wallet-balance","p":1366},{"i":1393,"t":"Code Stats","u":"/docs/widgets","h":"#code-stats","p":1366},{"i":1395,"t":"Mullvad Status","u":"/docs/widgets","h":"#mullvad-status","p":1366},{"i":1397,"t":"addy.io","u":"/docs/widgets","h":"#addyio","p":1366},{"i":1399,"t":"Vulnerability Feed","u":"/docs/widgets","h":"#vulnerability-feed","p":1366},{"i":1401,"t":"Exchange Rates","u":"/docs/widgets","h":"#exchange-rates","p":1366},{"i":1403,"t":"Public Holidays","u":"/docs/widgets","h":"#public-holidays","p":1366},{"i":1405,"t":"Covid-19 Status","u":"/docs/widgets","h":"#covid-19-status","p":1366},{"i":1407,"t":"Sports Scores","u":"/docs/widgets","h":"#sports-scores","p":1366},{"i":1409,"t":"News Headlines","u":"/docs/widgets","h":"#news-headlines","p":1366},{"i":1411,"t":"TFL Status","u":"/docs/widgets","h":"#tfl-status","p":1366},{"i":1413,"t":"Stock Price History","u":"/docs/widgets","h":"#stock-price-history","p":1366},{"i":1415,"t":"ETH Gas Prices","u":"/docs/widgets","h":"#eth-gas-prices","p":1366},{"i":1417,"t":"Joke","u":"/docs/widgets","h":"#joke","p":1366},{"i":1419,"t":"Chuck Norris quotes","u":"/docs/widgets","h":"#chuck-norris-quotes","p":1366},{"i":1421,"t":"XKCD Comics","u":"/docs/widgets","h":"#xkcd-comics","p":1366},{"i":1423,"t":"Flight Data","u":"/docs/widgets","h":"#flight-data","p":1366},{"i":1425,"t":"Astronomy Picture of the Day","u":"/docs/widgets","h":"#astronomy-picture-of-the-day","p":1366},{"i":1427,"t":"GitHub Trending","u":"/docs/widgets","h":"#github-trending","p":1366},{"i":1429,"t":"GitHub Profile Stats","u":"/docs/widgets","h":"#github-profile-stats","p":1366},{"i":1431,"t":"HealthChecks Status","u":"/docs/widgets","h":"#healthchecks-status","p":1366},{"i":1433,"t":"Hackernews Trending","u":"/docs/widgets","h":"#hackernews-trending","p":1366},{"i":1435,"t":"MVG Departure","u":"/docs/widgets","h":"#mvg-departure","p":1366},{"i":1437,"t":"MVG Connection","u":"/docs/widgets","h":"#mvg-connection","p":1366},{"i":1439,"t":"Custom List","u":"/docs/widgets","h":"#custom-list","p":1366},{"i":1441,"t":"Custom search","u":"/docs/widgets","h":"#custom-search","p":1366},{"i":1443,"t":"RescueTime Overview","u":"/docs/widgets","h":"#rescuetime-overview","p":1366},{"i":1445,"t":"Minecraft Server","u":"/docs/widgets","h":"#minecraft-server","p":1366},{"i":1447,"t":"Self-Hosted Services Widgets","u":"/docs/widgets","h":"#self-hosted-services-widgets","p":1366},{"i":1448,"t":"System Info","u":"/docs/widgets","h":"#system-info","p":1366},{"i":1450,"t":"CPU History (NetData)","u":"/docs/widgets","h":"#cpu-history-netdata","p":1366},{"i":1452,"t":"Memory History (NetData)","u":"/docs/widgets","h":"#memory-history-netdata","p":1366},{"i":1454,"t":"Load History (NetData)","u":"/docs/widgets","h":"#load-history-netdata","p":1366},{"i":1456,"t":"Pi-Hole Stats","u":"/docs/widgets","h":"#pi-hole-stats","p":1366},{"i":1458,"t":"Pi-Hole Stats v6","u":"/docs/widgets","h":"#pi-hole-stats-v6","p":1366},{"i":1460,"t":"Pi-Hole Queries","u":"/docs/widgets","h":"#pi-hole-queries","p":1366},{"i":1462,"t":"Pi-Hole Queries v6","u":"/docs/widgets","h":"#pi-hole-queries-v6","p":1366},{"i":1464,"t":"Pi-Hole Recent Traffic","u":"/docs/widgets","h":"#pi-hole-recent-traffic","p":1366},{"i":1466,"t":"Pi-Hole Recent Traffic v6","u":"/docs/widgets","h":"#pi-hole-recent-traffic-v6","p":1366},{"i":1468,"t":"Stat Ping Statuses","u":"/docs/widgets","h":"#stat-ping-statuses","p":1366},{"i":1470,"t":"Synology Download Station","u":"/docs/widgets","h":"#synology-download-station","p":1366},{"i":1472,"t":"AdGuard Home Block Stats","u":"/docs/widgets","h":"#adguard-home-block-stats","p":1366},{"i":1474,"t":"AdGuard Home Filters","u":"/docs/widgets","h":"#adguard-home-filters","p":1366},{"i":1476,"t":"AdGuard Home DNS Info","u":"/docs/widgets","h":"#adguard-home-dns-info","p":1366},{"i":1478,"t":"AdGuard Home Top Domains","u":"/docs/widgets","h":"#adguard-home-top-domains","p":1366},{"i":1480,"t":"Nextcloud User","u":"/docs/widgets","h":"#nextcloud-user","p":1366},{"i":1482,"t":"Nextcloud User Statuses","u":"/docs/widgets","h":"#nextcloud-user-statuses","p":1366},{"i":1484,"t":"Nextcloud Notifications","u":"/docs/widgets","h":"#nextcloud-notifications","p":1366},{"i":1486,"t":"Nextcloud System","u":"/docs/widgets","h":"#nextcloud-system","p":1366},{"i":1488,"t":"Nextcloud Stats","u":"/docs/widgets","h":"#nextcloud-stats","p":1366},{"i":1490,"t":"Nextcloud PHP OPcache Stats","u":"/docs/widgets","h":"#nextcloud-php-opcache-stats","p":1366},{"i":1492,"t":"ntfy stream","u":"/docs/widgets","h":"#ntfy-stream","p":1366},{"i":1494,"t":"Proxmox lists","u":"/docs/widgets","h":"#proxmox-lists","p":1366},{"i":1496,"t":"Sabnzbd","u":"/docs/widgets","h":"#sabnzbd","p":1366},{"i":1498,"t":"Gluetun VPN Info","u":"/docs/widgets","h":"#gluetun-vpn-info","p":1366},{"i":1500,"t":"Drone CI Builds","u":"/docs/widgets","h":"#drone-ci-builds","p":1366},{"i":1502,"t":"Filebrowser","u":"/docs/widgets","h":"#filebrowser","p":1366},{"i":1504,"t":"Linkding","u":"/docs/widgets","h":"#linkding","p":1366},{"i":1506,"t":"Uptime Kuma","u":"/docs/widgets","h":"#uptime-kuma","p":1366},{"i":1508,"t":"Uptime Kuma Status Page","u":"/docs/widgets","h":"#uptime-kuma-status-page","p":1366},{"i":1510,"t":"Tactical RMM","u":"/docs/widgets","h":"#tactical-rmm","p":1366},{"i":1512,"t":"System Resource Monitoring","u":"/docs/widgets","h":"#system-resource-monitoring","p":1366},{"i":1513,"t":"Glances","u":"/docs/widgets","h":"#glances","p":1366},{"i":1515,"t":"Current CPU Usage","u":"/docs/widgets","h":"#current-cpu-usage","p":1366},{"i":1517,"t":"Current CPU Usage Speedometer","u":"/docs/widgets","h":"#current-cpu-usage-speedometer","p":1366},{"i":1519,"t":"CPU Usage Per Core","u":"/docs/widgets","h":"#cpu-usage-per-core","p":1366},{"i":1521,"t":"CPU Usage History","u":"/docs/widgets","h":"#cpu-usage-history","p":1366},{"i":1523,"t":"Current Memory Usage","u":"/docs/widgets","h":"#current-memory-usage","p":1366},{"i":1525,"t":"Current Memory Usage Speedometer","u":"/docs/widgets","h":"#current-memory-usage-speedometer","p":1366},{"i":1527,"t":"Memory Usage History","u":"/docs/widgets","h":"#memory-usage-history","p":1366},{"i":1529,"t":"Disk Space","u":"/docs/widgets","h":"#disk-space","p":1366},{"i":1531,"t":"Disk IO","u":"/docs/widgets","h":"#disk-io","p":1366},{"i":1533,"t":"System Load","u":"/docs/widgets","h":"#system-load","p":1366},{"i":1535,"t":"Uptime","u":"/docs/widgets","h":"#uptime","p":1366},{"i":1537,"t":"System Load History","u":"/docs/widgets","h":"#system-load-history","p":1366},{"i":1539,"t":"Network Interfaces","u":"/docs/widgets","h":"#network-interfaces","p":1366},{"i":1541,"t":"Network Traffic","u":"/docs/widgets","h":"#network-traffic","p":1366},{"i":1543,"t":"Resource Usage Alerts","u":"/docs/widgets","h":"#resource-usage-alerts","p":1366},{"i":1545,"t":"IP Address","u":"/docs/widgets","h":"#ip-address","p":1366},{"i":1547,"t":"CPU Temp","u":"/docs/widgets","h":"#cpu-temp","p":1366},{"i":1549,"t":"Compact Metrics","u":"/docs/widgets","h":"#compact-metrics","p":1366},{"i":1551,"t":"Dynamic Widgets","u":"/docs/widgets","h":"#dynamic-widgets","p":1366},{"i":1552,"t":"Iframe Widget","u":"/docs/widgets","h":"#iframe-widget","p":1366},{"i":1554,"t":"HTML Embedded Widget","u":"/docs/widgets","h":"#html-embedded-widget","p":1366},{"i":1556,"t":"API Response","u":"/docs/widgets","h":"#api-response","p":1366},{"i":1558,"t":"Prometheus Data","u":"/docs/widgets","h":"#prometheus-data","p":1366},{"i":1560,"t":"Data Feed","u":"/docs/widgets","h":"#data-feed","p":1366},{"i":1562,"t":"Usage & Customizations","u":"/docs/widgets","h":"#usage--customizations","p":1366},{"i":1563,"t":"Widget Usage Guide","u":"/docs/widgets","h":"#widget-usage-guide","p":1366},{"i":1565,"t":"Continuous Updates","u":"/docs/widgets","h":"#continuous-updates","p":1366},{"i":1567,"t":"Proxying Requests","u":"/docs/widgets","h":"#proxying-requests","p":1366},{"i":1569,"t":"Handling Secrets","u":"/docs/widgets","h":"#handling-secrets","p":1366},{"i":1571,"t":"Setting Timeout","u":"/docs/widgets","h":"#setting-timeout","p":1366},{"i":1573,"t":"Adding Labels","u":"/docs/widgets","h":"#adding-labels","p":1366},{"i":1575,"t":"Ignoring Errors","u":"/docs/widgets","h":"#ignoring-errors","p":1366},{"i":1577,"t":"Widget Styling","u":"/docs/widgets","h":"#widget-styling","p":1366},{"i":1579,"t":"Customizing Charts","u":"/docs/widgets","h":"#customizing-charts","p":1366},{"i":1581,"t":"Language Translations","u":"/docs/widgets","h":"#language-translations","p":1366},{"i":1583,"t":"Widget UI Options","u":"/docs/widgets","h":"#widget-ui-options","p":1366},{"i":1585,"t":"Build your own Widget","u":"/docs/widgets","h":"#build-your-own-widget","p":1366},{"i":1587,"t":"Requesting a Widget","u":"/docs/widgets","h":"#requesting-a-widget","p":1366},{"i":1589,"t":"Troubleshooting Widget Errors","u":"/docs/widgets","h":"#troubleshooting-widget-errors","p":1366},{"i":1591,"t":"Raising an Issue","u":"/docs/widgets","h":"#raising-an-issue","p":1366}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/3",[0,7.235]],["t/5",[1,7.685]],["t/7",[2,7.685]],["t/9",[0,5.979,3,6.915]],["t/11",[4,5.48,5,4.999]],["t/15",[6,4.999,7,5.701]],["t/17",[8,6.217]],["t/19",[7,5.701,9,5.137]],["t/21",[10,6.899]],["t/23",[11,5.094,12,4.512,13,4.512]],["t/25",[14,5.645]],["t/27",[15,6.217]],["t/29",[16,4.858,17,3.343,18,4.669]],["t/33",[19,5.979,20,4.999]],["t/35",[21,6.049]],["t/37",[8,5.137,22,5.137]],["t/39",[23,5.901]],["t/41",[24,5.411,25,4.858,26,4.512]],["t/43",[5,4.259,8,4.377,27,4.512]],["t/45",[20,4.259,28,5.892,29,3.263]],["t/49",[30,5.335]],["t/51",[31,3.635,32,3.896,33,4.858]],["t/53",[33,4.858,34,3.58,35,3.757]],["t/54",[36,4.858,37,5.411,38,5.892]],["t/56",[39,4.266,40,6.915]],["t/58",[35,4.409,41,6.915]],["t/60",[42,6.899]],["t/62",[6,3.71,33,4.232,43,3.394,44,2.81]],["t/64",[45,4.259,46,4.06,47,4.512]],["t/66",[9,3.377,29,2.518,48,3.481,49,3.603,50,3.603]],["t/68",[33,4.232,51,3.462,52,3.71,53,3.394]],["t/70",[54,5.084]],["t/72",[55,4.765,56,4.488]],["t/74",[56,4.488,57,4.409]],["t/76",[58,5.701,59,5.48]],["t/80",[30,5.335]],["t/82",[31,3.635,32,3.896,60,4.512]],["t/84",[34,3.58,35,3.757,60,4.512]],["t/85",[46,4.06,61,3.263,62,5.892]],["t/87",[23,4.154,61,3.263,63,4.377]],["t/89",[61,3.829,64,5.979]],["t/91",[46,4.06,61,3.263,65,4.259]],["t/93",[39,3.635,61,3.263,66,4.858]],["t/95",[29,2.842,44,2.81,67,4.713,68,3.537]],["t/97",[6,3.71,43,3.394,44,2.81,60,3.93]],["t/99",[45,4.259,46,4.06,47,4.512]],["t/101",[9,3.377,29,2.518,48,3.481,49,3.603,50,3.603]],["t/103",[51,3.462,52,3.71,53,3.394,60,3.93]],["t/105",[15,5.137,69,4.409]],["t/107",[54,5.084]],["t/111",[30,5.335]],["t/113",[70,5.979,71,5.48]],["t/115",[39,4.266,72,6.915]],["t/117",[36,4.858,73,5.892,74,5.094]],["t/119",[74,3.528,75,2.479,76,4.08,77,4.08,78,3.747,79,4.08]],["t/121",[80,5.892,81,5.892,82,4.669]],["t/123",[55,4.06,56,3.824,83,4.259]],["t/125",[39,2.805,57,2.899,84,3.931,85,4.175,86,4.175]],["t/127",[44,2.489,83,3.287,87,3.603,88,4.546,89,2.722]],["t/129",[17,2.58,68,3.133,90,4.175,91,3.931,92,4.546]],["t/131",[68,4.765,91,5.979]],["t/133",[17,2.912,39,3.166,47,3.93,93,3.813]],["t/135",[12,3.481,13,3.481,47,3.481,94,6.278]],["t/137",[8,4.377,95,5.094,96,5.411]],["t/139",[19,3.931,20,3.287,51,3.067,52,3.287,53,3.006]],["t/141",[97,4.665,98,6.915]],["t/143",[54,5.084]],["t/144",[55,3.133,56,2.951,82,3.603,83,3.287,99,4.175]],["t/146",[57,3.757,82,4.669,84,5.094]],["t/150",[30,5.335]],["t/152",[31,3.635,100,5.411,101,5.094]],["t/154",[26,3.93,34,3.118,61,2.842,102,4.067]],["t/156",[43,3.896,102,4.669,103,4.06]],["t/158",[45,3.287,68,3.133,102,3.603,104,3.206,105,3.931]],["t/160",[35,3.757,44,3.225,48,4.512]],["t/162",[106,5.411,107,5.411,108,5.411]],["t/164",[26,3.481,51,3.067,52,3.287,53,3.006,102,3.603]],["t/166",[54,5.084]],["t/167",[56,4.488,57,4.409]],["t/171",[30,5.335]],["t/173",[35,4.409,44,3.785]],["t/175",[35,4.409,109,5.295]],["t/177",[51,5.645]],["t/179",[18,5.48,97,4.665]],["t/181",[54,5.084]],["t/185",[30,5.335]],["t/187",[21,4.259,31,3.635,32,3.896]],["t/189",[21,4.259,34,3.58,35,3.757]],["t/190",[21,4.259,61,3.263,110,5.411]],["t/192",[111,4.713,112,5.132,113,4.067,114,5.132]],["t/194",[44,3.225,55,4.06,61,3.263]],["t/196",[93,3.813,104,3.619,110,4.713,115,4.713]],["t/198",[44,2.81,61,2.842,65,3.71,93,3.813]],["t/200",[27,3.93,46,3.537,65,3.71,75,3.118]],["t/202",[39,3.635,61,3.263,66,4.858]],["t/204",[42,6.899]],["t/206",[6,3.71,21,3.71,43,3.394,44,2.81]],["t/208",[45,4.259,46,4.06,93,4.377]],["t/210",[21,3.71,51,3.462,52,3.71,53,3.394]],["t/212",[54,5.084]],["t/214",[55,4.765,56,4.488]],["t/216",[56,4.488,57,4.409]],["t/218",[115,6.35,116,6.915]],["t/220",[58,5.701,59,5.48]],["t/223",[117,6.131,118,5.094]],["t/225",[23,5.901]],["t/227",[65,4.999,68,4.765]],["t/229",[63,4.377,119,5.094,120,5.892]],["t/231",[68,4.765,91,5.979]],["t/233",[75,4.201,121,6.915]],["t/235",[54,3.118,56,3.331,57,3.272,84,4.438]],["t/237",[9,4.377,49,4.669,50,4.669]],["t/241",[20,4.259,109,4.512,122,5.411]],["t/243",[24,5.411,25,4.858,26,4.512]],["t/245",[17,2.58,75,2.762,123,3.481,124,4.546,125,4.546]],["t/247",[126,7.685]],["t/249",[68,4.06,127,4.669,128,5.892]],["t/251",[8,4.377,57,3.757,129,4.512]],["t/253",[55,4.06,130,4.669,131,5.892]],["t/255",[17,2.912,63,3.813,132,5.132,133,5.132]],["t/257",[63,4.377,134,4.377,135,3.694]],["t/261",[30,5.335]],["t/263",[31,3.166,32,3.394,136,4.232,137,4.067]],["t/265",[34,3.118,35,3.272,136,4.232,137,4.067]],["t/266",[65,3.71,138,4.438,139,4.713,140,5.132]],["t/268",[23,4.154,55,4.06,61,3.263]],["t/270",[46,4.06,61,3.263,65,4.259]],["t/272",[39,3.635,61,3.263,66,4.858]],["t/274",[42,6.899]],["t/276",[6,3.287,43,3.006,44,2.489,136,3.748,137,3.603]],["t/278",[45,4.259,46,4.06,47,4.512]],["t/280",[9,3.377,29,2.518,48,3.481,49,3.603,50,3.603]],["t/282",[51,3.067,52,3.287,53,3.006,136,3.748,137,3.603]],["t/284",[54,5.084]],["t/286",[55,4.765,56,4.488]],["t/288",[56,4.488,57,4.409]],["t/290",[58,5.701,59,5.48]],["t/294",[30,5.335]],["t/296",[100,6.35,101,5.979]],["t/298",[141,6.915,142,5.979]],["t/300",[17,2.58,44,2.489,101,3.931,103,3.133,142,3.931]],["t/302",[35,4.409,44,3.785]],["t/304",[29,2.842,68,3.537,143,5.132,144,4.232]],["t/306",[75,3.58,78,5.411,145,5.892]],["t/308",[107,6.35,108,6.35]],["t/310",[51,3.462,52,3.71,53,3.394,142,4.438]],["t/312",[54,5.084]],["t/313",[56,4.488,57,4.409]],["t/317",[30,5.335]],["t/319",[31,3.635,32,3.896,146,4.858]],["t/321",[34,3.58,35,3.757,146,4.858]],["t/323",[61,3.829,147,5.48]],["t/325",[23,4.154,61,3.263,64,5.094]],["t/327",[61,3.263,65,4.259,93,4.377]],["t/329",[39,3.635,93,4.377,148,5.892]],["t/331",[46,3.537,93,3.813,149,5.132,150,5.132]],["t/333",[39,3.635,61,3.263,66,4.858]],["t/335",[42,6.899]],["t/337",[6,3.71,43,3.394,44,2.81,146,4.232]],["t/339",[45,4.259,46,4.06,47,4.512]],["t/341",[9,3.377,29,2.518,48,3.481,49,3.603,50,3.603]],["t/343",[51,3.462,52,3.71,53,3.394,146,4.232]],["t/345",[54,5.084]],["t/347",[55,4.765,56,4.488]],["t/349",[56,4.488,57,4.409]],["t/351",[58,5.701,59,5.48]],["t/355",[54,5.084]],["t/357",[61,3.829,151,5.295]],["t/359",[151,5.295,152,6.915]],["t/361",[17,3.343,97,3.975,153,5.892]],["t/363",[75,3.58,105,5.094,154,5.892]],["t/365",[57,3.272,135,3.218,151,3.93,155,3.93]],["t/366",[70,5.979,71,5.48]],["t/368",[156,6.915,157,6.915]],["t/370",[158,6.915,159,6.915]],["t/372",[160,6.899]],["t/374",[7,6.899]],["t/378",[69,3.272,161,5.132,162,5.132,163,4.713]],["t/380",[164,8.368]],["t/382",[30,5.335]],["t/384",[165,5.094,166,4.858,167,5.892]],["t/386",[168,8.368]],["t/388",[29,3.829,169,6.915]],["t/390",[29,3.829,89,4.14]],["t/392",[29,3.829,170,6.35]],["t/394",[29,3.829,171,6.915]],["t/396",[29,3.829,172,6.915]],["t/398",[29,3.829,173,6.915]],["t/400",[29,3.829,174,6.915]],["t/402",[29,3.829,175,6.915]],["t/404",[29,3.829,176,6.915]],["t/406",[29,3.829,177,6.915]],["t/408",[12,6.408]],["t/410",[178,8.368]],["t/412",[29,3.829,179,6.915]],["t/414",[29,3.829,180,6.915]],["t/416",[29,3.829,181,6.915]],["t/418",[182,6.915,183,6.915]],["t/420",[184,5.132,185,5.132,186,5.132,187,5.132]],["t/422",[18,6.631]],["t/423",[69,3.272,163,4.713,188,4.713,189,4.067]],["t/425",[190,8.368]],["t/427",[5,4.259,69,3.757,191,4.06]],["t/429",[192,6.35,193,6.35]],["t/431",[15,6.217]],["t/435",[34,3.118,194,5.132,195,5.132,196,5.132]],["t/437",[197,6.915,198,4.266]],["t/439",[199,5.094,200,5.892,201,5.892]],["t/441",[6,3.71,202,4.713,203,4.438,204,4.232]],["t/443",[104,4.876,205,5.137]],["t/445",[206,5.48,207,6.35]],["t/447",[208,6.915,209,5.979]],["t/449",[203,5.979,210,6.35]],["t/451",[211,6.915,212,6.915]],["t/453",[85,4.713,213,4.067,214,4.438,215,5.132]],["t/455",[216,6.915,217,6.915]],["t/457",[218,4.713,219,5.132,220,4.713,221,5.132]],["t/459",[222,6.915,223,6.915]],["t/461",[224,7.685]],["t/463",[139,4.713,218,4.713,225,5.132,226,5.132]],["t/466",[227,8.368]],["t/467",[224,7.685]],["t/468",[228,6.915,229,6.915]],["t/469",[230,6.899]],["t/471",[231,7.685]],["t/473",[232,8.368]],["t/475",[96,6.35,233,5.701]],["t/477",[]],["t/481",[70,5.979,71,5.48]],["t/483",[5,4.999,32,4.573]],["t/485",[234,8.368]],["t/486",[235,8.368]],["t/488",[236,4.876,237,5.295]],["t/490",[238,6.915,239,6.915]],["t/492",[17,2.912,32,3.394,134,3.813,240,4.713]],["t/494",[241,5.137,242,5.979]],["t/497",[243,5.137,244,5.979]],["t/499",[19,5.094,245,5.892,246,4.512]],["t/501",[123,5.295,247,6.915]],["t/503",[248,6.915,249,6.915]],["t/505",[250,6.915,251,6.915]],["t/507",[252,6.35,253,6.915]],["t/511",[63,4.377,87,4.669,135,3.694]],["t/512",[254,6.631]],["t/514",[255,7.685]],["t/516",[89,4.14,256,6.915]],["t/518",[89,4.14,102,5.48]],["t/520",[135,4.335,257,6.35]],["t/522",[87,4.067,129,3.93,258,4.713,259,3.462]],["t/524",[260,4.488,261,6.915]],["t/526",[262,8.368]],["t/528",[263,8.368]],["t/530",[264,8.368]],["t/532",[265,8.368]],["t/534",[266,8.368]],["t/536",[259,3.975,267,5.892,268,5.892]],["t/538",[258,5.411,259,3.975,260,3.824]],["t/540",[103,4.06,134,4.377,269,5.892]],["t/542",[135,4.335,270,5.48]],["t/544",[135,4.335,240,6.35]],["t/546",[271,7.235]],["t/550",[103,4.765,272,4.488]],["t/552",[272,4.488,273,6.35]],["t/554",[274,8.368]],["t/556",[275,5.48,276,6.915]],["t/560",[277,6.915,278,6.915]],["t/562",[279,6.35,280,6.915]],["t/564",[281,8.368]],["t/566",[282,6.35,283,5.979]],["t/568",[284,8.368]],["t/570",[285,7.685]],["t/574",[286,8.368]],["t/576",[287,8.368]],["t/578",[288,8.368]],["t/580",[289,8.368]],["t/582",[134,5.137,290,6.915]],["t/584",[291,8.368]],["t/586",[292,8.368]],["t/588",[293,8.368]],["t/590",[294,8.368]],["t/594",[295,3.462,296,4.067,297,4.713,298,4.232]],["t/595",[299,7.235]],["t/597",[103,4.765,147,5.48]],["t/599",[147,5.48,160,5.701]],["t/601",[300,5.979,301,5.137]],["t/603",[82,5.48,298,5.701]],["t/605",[302,5.48,303,6.35]],["t/606",[99,6.35,302,5.48]],["t/608",[302,4.669,304,5.411,305,5.094]],["t/610",[306,6.35,307,6.35]],["t/612",[207,6.35,308,6.915]],["t/614",[237,5.295,309,6.915]],["t/616",[259,4.665,310,5.701]],["t/617",[118,5.979,311,5.48]],["t/619",[64,5.979,312,5.979]],["t/621",[313,6.35,314,6.915]],["t/622",[315,5.979,316,6.915]],["t/624",[230,5.701,317,6.915]],["t/628",[61,3.263,318,4.259,319,4.377]],["t/630",[31,3.166,104,3.619,305,4.438,319,3.813]],["t/632",[34,3.58,311,4.669,320,5.094]],["t/634",[205,5.137,320,5.979]],["t/636",[31,2.805,61,2.518,318,3.287,321,3.481,322,3.206]],["t/638",[34,4.201,205,5.137]],["t/640",[43,3.394,104,3.619,259,3.462,322,3.619]],["t/642",[29,2.518,69,2.899,318,3.287,322,3.206,323,3.287]],["t/644",[14,4.665,230,5.701]],["t/646",[134,3.813,254,4.067,313,4.713,324,4.232]],["t/648",[31,2.805,57,2.899,103,3.133,254,3.603,297,4.175]],["t/650",[34,3.118,61,2.842,324,4.232,325,5.132]],["t/652",[10,3.748,43,3.006,324,3.748,326,3.931,327,4.175]],["t/654",[89,2.722,328,3.748,329,4.546,330,4.546,331,3.931]],["t/656",[31,2.517,104,2.877,305,3.528,328,3.364,331,3.528,332,4.08]],["t/658",[34,2.479,104,2.877,233,3.364,312,3.528,328,3.364,333,4.08]],["t/660",[17,2.58,75,2.762,300,3.931,301,3.377,323,3.287]],["t/662",[243,5.137,334,3.974]],["t/663",[117,4.377,299,5.094,335,5.892]],["t/665",[31,3.166,61,2.842,117,3.813,334,2.949]],["t/667",[34,3.118,117,3.813,323,3.71,324,4.232]],["t/669",[43,3.896,117,4.377,336,5.892]],["t/671",[45,4.259,117,4.377,209,5.094]],["t/673",[69,3.757,337,5.892,338,5.094]],["t/677",[339,8.368]],["t/679",[340,6.35,341,6.35]],["t/681",[342,6.915,343,4.488]],["t/683",[36,5.701,343,4.488]],["t/685",[307,6.35,343,4.488]],["t/687",[343,4.488,344,6.915]],["t/689",[343,3.824,345,4.06,346,5.094]],["t/691",[343,3.824,347,5.892,348,5.892]],["t/693",[343,4.488,349,5.48]],["t/695",[113,5.48,343,4.488]],["t/697",[1,6.35,343,4.488]],["t/699",[343,5.431]],["t/701",[237,4.512,343,3.824,350,5.892]],["t/703",[18,6.631]],["t/706",[351,6.915,352,6.915]],["t/710",[30,5.335]],["t/712",[63,5.137,353,5.979]],["t/714",[322,4.154,338,5.094,354,5.892]],["t/716",[103,4.765,160,5.701]],["t/718",[355,6.899]],["t/720",[10,4.858,355,4.858,356,5.411]],["t/722",[315,5.979,357,5.48]],["t/723",[260,4.488,357,5.48]],["t/725",[260,4.488,315,5.979]],["t/727",[259,4.665,270,5.48]],["t/729",[357,4.669,358,5.892,359,4.669]],["t/731",[71,4.067,95,4.438,236,3.619,360,5.132]],["t/733",[14,5.645]],["t/735",[14,3.975,260,3.824,272,3.824]],["t/737",[14,3.975,272,3.824,361,5.892]],["t/739",[14,3.975,44,3.225,244,5.094]],["t/741",[246,4.512,362,5.094,363,5.411]],["t/743",[296,5.48,364,5.979]],["t/744",[260,3.824,296,4.669,364,5.094]],["t/746",[296,4.669,364,5.094,365,5.411]],["t/748",[44,3.225,151,4.512,366,5.411]],["t/750",[367,8.368]],["t/752",[130,5.48,368,5.701]],["t/754",[95,5.979,368,5.701]],["t/756",[130,3.603,155,3.481,368,3.748,369,4.175,370,3.931]],["t/758",[44,2.489,130,3.603,155,3.481,370,3.931,371,4.175]],["t/760",[8,6.217]],["t/762",[372,5.48,373,6.915]],["t/764",[260,3.331,270,4.067,272,3.331,273,4.713]],["t/766",[300,5.094,301,4.377,371,5.411]],["t/768",[22,5.137,295,4.665]],["t/770",[15,5.137,22,5.137]],["t/772",[68,4.765,374,5.979]],["t/774",[375,8.368]],["t/776",[26,4.512,122,5.411,376,5.892]],["t/778",[26,5.295,377,6.915]],["t/780",[378,4.876,379,5.979]],["t/782",[75,4.201,380,6.35]],["t/784",[75,4.201,381,5.979]],["t/786",[246,5.295,362,5.979]],["t/788",[97,4.665,260,4.488]],["t/790",[272,3.331,296,4.067,382,5.132,383,5.132]],["t/792",[237,4.512,295,3.975,384,5.892]],["t/794",[103,4.06,385,5.411,386,5.892]],["t/796",[39,4.266,387,6.35]],["t/798",[16,5.701,388,6.915]],["t/800",[192,4.175,318,3.287,323,3.287,389,4.546,390,4.546]],["t/802",[260,3.331,391,4.232,392,5.132,393,5.132]],["t/804",[272,2.951,385,4.175,394,4.546,395,4.546,396,4.546]],["t/806",[75,3.58,275,4.669,365,5.411]],["t/808",[166,4.858,295,3.975,357,4.669]],["t/810",[362,4.438,397,4.232,398,5.132,399,4.438]],["t/812",[387,6.35,400,5.48]],["t/814",[97,3.975,260,3.824,401,5.892]],["t/816",[97,4.665,402,6.915]],["t/818",[97,4.665,403,6.35]],["t/820",[35,3.757,57,3.757,129,4.512]],["t/822",[381,7.235]],["t/824",[404,7.685]],["t/826",[405,7.685]],["t/828",[135,4.335,257,6.35]],["t/830",[406,8.368]],["t/832",[103,3.537,259,3.462,407,4.713,408,4.438]],["t/834",[243,5.137,260,4.488]],["t/838",[295,4.665,321,5.295]],["t/840",[189,6.631]],["t/842",[69,4.409,322,4.876]],["t/844",[321,5.295,409,6.915]],["t/846",[318,4.259,321,4.512,323,4.259]],["t/848",[31,2.805,61,2.518,318,3.287,321,3.481,322,3.206]],["t/850",[34,4.201,205,5.137]],["t/852",[43,3.394,104,3.619,259,3.462,322,3.619]],["t/854",[205,4.377,410,4.512,411,5.892]],["t/856",[233,4.232,318,3.71,323,3.71,412,4.438]],["t/858",[31,3.166,104,3.619,205,3.813,412,4.438]],["t/860",[34,2.762,75,2.762,233,3.748,412,3.931,413,4.546]],["t/862",[15,5.137,414,6.915]],["t/865",[89,4.14,415,6.915]],["t/867",[89,3.527,119,5.094,242,5.094]],["t/869",[75,3.118,89,3.073,113,4.067,416,3.537]],["t/871",[75,3.118,89,3.073,374,4.438,416,3.537]],["t/873",[67,7.685]],["t/875",[312,5.979,349,5.48]],["t/877",[417,7.685]],["t/879",[12,3.93,199,4.438,418,5.132,419,5.132]],["t/881",[12,4.512,13,4.512,420,5.411]],["t/883",[13,5.295,416,4.765]],["t/887",[30,5.335]],["t/889",[241,5.137,421,5.979]],["t/891",[422,8.368]],["t/893",[90,6.35,421,5.979]],["t/895",[113,5.48,421,5.979]],["t/897",[423,5.892,424,5.892,425,4.512]],["t/899",[213,5.48,426,5.979]],["t/901",[343,5.431]],["t/903",[319,6.217]],["t/905",[410,5.295,427,4.765]],["t/907",[14,4.665,410,5.295]],["t/909",[134,5.137,151,5.295]],["t/911",[129,5.295,428,4.999]],["t/913",[89,4.14,429,6.915]],["t/915",[202,5.411,204,4.858,430,3.694]],["t/917",[334,4.809]],["t/919",[230,6.899]],["t/921",[97,4.665,298,5.701]],["t/923",[97,4.665,214,5.979]],["t/924",[431,6.915,432,6.915]],["t/926",[368,6.899]],["t/928",[8,6.217]],["t/930",[35,4.409,433,6.915]],["t/932",[214,5.979,391,5.701]],["t/934",[434,6.915,435,6.915]],["t/936",[32,4.573,436,6.915]],["t/938",[25,5.701,437,6.915]],["t/940",[353,7.235]],["t/942",[44,3.225,438,5.892,439,5.892]],["t/944",[16,5.701,440,6.915]],["t/946",[14,3.462,17,2.912,105,4.438,441,5.132]],["t/948",[53,3.896,97,3.975,204,4.858]],["t/952",[31,4.266,299,5.979]],["t/954",[34,4.201,252,6.35]],["t/956",[39,3.166,43,3.394,425,3.93,442,5.132]],["t/958",[35,4.409,45,4.999]],["t/960",[48,4.512,443,5.892,444,5.892]],["t/962",[18,4.669,106,5.411,445,5.892]],["t/964",[5,3.287,27,3.481,31,2.805,32,3.006,244,3.931]],["t/966",[5,3.287,27,3.481,32,3.006,34,2.762,254,3.603]],["t/968",[5,2.95,27,3.125,32,2.698,43,2.698,123,3.125,134,3.031]],["t/972",[246,5.295,408,5.979]],["t/973",[166,4.232,400,4.067,446,4.713,447,5.132]],["t/975",[61,3.829,246,5.295]],["t/977",[408,7.235]],["t/979",[448,8.368]],["t/981",[449,8.368]],["t/982",[450,7.685]],["t/984",[272,5.431]],["t/986",[246,6.408]],["t/988",[400,6.631]],["t/990",[451,8.368]],["t/992",[209,7.235]],["t/994",[302,5.48,303,6.35]],["t/995",[306,7.685]],["t/997",[304,7.685]],["t/999",[213,5.48,399,5.979]],["t/1002",[428,6.049]],["t/1004",[452,8.368]],["t/1006",[259,4.665,453,6.35]],["t/1008",[400,6.631]],["t/1010",[13,4.512,328,4.858,454,5.892]],["t/1012",[378,4.876,455,6.915]],["t/1014",[129,5.295,428,4.999]],["t/1016",[295,3.975,428,4.259,456,5.411]],["t/1018",[75,3.118,378,3.619,428,3.71,456,4.713]],["t/1020",[4,4.669,5,4.259,295,3.975]],["t/1022",[75,4.201,457,6.915]],["t/1024",[129,4.512,391,4.858,428,4.259]],["t/1026",[4,4.669,349,4.669,458,5.892]],["t/1028",[428,4.999,459,6.915]],["t/1032",[460,7.685]],["t/1034",[345,4.06,346,5.094,461,5.411]],["t/1035",[462,6.35,463,5.701]],["t/1037",[464,7.685]],["t/1039",[44,3.785,465,6.35]],["t/1041",[198,3.635,283,5.094,345,4.06]],["t/1043",[466,7.685]],["t/1045",[467,6.35,468,6.35]],["t/1047",[17,2.912,198,3.166,271,4.438,469,3.71]],["t/1049",[198,3.166,272,3.331,470,4.713,471,3.71]],["t/1051",[135,3.218,138,4.438,155,3.93,472,4.713]],["t/1053",[198,4.266,473,6.35]],["t/1055",[198,4.266,474,6.35]],["t/1057",[123,5.295,372,5.48]],["t/1059",[44,3.785,475,6.35]],["t/1061",[236,4.876,359,5.48]],["t/1063",[241,5.137,476,6.35]],["t/1064",[477,6.35,478,6.35]],["t/1065",[479,7.685]],["t/1066",[147,5.48,270,5.48]],["t/1067",[15,5.137,44,3.785]],["t/1069",[469,4.999,480,5.701]],["t/1071",[481,6.35,482,5.979]],["t/1073",[483,7.685]],["t/1075",[463,5.701,484,5.701]],["t/1077",[485,7.685]],["t/1079",[469,4.999,480,5.701]],["t/1081",[198,4.266,486,6.35]],["t/1083",[469,4.999,487,6.35]],["t/1084",[198,4.266,206,5.48]],["t/1085",[206,6.631]],["t/1087",[488,7.685]],["t/1089",[489,7.685]],["t/1093",[460,7.685]],["t/1095",[345,4.06,346,5.094,461,5.411]],["t/1096",[462,6.35,463,5.701]],["t/1098",[464,7.685]],["t/1100",[44,3.785,465,6.35]],["t/1102",[198,3.635,283,5.094,345,4.06]],["t/1104",[466,7.685]],["t/1106",[467,6.35,468,6.35]],["t/1108",[17,2.912,198,3.166,271,4.438,469,3.71]],["t/1110",[198,3.166,272,3.331,470,4.713,471,3.71]],["t/1112",[135,3.218,138,4.438,155,3.93,472,4.713]],["t/1114",[198,4.266,473,6.35]],["t/1116",[198,4.266,474,6.35]],["t/1118",[123,5.295,372,5.48]],["t/1120",[44,3.785,475,6.35]],["t/1122",[236,4.876,359,5.48]],["t/1124",[241,5.137,476,6.35]],["t/1125",[477,6.35,478,6.35]],["t/1126",[479,7.685]],["t/1127",[147,5.48,270,5.48]],["t/1128",[15,5.137,44,3.785]],["t/1130",[469,4.999,480,5.701]],["t/1132",[481,6.35,482,5.979]],["t/1134",[483,7.685]],["t/1136",[463,5.701,484,5.701]],["t/1138",[485,7.685]],["t/1140",[469,4.999,480,5.701]],["t/1142",[198,4.266,486,6.35]],["t/1144",[469,4.999,487,6.35]],["t/1145",[198,4.266,206,5.48]],["t/1146",[206,6.631]],["t/1148",[488,7.685]],["t/1150",[489,7.685]],["t/1154",[6,4.259,427,4.06,490,5.892]],["t/1156",[410,5.295,491,6.35]],["t/1158",[10,4.858,75,3.58,492,5.892]],["t/1160",[22,4.377,295,3.975,378,4.154]],["t/1162",[97,4.665,391,5.701]],["t/1164",[27,3.93,111,4.713,427,3.537,493,4.232]],["t/1166",[51,3.462,410,3.93,427,3.537,494,3.619]],["t/1168",[68,4.06,495,4.259,496,5.892]],["t/1170",[54,5.084]],["t/1174",[54,3.58,319,4.377,497,5.892]],["t/1176",[319,5.137,323,4.999]],["t/1177",[29,2.842,31,3.166,86,4.713,495,3.71]],["t/1179",[29,2.518,34,2.762,322,3.206,498,4.546,499,3.931]],["t/1181",[319,4.377,407,5.411,495,4.259]],["t/1183",[189,4.067,295,3.462,378,3.619,499,4.438]],["t/1185",[89,3.527,311,4.669,366,5.411]],["t/1187",[426,5.094,500,4.669,501,5.892]],["t/1189",[12,3.481,13,3.481,493,3.748,495,3.287,502,4.546]],["t/1191",[503,8.368]],["t/1193",[11,3.2,17,2.1,193,3.399,241,2.75,495,2.676,504,3.701,505,3.701]],["t/1195",[301,5.137,499,5.979]],["t/1197",[165,5.094,166,4.858,301,4.377]],["t/1199",[301,4.377,495,4.259,506,5.411]],["t/1201",[301,4.377,495,4.259,507,5.892]],["t/1203",[495,4.999,508,6.915]],["t/1207",[30,5.335]],["t/1209",[69,4.409,191,4.765]],["t/1211",[275,3.234,338,3.528,509,4.08,510,4.08,511,4.08,512,4.08]],["t/1213",[275,4.067,285,4.713,513,5.132,514,4.713]],["t/1215",[320,4.438,515,5.132,516,5.132,517,4.438]],["t/1217",[117,3.377,151,3.481,191,3.133,494,3.206,518,4.546]],["t/1219",[191,2.812,519,4.08,520,3.528,521,3.364,522,3.747,523,4.08]],["t/1221",[87,3.234,135,2.558,191,2.812,254,3.234,255,3.747,524,4.08]],["t/1223",[69,2.899,521,3.748,525,4.546,526,3.481,527,4.175]],["t/1225",[89,2.722,191,3.133,416,3.133,528,3.931,529,4.546]],["t/1227",[191,4.06,349,4.669,426,5.094]],["t/1229",[69,2.602,189,3.234,191,2.812,530,4.08,531,3.528,532,3.747]],["t/1231",[191,2.551,260,2.402,533,3.701,534,3.701,535,3.399,536,3.701,537,3.701]],["t/1233",[82,4.669,275,4.669,538,5.892]],["t/1235",[0,3.931,2,4.175,539,4.175,540,3.931,541,4.546]],["t/1237",[381,7.235]],["t/1239",[405,7.685]],["t/1241",[404,7.685]],["t/1243",[542,8.368]],["t/1245",[17,2.912,53,3.394,331,4.438,526,3.93]],["t/1246",[87,4.669,135,3.694,526,4.512]],["t/1248",[345,3.133,453,4.175,526,3.481,543,4.546,544,4.546]],["t/1250",[89,3.073,119,4.438,259,3.462,526,3.93]],["t/1252",[44,2.234,135,2.558,416,2.812,545,4.08,546,4.08,547,4.08]],["t/1254",[89,4.14,416,4.765]],["t/1255",[17,2.1,69,2.36,89,2.216,416,2.551,531,3.2,548,3.701,549,3.399]],["t/1257",[4,2.933,89,2.216,416,2.551,520,3.2,550,3.701,551,3.701,552,3.701]],["t/1259",[89,2.443,170,3.747,319,3.031,416,2.812,417,3.747,553,3.747]],["t/1261",[69,2.899,322,3.206,416,3.133,521,3.748,526,3.481]],["t/1263",[69,3.757,374,5.094,500,4.669]],["t/1265",[17,2.912,243,3.813,430,3.218,554,4.067]],["t/1266",[430,4.335,555,6.35]],["t/1268",[243,3.377,260,2.951,420,4.175,494,3.206,555,4.175]],["t/1270",[446,4.175,556,3.377,557,4.546,558,3.067,559,4.175]],["t/1272",[16,2.792,494,2.388,560,3.386,561,3.386,562,3.11,563,3.386,564,3.386,565,3.386]],["t/1274",[160,4.232,494,3.619,566,5.132,567,5.132]],["t/1276",[242,3.931,298,3.748,327,4.175,568,4.546,569,3.603]],["t/1278",[430,3.694,493,4.858,570,5.892]],["t/1280",[403,4.713,430,3.218,549,4.713,571,5.132]],["t/1282",[17,3.343,20,4.259,23,4.154]],["t/1283",[20,3.71,430,3.218,572,4.713,573,4.713]],["t/1285",[21,4.259,326,5.094,430,3.694]],["t/1287",[21,2.95,23,2.877,55,2.812,137,3.234,574,4.08,575,3.747]],["t/1289",[83,4.259,326,5.094,535,5.411]],["t/1291",[576,8.368]],["t/1293",[54,2.057,83,2.448,191,2.334,198,2.089,241,2.516,522,3.11,539,3.11,577,3.386]],["t/1295",[65,4.259,357,4.669,482,5.094]],["t/1297",[23,2.201,39,1.925,44,1.708,54,1.896,83,2.256,430,1.957,521,2.573,578,3.121,579,3.121]],["t/1299",[60,3.481,220,4.175,370,3.931,580,4.546,581,4.546]],["t/1301",[60,4.512,130,4.669,582,5.892]],["t/1303",[369,4.713,575,4.713,583,5.132,584,5.132]],["t/1305",[20,3.287,22,3.377,25,3.748,109,3.481,585,4.175]],["t/1307",[20,2.95,22,4.677,39,2.517,520,3.528,585,3.747]],["t/1309",[39,2.089,74,2.928,528,2.928,559,3.11,573,3.11,586,3.11,587,3.386,588,3.386]],["t/1311",[23,2.041,35,1.845,83,2.092,430,1.814,589,2.894,590,3.944,591,2.894,592,2.894,593,2.894]],["t/1313",[17,2.912,53,3.394,272,3.331,397,4.232]],["t/1314",[14,3.462,71,4.067,259,3.462,594,5.132]],["t/1316",[514,5.411,595,5.892,596,5.892]],["t/1318",[597,6.915,598,6.915]],["t/1320",[397,3.748,399,3.931,400,3.603,494,3.206,532,4.175]],["t/1322",[272,3.824,355,4.858,494,4.154]],["t/1324",[83,3.71,272,3.331,494,3.619,599,5.132]],["t/1326",[14,3.975,311,4.669,353,5.094]],["t/1328",[69,3.757,430,3.694,572,5.411]],["t/1330",[22,3.031,103,2.812,135,2.558,188,3.747,528,3.528,600,4.08]],["t/1332",[32,3.394,586,4.713,601,4.713,602,5.132]],["t/1334",[410,4.512,427,4.06,494,4.154]],["t/1336",[334,4.809]],["t/1337",[334,3.974,430,4.335]],["t/1339",[334,3.386,430,3.694,590,4.858]],["t/1341",[17,3.085,109,2.593,540,2.928,590,2.792,603,3.386,604,3.386,605,3.386]],["t/1343",[17,2.56,75,1.639,109,2.066,135,1.692,349,2.138,356,4.144,506,2.478,517,2.333,590,2.225]],["t/1345",[334,2.949,430,3.218,531,4.438,606,5.132]],["t/1347",[334,2.949,607,4.438,608,4.713,609,5.132]],["t/1349",[334,2.949,425,3.93,610,4.438,611,5.132]],["t/1351",[54,2.249,63,2.75,127,2.933,144,3.052,334,2.127,612,3.701,613,3.701]],["t/1353",[340,4.713,341,4.713,343,3.331,610,4.438]],["t/1355",[54,3.58,614,5.892,615,5.892]],["t/1357",[17,3.924,616,6.915]],["t/1358",[113,4.669,295,3.975,617,5.892]],["t/1360",[199,5.094,203,5.094,204,4.858]],["t/1362",[4,4.669,241,4.377,601,5.411]],["t/1364",[302,4.669,610,5.094,618,5.892]],["t/1368",[30,5.335]],["t/1370",[36,5.701,334,3.974]],["t/1371",[619,8.368]],["t/1373",[607,7.235]],["t/1375",[607,5.979,608,6.35]],["t/1377",[620,6.915,621,5.979]],["t/1379",[397,6.899]],["t/1381",[127,5.48,144,5.701]],["t/1383",[127,5.48,622,6.915]],["t/1385",[359,5.48,379,5.979]],["t/1387",[484,4.858,623,5.892,624,5.094]],["t/1389",[9,3.813,484,4.232,625,4.438,626,3.71]],["t/1391",[627,6.915,628,6.915]],["t/1393",[493,5.701,629,4.999]],["t/1395",[427,4.765,630,6.915]],["t/1397",[631,8.368]],["t/1399",[621,5.979,632,6.915]],["t/1401",[633,6.915,634,6.915]],["t/1403",[144,5.701,635,6.915]],["t/1405",[427,4.06,636,5.892,637,5.892]],["t/1407",[638,6.915,639,6.915]],["t/1409",[318,4.999,640,6.915]],["t/1411",[427,4.765,641,6.915]],["t/1413",[625,5.094,626,4.259,642,5.892]],["t/1415",[625,5.094,643,5.892,644,5.892]],["t/1417",[645,8.368]],["t/1419",[646,5.892,647,5.892,648,5.892]],["t/1421",[649,6.915,650,6.915]],["t/1423",[425,5.295,651,6.915]],["t/1425",[652,5.892,653,5.892,654,5.892]],["t/1427",[655,6.35,656,6.35]],["t/1429",[629,4.259,655,5.411,657,5.892]],["t/1431",[355,5.701,427,4.765]],["t/1433",[656,6.35,658,6.915]],["t/1435",[659,6.35,660,6.915]],["t/1437",[540,5.979,659,6.35]],["t/1439",[378,4.876,624,5.979]],["t/1441",[378,4.876,428,4.999]],["t/1443",[59,5.48,661,6.915]],["t/1445",[57,4.409,662,6.915]],["t/1447",[123,3.93,135,3.218,155,3.93,334,2.949]],["t/1448",[236,4.876,310,5.701]],["t/1450",[556,4.377,626,4.259,663,5.094]],["t/1452",[554,4.669,626,4.259,663,5.094]],["t/1454",[500,4.669,626,4.259,663,5.094]],["t/1456",[471,4.259,629,4.259,664,4.512]],["t/1458",[471,3.71,629,3.71,664,3.93,665,4.438]],["t/1460",[471,4.259,664,4.512,666,5.411]],["t/1462",[471,3.71,664,3.93,665,4.438,666,4.713]],["t/1464",[471,3.71,664,3.93,667,4.713,668,4.438]],["t/1466",[471,3.287,664,3.481,665,3.931,667,4.175,668,3.931]],["t/1468",[629,4.259,669,5.892,670,5.411]],["t/1470",[282,5.411,363,5.411,671,5.892]],["t/1472",[345,3.537,517,4.438,629,3.71,672,4.232]],["t/1474",[345,4.06,672,4.858,673,5.892]],["t/1476",[310,4.232,345,3.537,380,4.713,672,4.232]],["t/1478",[165,4.438,345,3.537,379,4.438,672,4.232]],["t/1480",[39,4.266,674,5.295]],["t/1482",[39,3.635,670,5.411,674,4.512]],["t/1484",[674,5.295,675,6.915]],["t/1486",[236,4.876,674,5.295]],["t/1488",[629,4.999,674,5.295]],["t/1490",[629,3.71,674,3.93,676,5.132,677,5.132]],["t/1492",[678,6.915,679,6.915]],["t/1494",[279,6.35,624,5.979]],["t/1496",[680,8.368]],["t/1498",[126,5.411,310,4.858,681,5.892]],["t/1500",[243,4.377,450,5.411,682,5.892]],["t/1502",[683,8.368]],["t/1504",[684,8.368]],["t/1506",[685,5.979,686,6.35]],["t/1508",[89,3.073,427,3.537,685,4.438,686,4.713]],["t/1510",[687,6.915,688,6.915]],["t/1512",[236,4.154,237,4.512,359,4.669]],["t/1513",[689,8.368]],["t/1515",[556,4.377,558,3.975,569,4.669]],["t/1517",[556,3.813,558,3.462,569,4.067,690,4.713]],["t/1519",[94,4.713,231,4.713,556,3.813,558,3.462]],["t/1521",[556,4.377,558,3.975,626,4.259]],["t/1523",[554,4.669,558,3.975,569,4.669]],["t/1525",[554,4.067,558,3.462,569,4.067,690,4.713]],["t/1527",[554,4.669,558,3.975,626,4.259]],["t/1529",[691,6.35,692,6.915]],["t/1531",[691,6.35,693,6.915]],["t/1533",[236,4.876,500,5.48]],["t/1535",[685,7.235]],["t/1537",[236,4.154,500,4.669,626,4.259]],["t/1539",[372,5.48,694,6.915]],["t/1541",[372,5.48,668,5.979]],["t/1543",[237,4.512,558,3.975,695,5.892]],["t/1545",[11,5.979,127,5.48]],["t/1547",[556,5.137,696,6.915]],["t/1549",[562,6.35,697,6.915]],["t/1551",[334,3.974,698,6.915]],["t/1552",[334,3.974,699,6.915]],["t/1554",[334,3.386,527,5.411,700,5.892]],["t/1556",[7,5.701,701,6.915]],["t/1558",[425,5.295,702,6.915]],["t/1560",[425,5.295,621,5.979]],["t/1562",[17,3.343,378,4.154,558,3.975]],["t/1563",[118,5.094,334,3.386,558,3.975]],["t/1565",[14,4.665,491,6.35]],["t/1567",[109,5.295,213,5.48]],["t/1569",[37,6.35,703,6.915]],["t/1571",[295,4.665,704,6.915]],["t/1573",[323,4.999,705,6.915]],["t/1575",[430,4.335,553,6.35]],["t/1577",[311,5.48,334,3.974]],["t/1579",[378,4.876,706,6.915]],["t/1581",[205,5.137,321,5.295]],["t/1583",[29,3.263,189,4.669,334,3.386]],["t/1585",[243,5.137,334,3.974]],["t/1587",[213,5.48,334,3.974]],["t/1589",[51,3.975,334,3.386,430,3.694]],["t/1591",[53,4.573,210,6.35]]],"invertedIndex":[["",{"_index":17,"t":{"29":{"position":[[12,1]]},"129":{"position":[[23,1]]},"133":{"position":[[11,1]]},"245":{"position":[[15,1]]},"255":{"position":[[4,1]]},"300":{"position":[[14,1]]},"361":{"position":[[8,1]]},"492":{"position":[[4,1]]},"660":{"position":[[7,1]]},"946":{"position":[[7,1]]},"1047":{"position":[[8,1]]},"1108":{"position":[[8,1]]},"1193":{"position":[[29,1]]},"1245":{"position":[[4,1]]},"1255":{"position":[[42,4]]},"1265":{"position":[[6,1]]},"1282":{"position":[[5,1]]},"1313":{"position":[[7,1]]},"1341":{"position":[[32,3],[61,3]]},"1343":{"position":[[27,5],[44,1]]},"1357":{"position":[[7,1]]},"1562":{"position":[[6,1]]}}}],["0",{"_index":335,"t":{"663":{"position":[[5,1]]}}}],["1",{"_index":31,"t":{"51":{"position":[[0,2]]},"82":{"position":[[0,2]]},"152":{"position":[[0,2]]},"187":{"position":[[0,2]]},"263":{"position":[[0,2]]},"319":{"position":[[0,2]]},"630":{"position":[[0,2]]},"636":{"position":[[0,2]]},"648":{"position":[[0,2]]},"656":{"position":[[0,2]]},"665":{"position":[[5,1]]},"848":{"position":[[0,2]]},"858":{"position":[[0,2]]},"952":{"position":[[0,2]]},"964":{"position":[[30,1]]},"1177":{"position":[[7,2]]}}}],["19",{"_index":637,"t":{"1405":{"position":[[6,2]]}}}],["1panel",{"_index":288,"t":{"578":{"position":[[0,6]]}}}],["2",{"_index":34,"t":{"53":{"position":[[0,2]]},"84":{"position":[[0,2]]},"154":{"position":[[0,2]]},"189":{"position":[[0,2]]},"265":{"position":[[0,2]]},"321":{"position":[[0,2]]},"435":{"position":[[7,1]]},"632":{"position":[[0,2]]},"638":{"position":[[0,2]]},"650":{"position":[[0,2]]},"658":{"position":[[0,2]]},"667":{"position":[[5,1]]},"850":{"position":[[0,2]]},"860":{"position":[[0,2]]},"954":{"position":[[0,2]]},"966":{"position":[[30,1]]},"1179":{"position":[[7,2]]}}}],["2.0",{"_index":461,"t":{"1034":{"position":[[9,3]]},"1095":{"position":[[9,3]]}}}],["2.0.4",{"_index":594,"t":{"1314":{"position":[[33,5]]}}}],["3",{"_index":43,"t":{"62":{"position":[[0,2]]},"97":{"position":[[0,2]]},"156":{"position":[[0,2]]},"206":{"position":[[0,2]]},"276":{"position":[[0,2]]},"337":{"position":[[0,2]]},"640":{"position":[[0,2]]},"652":{"position":[[0,2]]},"669":{"position":[[5,1]]},"852":{"position":[[0,2]]},"956":{"position":[[0,2]]},"968":{"position":[[30,1]]}}}],["3.0",{"_index":480,"t":{"1069":{"position":[[8,3]]},"1079":{"position":[[8,3]]},"1130":{"position":[[8,3]]},"1140":{"position":[[8,3]]}}}],["3.1.0",{"_index":536,"t":{"1231":{"position":[[47,6]]}}}],["3.1.1",{"_index":537,"t":{"1231":{"position":[[58,5]]}}}],["4",{"_index":45,"t":{"64":{"position":[[0,2]]},"99":{"position":[[0,2]]},"158":{"position":[[0,2]]},"208":{"position":[[0,2]]},"278":{"position":[[0,2]]},"339":{"position":[[0,2]]},"671":{"position":[[5,1]]},"958":{"position":[[0,2]]}}}],["401",{"_index":609,"t":{"1347":{"position":[[24,3]]}}}],["403",{"_index":522,"t":{"1219":{"position":[[34,3]]},"1293":{"position":[[70,5]]}}}],["404",{"_index":526,"t":{"1223":{"position":[[29,3]]},"1245":{"position":[[0,3]]},"1246":{"position":[[0,3]]},"1248":{"position":[[0,3]]},"1250":{"position":[[0,3]]},"1261":{"position":[[24,3]]}}}],["5",{"_index":48,"t":{"66":{"position":[[0,2]]},"101":{"position":[[0,2]]},"160":{"position":[[0,2]]},"280":{"position":[[0,2]]},"341":{"position":[[0,2]]},"960":{"position":[[0,2]]}}}],["6",{"_index":106,"t":{"162":{"position":[[0,2]]},"962":{"position":[[0,2]]}}}],["_basevalu",{"_index":571,"t":{"1280":{"position":[[26,15]]}}}],["abort",{"_index":518,"t":{"1217":{"position":[[26,6]]}}}],["access",{"_index":68,"t":{"95":{"position":[[17,6]]},"129":{"position":[[16,6]]},"131":{"position":[[6,6]]},"158":{"position":[[20,6]]},"227":{"position":[[6,6]]},"231":{"position":[[6,6]]},"249":{"position":[[9,6]]},"304":{"position":[[18,6]]},"772":{"position":[[7,6]]},"1168":{"position":[[12,13]]}}}],["action",{"_index":508,"t":{"1203":{"position":[[0,6]]}}}],["ad",{"_index":323,"t":{"642":{"position":[[0,6]]},"660":{"position":[[0,6]]},"667":{"position":[[9,6]]},"800":{"position":[[29,5]]},"846":{"position":[[0,6]]},"856":{"position":[[0,6]]},"1176":{"position":[[0,6]]},"1573":{"position":[[0,6]]}}}],["add",{"_index":104,"t":{"158":{"position":[[3,3]]},"196":{"position":[[0,3]]},"443":{"position":[[0,3]]},"630":{"position":[[3,3]]},"640":{"position":[[3,3]]},"656":{"position":[[3,3]]},"658":{"position":[[3,3]]},"852":{"position":[[3,3]]},"858":{"position":[[3,3]]}}}],["address",{"_index":11,"t":{"23":{"position":[[0,10]]},"1193":{"position":[[17,7]]},"1545":{"position":[[3,7]]}}}],["addy.io",{"_index":631,"t":{"1397":{"position":[[0,7]]}}}],["adguard",{"_index":672,"t":{"1472":{"position":[[0,7]]},"1474":{"position":[[0,7]]},"1476":{"position":[[0,7]]},"1478":{"position":[[0,7]]}}}],["admin",{"_index":65,"t":{"91":{"position":[[11,5]]},"198":{"position":[[17,5]]},"200":{"position":[[30,5]]},"227":{"position":[[0,5]]},"266":{"position":[[11,5]]},"270":{"position":[[11,5]]},"327":{"position":[[11,5]]},"1295":{"position":[[17,5]]}}}],["advanc",{"_index":358,"t":{"729":{"position":[[0,8]]}}}],["alert",{"_index":695,"t":{"1543":{"position":[[15,6]]}}}],["alloc",{"_index":565,"t":{"1272":{"position":[[42,10]]}}}],["allow",{"_index":111,"t":{"192":{"position":[[0,5]]},"1164":{"position":[[0,8]]}}}],["altern",{"_index":27,"t":{"43":{"position":[[0,11]]},"200":{"position":[[0,13]]},"964":{"position":[[0,11]]},"966":{"position":[[0,11]]},"968":{"position":[[0,11]]},"1164":{"position":[[9,11]]}}}],["anonym",{"_index":202,"t":{"441":{"position":[[7,9]]},"915":{"position":[[0,9]]}}}],["anoth",{"_index":487,"t":{"1083":{"position":[[4,7]]},"1144":{"position":[[4,7]]}}}],["anyth",{"_index":577,"t":{"1293":{"position":[[61,8]]}}}],["apach",{"_index":404,"t":{"824":{"position":[[0,6]]},"1241":{"position":[[0,6]]}}}],["api",{"_index":7,"t":{"15":{"position":[[13,3]]},"19":{"position":[[0,3]]},"374":{"position":[[0,3]]},"1556":{"position":[[0,3]]}}}],["app",{"_index":259,"t":{"522":{"position":[[17,4]]},"536":{"position":[[13,3]]},"538":{"position":[[16,4]]},"616":{"position":[[0,3]]},"640":{"position":[[24,3]]},"727":{"position":[[11,4]]},"832":{"position":[[34,3]]},"852":{"position":[[24,3]]},"1006":{"position":[[10,4]]},"1250":{"position":[[18,4]]},"1314":{"position":[[0,3]]}}}],["apparmor",{"_index":516,"t":{"1215":{"position":[[11,8]]}}}],["appconfig",{"_index":170,"t":{"392":{"position":[[0,9]]},"1259":{"position":[[38,9]]}}}],["appconfig.auth",{"_index":171,"t":{"394":{"position":[[0,14]]}}}],["appconfig.auth.headerauth",{"_index":174,"t":{"400":{"position":[[0,25]]}}}],["appconfig.auth.keycloak",{"_index":173,"t":{"398":{"position":[[0,23]]}}}],["appconfig.auth.oidc",{"_index":175,"t":{"402":{"position":[[0,19]]}}}],["appconfig.auth.us",{"_index":172,"t":{"396":{"position":[[0,20]]}}}],["appconfig.hidecompon",{"_index":177,"t":{"406":{"position":[[0,24]]}}}],["appconfig.websearch",{"_index":176,"t":{"404":{"position":[[0,19]]}}}],["applic",{"_index":64,"t":{"89":{"position":[[11,11]]},"325":{"position":[[16,11]]},"619":{"position":[[0,11]]}}}],["architectur",{"_index":235,"t":{"486":{"position":[[0,12]]}}}],["array",{"_index":332,"t":{"656":{"position":[[41,5]]}}}],["asset",{"_index":353,"t":{"712":{"position":[[10,6]]},"940":{"position":[[0,6]]},"1326":{"position":[[11,6]]}}}],["astronomi",{"_index":652,"t":{"1425":{"position":[[0,9]]}}}],["attest",{"_index":251,"t":{"505":{"position":[[11,11]]}}}],["auth",{"_index":20,"t":{"33":{"position":[[9,4]]},"45":{"position":[[14,4]]},"139":{"position":[[32,4]]},"241":{"position":[[14,4]]},"1282":{"position":[[0,4]]},"1283":{"position":[[0,4]]},"1305":{"position":[[7,5]]},"1307":{"position":[[7,5]]}}}],["authelia",{"_index":33,"t":{"51":{"position":[[10,8]]},"53":{"position":[[13,8]]},"62":{"position":[[12,8]]},"68":{"position":[[23,8]]}}}],["authent",{"_index":8,"t":{"17":{"position":[[0,14]]},"37":{"position":[[7,14]]},"43":{"position":[[12,14]]},"137":{"position":[[5,14]]},"251":{"position":[[11,14]]},"760":{"position":[[0,14]]},"928":{"position":[[0,14]]}}}],["authentik",{"_index":60,"t":{"82":{"position":[[10,9]]},"84":{"position":[[13,9]]},"97":{"position":[[12,9]]},"103":{"position":[[23,9]]},"1299":{"position":[[29,9]]},"1301":{"position":[[27,9]]}}}],["auto",{"_index":95,"t":{"137":{"position":[[0,4]]},"731":{"position":[[0,4]]},"754":{"position":[[0,4]]}}}],["automat",{"_index":361,"t":{"737":{"position":[[0,9]]}}}],["avail",{"_index":409,"t":{"844":{"position":[[0,9]]}}}],["awesom",{"_index":341,"t":{"679":{"position":[[5,7]]},"1353":{"position":[[5,7]]}}}],["azur",{"_index":258,"t":{"522":{"position":[[0,5]]},"538":{"position":[[0,5]]}}}],["back",{"_index":364,"t":{"743":{"position":[[0,7]]},"744":{"position":[[0,7]]},"746":{"position":[[0,7]]}}}],["backup",{"_index":151,"t":{"357":{"position":[[11,6]]},"359":{"position":[[12,6]]},"365":{"position":[[17,6]]},"748":{"position":[[15,6]]},"909":{"position":[[6,6]]},"1217":{"position":[[0,6]]}}}],["balanc",{"_index":628,"t":{"1391":{"position":[[7,7]]}}}],["bang",{"_index":457,"t":{"1022":{"position":[[6,5]]}}}],["bar",{"_index":504,"t":{"1193":{"position":[[25,3]]}}}],["bare",{"_index":238,"t":{"490":{"position":[[0,4]]}}}],["base",{"_index":128,"t":{"249":{"position":[[3,5]]}}}],["basic",{"_index":414,"t":{"862":{"position":[[0,5]]}}}],["be",{"_index":390,"t":{"800":{"position":[[23,5]]}}}],["befor",{"_index":398,"t":{"810":{"position":[[13,6]]}}}],["beginn",{"_index":309,"t":{"614":{"position":[[14,9]]}}}],["best",{"_index":107,"t":{"162":{"position":[[3,4]]},"308":{"position":[[0,4]]}}}],["blacklist",{"_index":622,"t":{"1383":{"position":[[3,9]]}}}],["blind",{"_index":496,"t":{"1168":{"position":[[6,5]]}}}],["block",{"_index":517,"t":{"1215":{"position":[[20,6]]},"1343":{"position":[[36,7]]},"1472":{"position":[[13,5]]}}}],["bookmark",{"_index":479,"t":{"1065":{"position":[[0,9]]},"1126":{"position":[[0,9]]}}}],["boot",{"_index":360,"t":{"731":{"position":[[24,4]]}}}],["boundari",{"_index":437,"t":{"938":{"position":[[6,10]]}}}],["bountysourc",{"_index":215,"t":{"453":{"position":[[22,12]]}}}],["branch",{"_index":304,"t":{"608":{"position":[[4,6]]},"997":{"position":[[0,8]]}}}],["brewhack",{"_index":466,"t":{"1043":{"position":[[0,8]]},"1104":{"position":[[0,8]]}}}],["browser",{"_index":241,"t":{"494":{"position":[[0,7]]},"889":{"position":[[0,7]]},"1063":{"position":[[0,7]]},"1124":{"position":[[0,7]]},"1193":{"position":[[9,7]]},"1293":{"position":[[19,7]]},"1362":{"position":[[12,7]]}}}],["bug",{"_index":203,"t":{"441":{"position":[[17,3]]},"449":{"position":[[8,3]]},"1360":{"position":[[14,3]]}}}],["build",{"_index":243,"t":{"497":{"position":[[0,5]]},"662":{"position":[[0,8]]},"834":{"position":[[0,8]]},"1265":{"position":[[0,5]]},"1268":{"position":[[5,5]]},"1500":{"position":[[9,6]]},"1585":{"position":[[0,5]]}}}],["built",{"_index":19,"t":{"33":{"position":[[0,5]]},"139":{"position":[[23,5]]},"499":{"position":[[4,5]]}}}],["bundlephobia",{"_index":317,"t":{"624":{"position":[[15,12]]}}}],["button",{"_index":519,"t":{"1219":{"position":[[5,6]]}}}],["caddi",{"_index":405,"t":{"826":{"position":[[0,5]]},"1239":{"position":[[0,5]]}}}],["callback",{"_index":578,"t":{"1297":{"position":[[36,8]]}}}],["capabl",{"_index":388,"t":{"798":{"position":[[6,12]]}}}],["casao",{"_index":291,"t":{"584":{"position":[[0,6]]}}}],["cdn",{"_index":240,"t":{"492":{"position":[[0,3]]},"544":{"position":[[17,3]]}}}],["certain",{"_index":330,"t":{"654":{"position":[[25,7]]}}}],["certif",{"_index":130,"t":{"253":{"position":[[7,12]]},"752":{"position":[[4,12]]},"756":{"position":[[26,11]]},"758":{"position":[[22,11]]},"1301":{"position":[[10,11]]}}}],["cft",{"_index":477,"t":{"1064":{"position":[[0,3]]},"1125":{"position":[[0,3]]}}}],["chang",{"_index":193,"t":{"429":{"position":[[11,7]]},"1193":{"position":[[0,8]]}}}],["changelog",{"_index":448,"t":{"979":{"position":[[0,9]]}}}],["chart",{"_index":706,"t":{"1579":{"position":[[12,6]]}}}],["check",{"_index":410,"t":{"854":{"position":[[0,8]]},"905":{"position":[[7,8]]},"907":{"position":[[7,6]]},"1156":{"position":[[11,8]]},"1166":{"position":[[31,6]]},"1334":{"position":[[7,6]]}}}],["checksum",{"_index":248,"t":{"503":{"position":[[0,8]]}}}],["choic",{"_index":439,"t":{"942":{"position":[[28,6]]}}}],["choos",{"_index":80,"t":{"121":{"position":[[0,8]]}}}],["chuck",{"_index":646,"t":{"1419":{"position":[[0,5]]}}}],["ci",{"_index":450,"t":{"982":{"position":[[0,2]]},"1500":{"position":[[6,2]]}}}],["claim",{"_index":150,"t":{"331":{"position":[[22,5]]}}}],["clear",{"_index":459,"t":{"1028":{"position":[[0,8]]}}}],["click",{"_index":552,"t":{"1257":{"position":[[46,7]]}}}],["client",{"_index":55,"t":{"72":{"position":[[0,6]]},"123":{"position":[[0,6]]},"144":{"position":[[15,7]]},"194":{"position":[[17,6]]},"214":{"position":[[0,6]]},"253":{"position":[[0,6]]},"268":{"position":[[16,6]]},"286":{"position":[[0,6]]},"347":{"position":[[0,6]]},"1287":{"position":[[36,6]]}}}],["client_id",{"_index":583,"t":{"1303":{"position":[[8,9]]}}}],["clipboard",{"_index":615,"t":{"1355":{"position":[[8,9]]}}}],["clock",{"_index":619,"t":{"1371":{"position":[[0,5]]}}}],["cloud",{"_index":134,"t":{"257":{"position":[[0,5]]},"492":{"position":[[6,5]]},"540":{"position":[[7,5]]},"582":{"position":[[7,5]]},"646":{"position":[[19,5]]},"909":{"position":[[0,5]]},"968":{"position":[[34,5]]}}}],["cloudflar",{"_index":102,"t":{"154":{"position":[[24,10]]},"156":{"position":[[7,11]]},"158":{"position":[[9,10]]},"164":{"position":[[23,10]]},"518":{"position":[[0,10]]}}}],["code",{"_index":493,"t":{"1164":{"position":[[28,5]]},"1189":{"position":[[5,6]]},"1278":{"position":[[12,4]]},"1393":{"position":[[0,4]]}}}],["collect",{"_index":350,"t":{"701":{"position":[[5,11]]}}}],["color",{"_index":495,"t":{"1168":{"position":[[0,5]]},"1177":{"position":[[10,6]]},"1181":{"position":[[16,6]]},"1189":{"position":[[28,6]]},"1193":{"position":[[35,5]]},"1199":{"position":[[9,5]]},"1201":{"position":[[4,5]]},"1203":{"position":[[7,6]]}}}],["comic",{"_index":650,"t":{"1421":{"position":[[5,6]]}}}],["command",{"_index":160,"t":{"372":{"position":[[0,8]]},"599":{"position":[[8,8]]},"716":{"position":[[8,8]]},"1274":{"position":[[0,7]]}}}],["commit",{"_index":306,"t":{"610":{"position":[[0,6]]},"995":{"position":[[0,7]]}}}],["common",{"_index":52,"t":{"68":{"position":[[16,6]]},"103":{"position":[[16,6]]},"139":{"position":[[16,6]]},"164":{"position":[[16,6]]},"210":{"position":[[16,6]]},"282":{"position":[[16,6]]},"310":{"position":[[16,6]]},"343":{"position":[[16,6]]}}}],["commun",{"_index":393,"t":{"802":{"position":[[24,13]]}}}],["compact",{"_index":562,"t":{"1272":{"position":[[17,8]]},"1549":{"position":[[0,7]]}}}],["comparison",{"_index":28,"t":{"45":{"position":[[0,10]]}}}],["complet",{"_index":158,"t":{"370":{"position":[[0,8]]}}}],["compon",{"_index":233,"t":{"475":{"position":[[9,10]]},"658":{"position":[[41,9]]},"856":{"position":[[21,9]]},"860":{"position":[[19,9]]}}}],["compos",{"_index":273,"t":{"552":{"position":[[7,7]]},"764":{"position":[[32,7]]}}}],["condit",{"_index":333,"t":{"658":{"position":[[11,11]]}}}],["conf.yml",{"_index":86,"t":{"125":{"position":[[20,8]]},"1177":{"position":[[26,8]]}}}],["config",{"_index":69,"t":{"105":{"position":[[0,6]]},"378":{"position":[[33,6]]},"423":{"position":[[8,6]]},"427":{"position":[[0,6]]},"642":{"position":[[27,6]]},"673":{"position":[[11,6]]},"842":{"position":[[0,6]]},"1209":{"position":[[0,6]]},"1223":{"position":[[0,7]]},"1229":{"position":[[44,6]]},"1255":{"position":[[31,6]]},"1261":{"position":[[4,6]]},"1263":{"position":[[7,6]]},"1328":{"position":[[0,6]]}}}],["configmap",{"_index":513,"t":{"1213":{"position":[[11,9]]}}}],["configur",{"_index":35,"t":{"53":{"position":[[3,9]]},"58":{"position":[[5,13]]},"84":{"position":[[3,9]]},"160":{"position":[[3,9]]},"173":{"position":[[0,9]]},"175":{"position":[[0,9]]},"189":{"position":[[3,9]]},"265":{"position":[[3,9]]},"302":{"position":[[0,9]]},"321":{"position":[[3,9]]},"820":{"position":[[11,13]]},"930":{"position":[[0,13]]},"958":{"position":[[3,9]]},"1311":{"position":[[63,10]]}}}],["connect",{"_index":540,"t":{"1235":{"position":[[11,7]]},"1341":{"position":[[11,7]]},"1437":{"position":[[4,10]]}}}],["consider",{"_index":98,"t":{"141":{"position":[[9,14]]}}}],["consol",{"_index":601,"t":{"1332":{"position":[[16,7]]},"1362":{"position":[[20,7]]}}}],["contain",{"_index":260,"t":{"524":{"position":[[0,9]]},"538":{"position":[[6,9]]},"723":{"position":[[0,9]]},"725":{"position":[[0,9]]},"735":{"position":[[16,9]]},"744":{"position":[[11,10]]},"764":{"position":[[9,10]]},"788":{"position":[[0,9]]},"802":{"position":[[14,9]]},"814":{"position":[[0,9]]},"834":{"position":[[18,9]]},"1231":{"position":[[0,9]]},"1268":{"position":[[28,9]]}}}],["content",{"_index":30,"t":{"49":{"position":[[0,8]]},"80":{"position":[[0,8]]},"111":{"position":[[0,8]]},"150":{"position":[[0,8]]},"171":{"position":[[0,8]]},"185":{"position":[[0,8]]},"261":{"position":[[0,8]]},"294":{"position":[[0,8]]},"317":{"position":[[0,8]]},"382":{"position":[[0,8]]},"710":{"position":[[0,8]]},"887":{"position":[[0,8]]},"1207":{"position":[[0,8]]},"1368":{"position":[[0,8]]}}}],["continu",{"_index":491,"t":{"1156":{"position":[[0,10]]},"1565":{"position":[[0,10]]}}}],["contribut",{"_index":618,"t":{"1364":{"position":[[4,13]]}}}],["contributor",{"_index":224,"t":{"461":{"position":[[0,12]]},"467":{"position":[[0,12]]}}}],["control",{"_index":482,"t":{"1071":{"position":[[7,7]]},"1132":{"position":[[7,7]]},"1295":{"position":[[23,8]]}}}],["cooki",{"_index":422,"t":{"891":{"position":[[0,7]]}}}],["coolifi",{"_index":287,"t":{"576":{"position":[[0,7]]}}}],["copi",{"_index":614,"t":{"1355":{"position":[[0,4]]}}}],["cor",{"_index":590,"t":{"1311":{"position":[[24,4],[78,4]]},"1339":{"position":[[7,4]]},"1341":{"position":[[0,4]]},"1343":{"position":[[0,4]]}}}],["core",{"_index":231,"t":{"471":{"position":[[0,4]]},"1519":{"position":[[14,4]]}}}],["correctli",{"_index":593,"t":{"1311":{"position":[[83,9]]}}}],["cosmo",{"_index":290,"t":{"582":{"position":[[0,6]]}}}],["coverag",{"_index":411,"t":{"854":{"position":[[21,8]]}}}],["covid",{"_index":636,"t":{"1405":{"position":[[0,5]]}}}],["cpanel",{"_index":406,"t":{"830":{"position":[[0,6]]}}}],["cpu",{"_index":556,"t":{"1270":{"position":[[5,3]]},"1450":{"position":[[0,3]]},"1515":{"position":[[8,3]]},"1517":{"position":[[8,3]]},"1519":{"position":[[0,3]]},"1521":{"position":[[0,3]]},"1547":{"position":[[0,3]]}}}],["crash",{"_index":533,"t":{"1231":{"position":[[10,7]]}}}],["creat",{"_index":61,"t":{"85":{"position":[[0,6]]},"87":{"position":[[0,6]]},"89":{"position":[[0,6]]},"91":{"position":[[0,6]]},"93":{"position":[[0,6]]},"154":{"position":[[3,6]]},"190":{"position":[[0,6]]},"194":{"position":[[0,6]]},"198":{"position":[[0,6]]},"202":{"position":[[0,6]]},"268":{"position":[[0,6]]},"270":{"position":[[0,6]]},"272":{"position":[[0,6]]},"323":{"position":[[0,6]]},"325":{"position":[[0,6]]},"327":{"position":[[0,6]]},"333":{"position":[[0,6]]},"357":{"position":[[0,8]]},"628":{"position":[[0,8]]},"636":{"position":[[3,6]]},"650":{"position":[[3,6]]},"665":{"position":[[9,6]]},"848":{"position":[[3,6]]},"975":{"position":[[0,8]]}}}],["credenti",{"_index":88,"t":{"127":{"position":[[7,11]]}}}],["croco_griev",{"_index":483,"t":{"1073":{"position":[[0,14]]},"1134":{"position":[[0,14]]}}}],["crypto",{"_index":484,"t":{"1075":{"position":[[0,6]]},"1136":{"position":[[0,6]]},"1387":{"position":[[0,6]]},"1389":{"position":[[0,6]]}}}],["css",{"_index":499,"t":{"1179":{"position":[[15,3]]},"1183":{"position":[[15,3]]},"1195":{"position":[[0,3]]}}}],["current",{"_index":569,"t":{"1276":{"position":[[36,7]]},"1515":{"position":[[0,7]]},"1517":{"position":[[0,7]]},"1523":{"position":[[0,7]]},"1525":{"position":[[0,7]]}}}],["custom",{"_index":378,"t":{"780":{"position":[[0,6]]},"1012":{"position":[[0,6]]},"1018":{"position":[[6,6]]},"1160":{"position":[[8,6]]},"1183":{"position":[[8,6]]},"1439":{"position":[[0,6]]},"1441":{"position":[[0,6]]},"1562":{"position":[[8,14]]},"1579":{"position":[[0,11]]}}}],["customis",{"_index":444,"t":{"960":{"position":[[11,13]]}}}],["daemon",{"_index":395,"t":{"804":{"position":[[24,6]]}}}],["dash",{"_index":463,"t":{"1035":{"position":[[8,4]]},"1075":{"position":[[7,4]]},"1096":{"position":[[8,4]]},"1136":{"position":[[7,4]]}}}],["dashboard",{"_index":198,"t":{"437":{"position":[[11,9]]},"1041":{"position":[[9,9]]},"1047":{"position":[[14,9]]},"1049":{"position":[[20,9]]},"1053":{"position":[[4,9]]},"1055":{"position":[[12,9]]},"1081":{"position":[[8,9]]},"1084":{"position":[[16,9]]},"1102":{"position":[[9,9]]},"1108":{"position":[[14,9]]},"1110":{"position":[[20,9]]},"1114":{"position":[[4,9]]},"1116":{"position":[[12,9]]},"1142":{"position":[[8,9]]},"1145":{"position":[[16,9]]},"1293":{"position":[[35,9]]}}}],["dashi",{"_index":44,"t":{"62":{"position":[[24,5]]},"95":{"position":[[24,5]]},"97":{"position":[[25,5]]},"127":{"position":[[23,5]]},"160":{"position":[[13,5]]},"173":{"position":[[10,5]]},"194":{"position":[[11,5]]},"198":{"position":[[11,5]]},"206":{"position":[[24,5]]},"276":{"position":[[25,5]]},"300":{"position":[[16,5]]},"302":{"position":[[10,5]]},"337":{"position":[[23,5]]},"739":{"position":[[9,5]]},"748":{"position":[[0,5]]},"758":{"position":[[37,5]]},"942":{"position":[[5,5]]},"1039":{"position":[[12,5]]},"1059":{"position":[[0,5]]},"1067":{"position":[[0,5]]},"1100":{"position":[[12,5]]},"1120":{"position":[[0,5]]},"1128":{"position":[[0,5]]},"1252":{"position":[[0,5]]},"1297":{"position":[[16,5]]}}}],["dashy'",{"_index":112,"t":{"192":{"position":[[6,7]]}}}],["data",{"_index":425,"t":{"897":{"position":[[16,4]]},"956":{"position":[[8,4]]},"1349":{"position":[[29,4]]},"1423":{"position":[[7,4]]},"1558":{"position":[[11,4]]},"1560":{"position":[[0,4]]}}}],["databas",{"_index":40,"t":{"56":{"position":[[6,8]]}}}],["date",{"_index":383,"t":{"790":{"position":[[18,4]]}}}],["day",{"_index":654,"t":{"1425":{"position":[[25,3]]}}}],["default",{"_index":1,"t":{"5":{"position":[[0,7]]},"697":{"position":[[0,7]]}}}],["defin",{"_index":72,"t":{"115":{"position":[[0,8]]}}}],["delet",{"_index":423,"t":{"897":{"position":[[0,8]]}}}],["deni",{"_index":509,"t":{"1211":{"position":[[11,6]]}}}],["departur",{"_index":660,"t":{"1435":{"position":[[4,9]]}}}],["depend",{"_index":230,"t":{"469":{"position":[[0,12]]},"624":{"position":[[0,12]]},"644":{"position":[[9,12]]},"919":{"position":[[0,12]]}}}],["deploy",{"_index":32,"t":{"51":{"position":[[3,6]]},"82":{"position":[[3,6]]},"187":{"position":[[3,6]]},"263":{"position":[[3,6]]},"319":{"position":[[3,6]]},"483":{"position":[[0,10]]},"492":{"position":[[12,6]]},"936":{"position":[[9,10]]},"964":{"position":[[12,10]]},"966":{"position":[[12,10]]},"968":{"position":[[12,10]]},"1332":{"position":[[31,6]]}}}],["design",{"_index":348,"t":{"691":{"position":[[9,6]]}}}],["dev",{"_index":297,"t":{"594":{"position":[[15,3]]},"648":{"position":[[15,3]]}}}],["develop",{"_index":313,"t":{"621":{"position":[[0,11]]},"646":{"position":[[0,10]]}}}],["devtool",{"_index":591,"t":{"1311":{"position":[[38,9]]}}}],["differ",{"_index":492,"t":{"1158":{"position":[[8,9]]}}}],["digit",{"_index":587,"t":{"1309":{"position":[[53,5]]}}}],["digitalocean",{"_index":267,"t":{"536":{"position":[[0,12]]}}}],["dipan'",{"_index":462,"t":{"1035":{"position":[[0,7]]},"1096":{"position":[[0,7]]}}}],["directli",{"_index":458,"t":{"1026":{"position":[[13,8]]}}}],["directori",{"_index":442,"t":{"956":{"position":[[13,9]]}}}],["disabl",{"_index":391,"t":{"802":{"position":[[0,7]]},"932":{"position":[[0,9]]},"1024":{"position":[[0,9]]},"1162":{"position":[[0,9]]}}}],["discuss",{"_index":212,"t":{"451":{"position":[[9,10]]}}}],["disk",{"_index":691,"t":{"1529":{"position":[[0,4]]},"1531":{"position":[[0,4]]}}}],["display",{"_index":610,"t":{"1349":{"position":[[7,10]]},"1353":{"position":[[23,10]]},"1364":{"position":[[22,10]]}}}],["dn",{"_index":380,"t":{"782":{"position":[[6,3]]},"1476":{"position":[[13,3]]}}}],["doc",{"_index":209,"t":{"447":{"position":[[12,4]]},"671":{"position":[[9,4]]},"992":{"position":[[0,4]]}}}],["docker",{"_index":272,"t":{"550":{"position":[[0,6]]},"552":{"position":[[0,6]]},"735":{"position":[[9,6]]},"737":{"position":[[10,6]]},"764":{"position":[[25,6]]},"790":{"position":[[5,6]]},"804":{"position":[[17,6]]},"984":{"position":[[0,6]]},"1049":{"position":[[13,6]]},"1110":{"position":[[13,6]]},"1313":{"position":[[0,6]]},"1322":{"position":[[23,6]]},"1324":{"position":[[0,6]]}}}],["dockerhub",{"_index":597,"t":{"1318":{"position":[[0,9]]}}}],["domain",{"_index":379,"t":{"780":{"position":[[7,6]]},"1385":{"position":[[0,6]]},"1478":{"position":[[17,7]]}}}],["don't",{"_index":385,"t":{"794":{"position":[[0,5]]},"804":{"position":[[0,5]]}}}],["donat",{"_index":201,"t":{"439":{"position":[[13,8]]}}}],["download",{"_index":363,"t":{"741":{"position":[[20,8]]},"1470":{"position":[[9,8]]}}}],["dragon",{"_index":467,"t":{"1045":{"position":[[4,7]]},"1106":{"position":[[4,7]]}}}],["drone",{"_index":682,"t":{"1500":{"position":[[0,5]]}}}],["dure",{"_index":602,"t":{"1332":{"position":[[24,6]]}}}],["dynam",{"_index":698,"t":{"1551":{"position":[[0,7]]}}}],["e.g",{"_index":546,"t":{"1252":{"position":[[27,5]]}}}],["eacc",{"_index":511,"t":{"1211":{"position":[[42,8]]}}}],["easypanel",{"_index":293,"t":{"588":{"position":[[0,9]]}}}],["econnrefus",{"_index":603,"t":{"1341":{"position":[[19,12]]}}}],["edgeon",{"_index":256,"t":{"516":{"position":[[0,7]]}}}],["edit",{"_index":163,"t":{"378":{"position":[[24,4]]},"423":{"position":[[0,7]]}}}],["embed",{"_index":700,"t":{"1554":{"position":[[5,8]]}}}],["emoji",{"_index":307,"t":{"610":{"position":[[7,6]]},"685":{"position":[[0,5]]}}}],["enabl",{"_index":6,"t":{"15":{"position":[[0,8]]},"62":{"position":[[3,8]]},"97":{"position":[[3,8]]},"206":{"position":[[3,8]]},"276":{"position":[[3,8]]},"337":{"position":[[3,8]]},"441":{"position":[[0,6]]},"1154":{"position":[[0,8]]}}}],["endpoint",{"_index":10,"t":{"21":{"position":[[0,9]]},"652":{"position":[[21,8]]},"720":{"position":[[17,8]]},"1158":{"position":[[18,8]]}}}],["enforc",{"_index":84,"t":{"125":{"position":[[7,8]]},"146":{"position":[[7,8]]},"235":{"position":[[16,11]]}}}],["engin",{"_index":456,"t":{"1016":{"position":[[15,6]]},"1018":{"position":[[20,6]]}}}],["enotfound",{"_index":605,"t":{"1341":{"position":[[51,9]]}}}],["env",{"_index":76,"t":{"119":{"position":[[9,3]]}}}],["environ",{"_index":298,"t":{"594":{"position":[[19,11]]},"603":{"position":[[0,11]]},"921":{"position":[[14,11]]},"1276":{"position":[[44,11]]}}}],["environment",{"_index":300,"t":{"601":{"position":[[0,13]]},"660":{"position":[[15,13]]},"766":{"position":[[11,13]]}}}],["erof",{"_index":512,"t":{"1211":{"position":[[51,6]]}}}],["error",{"_index":430,"t":{"915":{"position":[[10,5]]},"1265":{"position":[[15,6]]},"1266":{"position":[[5,5]]},"1278":{"position":[[17,5]]},"1280":{"position":[[0,6]]},"1283":{"position":[[16,6]]},"1285":{"position":[[18,5]]},"1297":{"position":[[22,6]]},"1311":{"position":[[29,5]]},"1328":{"position":[[18,6]]},"1337":{"position":[[7,6]]},"1339":{"position":[[12,6]]},"1345":{"position":[[13,5]]},"1575":{"position":[[9,6]]},"1589":{"position":[[23,6]]}}}],["eth",{"_index":643,"t":{"1415":{"position":[[0,3]]}}}],["evo",{"_index":473,"t":{"1053":{"position":[[0,3]]},"1114":{"position":[[0,3]]}}}],["exampl",{"_index":15,"t":{"27":{"position":[[0,8]]},"105":{"position":[[7,7]]},"431":{"position":[[0,7]]},"770":{"position":[[0,7]]},"862":{"position":[[6,7]]},"1067":{"position":[[6,7]]},"1128":{"position":[[6,7]]}}}],["example.com/dashi",{"_index":547,"t":{"1252":{"position":[[33,18]]}}}],["exchang",{"_index":633,"t":{"1401":{"position":[[0,8]]}}}],["expos",{"_index":394,"t":{"804":{"position":[[6,6]]}}}],["exposur",{"_index":373,"t":{"762":{"position":[[8,8]]}}}],["extern",{"_index":426,"t":{"899":{"position":[[0,8]]},"1187":{"position":[[8,8]]},"1227":{"position":[[19,8]]}}}],["fail",{"_index":494,"t":{"1166":{"position":[[16,7]]},"1217":{"position":[[12,5]]},"1268":{"position":[[11,5]]},"1272":{"position":[[53,6]]},"1274":{"position":[[8,6]]},"1320":{"position":[[15,4]]},"1322":{"position":[[12,7]]},"1324":{"position":[[13,5]]},"1334":{"position":[[14,7]]}}}],["failur",{"_index":574,"t":{"1287":{"position":[[17,7]]}}}],["fair",{"_index":154,"t":{"363":{"position":[[0,4]]}}}],["favicon",{"_index":339,"t":{"677":{"position":[[0,8]]}}}],["featur",{"_index":214,"t":{"453":{"position":[[10,7]]},"923":{"position":[[9,8]]},"932":{"position":[[10,8]]}}}],["feed",{"_index":621,"t":{"1377":{"position":[[4,4]]},"1399":{"position":[[14,4]]},"1560":{"position":[[5,4]]}}}],["field",{"_index":167,"t":{"384":{"position":[[10,6]]}}}],["file",{"_index":322,"t":{"636":{"position":[[25,4]]},"640":{"position":[[12,4]]},"642":{"position":[[34,4]]},"714":{"position":[[0,4]]},"842":{"position":[[7,4]]},"848":{"position":[[25,4]]},"852":{"position":[[12,4]]},"1179":{"position":[[19,4]]},"1261":{"position":[[11,5]]}}}],["filebrows",{"_index":683,"t":{"1502":{"position":[[0,11]]}}}],["filenam",{"_index":529,"t":{"1225":{"position":[[9,9]]}}}],["filesystem",{"_index":510,"t":{"1211":{"position":[[31,10]]}}}],["filter",{"_index":673,"t":{"1474":{"position":[[13,7]]}}}],["final",{"_index":445,"t":{"962":{"position":[[3,5]]}}}],["find",{"_index":549,"t":{"1255":{"position":[[26,4]]},"1280":{"position":[[14,4]]}}}],["firebas",{"_index":257,"t":{"520":{"position":[[0,8]]},"828":{"position":[[0,8]]}}}],["first",{"_index":138,"t":{"266":{"position":[[0,5]]},"1051":{"position":[[0,5]]},"1112":{"position":[[0,5]]}}}],["fit",{"_index":100,"t":{"152":{"position":[[10,4]]},"296":{"position":[[7,4]]}}}],["flight",{"_index":651,"t":{"1423":{"position":[[0,6]]}}}],["flow",{"_index":99,"t":{"144":{"position":[[10,4]]},"606":{"position":[[4,4]]}}}],["fly.io",{"_index":264,"t":{"530":{"position":[[0,6]]}}}],["follow",{"_index":222,"t":{"459":{"position":[[0,6]]}}}],["font",{"_index":340,"t":{"679":{"position":[[0,4]]},"1353":{"position":[[0,4]]}}}],["forbidden",{"_index":523,"t":{"1219":{"position":[[38,9]]}}}],["forecast",{"_index":608,"t":{"1347":{"position":[[8,8]]},"1375":{"position":[[8,8]]}}}],["frontend",{"_index":96,"t":{"137":{"position":[[24,8]]},"475":{"position":[[0,8]]}}}],["full",{"_index":498,"t":{"1179":{"position":[[10,4]]}}}],["function",{"_index":324,"t":{"646":{"position":[[25,9]]},"650":{"position":[[19,8]]},"652":{"position":[[37,8]]},"667":{"position":[[16,13]]}}}],["funnel",{"_index":143,"t":{"304":{"position":[[0,6]]}}}],["furnitur",{"_index":329,"t":{"654":{"position":[[12,9]]}}}],["further",{"_index":443,"t":{"960":{"position":[[3,7]]}}}],["ga",{"_index":644,"t":{"1415":{"position":[[4,3]]}}}],["gazer",{"_index":225,"t":{"463":{"position":[[5,6]]}}}],["gener",{"_index":36,"t":{"54":{"position":[[0,8]]},"117":{"position":[[0,10]]},"683":{"position":[[0,10]]},"1370":{"position":[[0,7]]}}}],["get",{"_index":369,"t":{"756":{"position":[[0,7]]},"1303":{"position":[[18,7]]}}}],["getaddrinfo",{"_index":604,"t":{"1341":{"position":[[39,11]]}}}],["git",{"_index":302,"t":{"605":{"position":[[0,3]]},"606":{"position":[[0,3]]},"608":{"position":[[0,3]]},"994":{"position":[[0,3]]},"1364":{"position":[[0,3]]}}}],["github",{"_index":655,"t":{"1427":{"position":[[0,6]]},"1429":{"position":[[0,6]]}}}],["glanc",{"_index":689,"t":{"1513":{"position":[[0,7]]}}}],["gluetun",{"_index":681,"t":{"1498":{"position":[[0,7]]}}}],["googl",{"_index":269,"t":{"540":{"position":[[0,6]]}}}],["grant",{"_index":148,"t":{"329":{"position":[[0,5]]}}}],["ground",{"_index":481,"t":{"1071":{"position":[[0,6]]},"1132":{"position":[[0,6]]}}}],["group",{"_index":46,"t":{"64":{"position":[[3,6]]},"85":{"position":[[11,6]]},"91":{"position":[[17,5]]},"99":{"position":[[3,6]]},"200":{"position":[[20,5]]},"208":{"position":[[3,6]]},"270":{"position":[[17,5]]},"278":{"position":[[3,6]]},"331":{"position":[[15,6]]},"339":{"position":[[3,6]]}}}],["guest",{"_index":91,"t":{"129":{"position":[[10,5]]},"131":{"position":[[0,5]]},"231":{"position":[[0,5]]}}}],["guid",{"_index":118,"t":{"223":{"position":[[13,6]]},"617":{"position":[[6,5]]},"1563":{"position":[[13,5]]}}}],["guidelin",{"_index":308,"t":{"612":{"position":[[3,10]]}}}],["hackernew",{"_index":658,"t":{"1433":{"position":[[0,10]]}}}],["handl",{"_index":703,"t":{"1569":{"position":[[0,8]]}}}],["hard",{"_index":502,"t":{"1189":{"position":[[0,4]]}}}],["hash",{"_index":74,"t":{"117":{"position":[[22,4]]},"119":{"position":[[38,4]]},"1309":{"position":[[59,4]]}}}],["header",{"_index":22,"t":{"37":{"position":[[0,6]]},"768":{"position":[[8,7]]},"770":{"position":[[8,7]]},"1160":{"position":[[15,7]]},"1305":{"position":[[0,6]]},"1307":{"position":[[0,6],[42,7]]},"1330":{"position":[[13,6]]}}}],["headlin",{"_index":640,"t":{"1409":{"position":[[5,9]]}}}],["headscal",{"_index":145,"t":{"306":{"position":[[6,9]]}}}],["healthcheck",{"_index":355,"t":{"718":{"position":[[0,12]]},"720":{"position":[[5,11]]},"1322":{"position":[[0,11]]},"1431":{"position":[[0,12]]}}}],["heap",{"_index":564,"t":{"1272":{"position":[[31,4]]}}}],["hide",{"_index":328,"t":{"654":{"position":[[0,6]]},"656":{"position":[[36,4]]},"658":{"position":[[54,4]]},"1010":{"position":[[0,6]]}}}],["high",{"_index":446,"t":{"973":{"position":[[0,4]]},"1270":{"position":[[0,4]]}}}],["histori",{"_index":626,"t":{"1389":{"position":[[19,7]]},"1413":{"position":[[12,7]]},"1450":{"position":[[4,7]]},"1452":{"position":[[7,7]]},"1454":{"position":[[5,7]]},"1521":{"position":[[10,7]]},"1527":{"position":[[13,7]]},"1537":{"position":[[12,7]]}}}],["hole",{"_index":664,"t":{"1456":{"position":[[3,4]]},"1458":{"position":[[3,4]]},"1460":{"position":[[3,4]]},"1462":{"position":[[3,4]]},"1464":{"position":[[3,4]]},"1466":{"position":[[3,4]]}}}],["holiday",{"_index":635,"t":{"1403":{"position":[[7,8]]}}}],["home",{"_index":345,"t":{"689":{"position":[[0,4]]},"1034":{"position":[[0,4]]},"1041":{"position":[[4,4]]},"1095":{"position":[[0,4]]},"1102":{"position":[[4,4]]},"1248":{"position":[[29,4]]},"1472":{"position":[[8,4]]},"1474":{"position":[[8,4]]},"1476":{"position":[[8,4]]},"1478":{"position":[[8,4]]}}}],["homelab",{"_index":469,"t":{"1047":{"position":[[0,7]]},"1069":{"position":[[0,7]]},"1079":{"position":[[0,7]]},"1083":{"position":[[12,7]]},"1108":{"position":[[0,7]]},"1130":{"position":[[0,7]]},"1140":{"position":[[0,7]]},"1144":{"position":[[12,7]]}}}],["homepag",{"_index":454,"t":{"1010":{"position":[[22,8]]}}}],["host",{"_index":135,"t":{"257":{"position":[[6,7]]},"365":{"position":[[5,7]]},"511":{"position":[[7,7]]},"520":{"position":[[9,7]]},"542":{"position":[[8,7]]},"544":{"position":[[0,7]]},"828":{"position":[[9,7]]},"1051":{"position":[[19,7]]},"1112":{"position":[[19,7]]},"1221":{"position":[[52,5]]},"1246":{"position":[[14,7]]},"1252":{"position":[[6,6]]},"1330":{"position":[[8,4]]},"1343":{"position":[[22,4]]},"1447":{"position":[[5,6]]}}}],["hotkey",{"_index":455,"t":{"1012":{"position":[[7,7]]}}}],["html",{"_index":527,"t":{"1223":{"position":[[36,4]]},"1554":{"position":[[0,4]]}}}],["http",{"_index":356,"t":{"720":{"position":[[0,4]]},"1343":{"position":[[55,7],[66,8]]}}}],["hugalafutro",{"_index":465,"t":{"1039":{"position":[[0,11]]},"1100":{"position":[[0,11]]}}}],["icon",{"_index":343,"t":{"681":{"position":[[7,5]]},"683":{"position":[[11,5]]},"685":{"position":[[6,5]]},"687":{"position":[[9,5]]},"689":{"position":[[9,5]]},"691":{"position":[[16,5]]},"693":{"position":[[0,5]]},"695":{"position":[[6,5]]},"697":{"position":[[8,4]]},"699":{"position":[[3,4]]},"701":{"position":[[0,4]]},"901":{"position":[[0,5]]},"1353":{"position":[[13,5]]}}}],["id",{"_index":137,"t":{"263":{"position":[[17,2]]},"265":{"position":[[20,2]]},"276":{"position":[[19,2]]},"282":{"position":[[30,2]]},"1287":{"position":[[43,3]]}}}],["idp",{"_index":592,"t":{"1311":{"position":[[56,3]]}}}],["ifram",{"_index":699,"t":{"1552":{"position":[[0,6]]}}}],["ignor",{"_index":553,"t":{"1259":{"position":[[9,7]]},"1575":{"position":[[0,8]]}}}],["imag",{"_index":397,"t":{"810":{"position":[[7,5]]},"1313":{"position":[[9,5]]},"1320":{"position":[[4,5]]},"1379":{"position":[[0,5]]}}}],["improv",{"_index":208,"t":{"447":{"position":[[0,7]]}}}],["inaccur",{"_index":611,"t":{"1349":{"position":[[18,10]]}}}],["includ",{"_index":488,"t":{"1087":{"position":[[8,7]]},"1148":{"position":[[8,7]]}}}],["incorrectli",{"_index":606,"t":{"1345":{"position":[[19,11]]}}}],["index.j",{"_index":159,"t":{"370":{"position":[[9,8]]}}}],["indic",{"_index":490,"t":{"1154":{"position":[[16,10]]}}}],["ineffect",{"_index":560,"t":{"1272":{"position":[[0,11]]}}}],["info",{"_index":310,"t":{"616":{"position":[[4,4]]},"1448":{"position":[[7,4]]},"1476":{"position":[[17,4]]},"1498":{"position":[[12,4]]}}}],["initi",{"_index":429,"t":{"913":{"position":[[0,14]]}}}],["inlin",{"_index":79,"t":{"119":{"position":[[31,6]]}}}],["insid",{"_index":420,"t":{"881":{"position":[[6,6]]},"1268":{"position":[[17,6]]}}}],["instal",{"_index":252,"t":{"507":{"position":[[0,7]]},"954":{"position":[[3,12]]}}}],["instead",{"_index":78,"t":{"119":{"position":[[17,7]]},"306":{"position":[[16,7]]}}}],["integr",{"_index":432,"t":{"924":{"position":[[12,9]]}}}],["intend",{"_index":436,"t":{"936":{"position":[[0,8]]}}}],["intention",{"_index":538,"t":{"1233":{"position":[[0,13]]}}}],["inter",{"_index":392,"t":{"802":{"position":[[8,5]]}}}],["interfac",{"_index":694,"t":{"1539":{"position":[[8,10]]}}}],["invalid",{"_index":528,"t":{"1225":{"position":[[0,8]]},"1309":{"position":[[0,8]]},"1330":{"position":[[0,7]]}}}],["invalid_redirect_uri",{"_index":576,"t":{"1291":{"position":[[0,20]]}}}],["io",{"_index":693,"t":{"1531":{"position":[[5,2]]}}}],["ip",{"_index":127,"t":{"249":{"position":[[0,2]]},"1351":{"position":[[7,2]]},"1381":{"position":[[7,2]]},"1383":{"position":[[0,2]]},"1545":{"position":[[0,2]]}}}],["ipinfo",{"_index":612,"t":{"1351":{"position":[[33,6]]}}}],["ipqueri",{"_index":613,"t":{"1351":{"position":[[43,7]]}}}],["issu",{"_index":53,"t":{"68":{"position":[[32,6]]},"103":{"position":[[33,6]]},"139":{"position":[[37,6]]},"164":{"position":[[41,6]]},"210":{"position":[[32,6]]},"282":{"position":[[33,6]]},"310":{"position":[[33,6]]},"343":{"position":[[31,6]]},"948":{"position":[[21,5]]},"1245":{"position":[[14,6]]},"1313":{"position":[[15,6]]},"1591":{"position":[[11,5]]}}}],["item",{"_index":13,"t":{"23":{"position":[[24,5]]},"135":{"position":[[20,4]]},"881":{"position":[[0,5]]},"883":{"position":[[4,5]]},"1010":{"position":[[7,5]]},"1189":{"position":[[23,4]]}}}],["item.displaydata",{"_index":179,"t":{"412":{"position":[[0,16]]}}}],["item.displaydata.hideforkeycloakus",{"_index":186,"t":{"420":{"position":[[84,37]]}}}],["item.displaydata.showforkeycloakus",{"_index":187,"t":{"420":{"position":[[126,37]]}}}],["join",{"_index":211,"t":{"451":{"position":[[0,4]]}}}],["joke",{"_index":645,"t":{"1417":{"position":[[0,4]]}}}],["keep",{"_index":382,"t":{"790":{"position":[[0,4]]}}}],["key",{"_index":38,"t":{"54":{"position":[[21,4]]}}}],["keycloak",{"_index":21,"t":{"35":{"position":[[0,8]]},"187":{"position":[[10,8]]},"189":{"position":[[13,8]]},"190":{"position":[[11,8]]},"206":{"position":[[12,8]]},"210":{"position":[[23,8]]},"1285":{"position":[[0,8]]},"1287":{"position":[[8,8]]}}}],["known",{"_index":440,"t":{"944":{"position":[[0,5]]}}}],["koyeb",{"_index":265,"t":{"532":{"position":[[0,5]]}}}],["kubernet",{"_index":285,"t":{"570":{"position":[[0,10]]},"1213":{"position":[[0,10]]}}}],["kuma",{"_index":686,"t":{"1506":{"position":[[7,4]]},"1508":{"position":[[7,4]]}}}],["lab",{"_index":346,"t":{"689":{"position":[[5,3]]},"1034":{"position":[[5,3]]},"1095":{"position":[[5,3]]}}}],["label",{"_index":705,"t":{"1573":{"position":[[7,6]]}}}],["lair",{"_index":468,"t":{"1045":{"position":[[12,4]]},"1106":{"position":[[12,4]]}}}],["lambda",{"_index":325,"t":{"650":{"position":[[12,6]]}}}],["languag",{"_index":321,"t":{"636":{"position":[[16,8]]},"838":{"position":[[8,8]]},"844":{"position":[[10,9]]},"846":{"position":[[13,8]]},"848":{"position":[[16,8]]},"1581":{"position":[[0,8]]}}}],["launch",{"_index":453,"t":{"1006":{"position":[[0,9]]},"1248":{"position":[[10,6]]}}}],["layout",{"_index":417,"t":{"877":{"position":[[0,6]]},"1259":{"position":[[28,6]]}}}],["leav",{"_index":220,"t":{"457":{"position":[[16,5]]},"1299":{"position":[[9,6]]}}}],["level",{"_index":166,"t":{"384":{"position":[[4,5]]},"808":{"position":[[16,5]]},"973":{"position":[[5,5]]},"1197":{"position":[[4,5]]}}}],["licens",{"_index":352,"t":{"706":{"position":[[4,7]]}}}],["lighthous",{"_index":316,"t":{"622":{"position":[[14,10]]}}}],["lighthttpd",{"_index":542,"t":{"1243":{"position":[[0,10]]}}}],["limit",{"_index":16,"t":{"29":{"position":[[0,11]]},"798":{"position":[[0,5]]},"944":{"position":[[6,11]]},"1272":{"position":[[36,5]]}}}],["linkd",{"_index":684,"t":{"1504":{"position":[[0,8]]}}}],["list",{"_index":624,"t":{"1387":{"position":[[13,4]]},"1439":{"position":[[7,4]]},"1494":{"position":[[8,5]]}}}],["live",{"_index":475,"t":{"1059":{"position":[[6,4]]},"1120":{"position":[[6,4]]}}}],["load",{"_index":500,"t":{"1187":{"position":[[0,7]]},"1263":{"position":[[18,7]]},"1454":{"position":[[0,4]]},"1533":{"position":[[7,4]]},"1537":{"position":[[7,4]]}}}],["local",{"_index":113,"t":{"192":{"position":[[14,5]]},"695":{"position":[[0,5]]},"869":{"position":[[6,5]]},"895":{"position":[[0,5]]},"1358":{"position":[[13,5]]}}}],["lockdown",{"_index":433,"t":{"930":{"position":[[14,8]]}}}],["log",{"_index":357,"t":{"722":{"position":[[0,4]]},"723":{"position":[[10,4]]},"729":{"position":[[9,7]]},"808":{"position":[[8,7]]},"1295":{"position":[[0,6]]}}}],["login",{"_index":83,"t":{"123":{"position":[[12,5]]},"127":{"position":[[29,5]]},"144":{"position":[[4,5]]},"1289":{"position":[[20,5]]},"1293":{"position":[[0,5]]},"1297":{"position":[[0,5]]},"1311":{"position":[[5,5]]},"1324":{"position":[[7,5]]}}}],["logout",{"_index":92,"t":{"129":{"position":[[25,6]]}}}],["look",{"_index":589,"t":{"1311":{"position":[[11,5]]}}}],["loop",{"_index":535,"t":{"1231":{"position":[[29,4]]},"1289":{"position":[[9,4]]}}}],["main",{"_index":41,"t":{"58":{"position":[[0,4]]}}}],["make",{"_index":199,"t":{"439":{"position":[[0,4]]},"879":{"position":[[0,6]]},"1360":{"position":[[7,4]]}}}],["manag",{"_index":270,"t":{"542":{"position":[[0,7]]},"727":{"position":[[0,10]]},"764":{"position":[[0,8]]},"1066":{"position":[[8,10]]},"1127":{"position":[[8,10]]}}}],["manager/sav",{"_index":525,"t":{"1223":{"position":[[8,12]]}}}],["map",{"_index":149,"t":{"331":{"position":[[0,3]]}}}],["mapper",{"_index":115,"t":{"196":{"position":[[19,6]]},"218":{"position":[[8,6]]}}}],["mark",{"_index":561,"t":{"1272":{"position":[[12,4]]}}}],["materi",{"_index":347,"t":{"691":{"position":[[0,8]]}}}],["matter",{"_index":116,"t":{"218":{"position":[[15,7]]}}}],["memori",{"_index":554,"t":{"1265":{"position":[[8,6]]},"1452":{"position":[[0,6]]},"1523":{"position":[[8,6]]},"1525":{"position":[[8,6]]},"1527":{"position":[[0,6]]}}}],["metadata",{"_index":415,"t":{"865":{"position":[[5,8]]}}}],["metal",{"_index":239,"t":{"490":{"position":[[5,5]]}}}],["method",{"_index":5,"t":{"11":{"position":[[8,7]]},"43":{"position":[[27,7]]},"427":{"position":[[14,7]]},"483":{"position":[[11,7]]},"964":{"position":[[23,6]]},"966":{"position":[[23,6]]},"968":{"position":[[23,6]]},"1020":{"position":[[16,6]]}}}],["metric",{"_index":697,"t":{"1549":{"position":[[8,7]]}}}],["minecraft",{"_index":662,"t":{"1445":{"position":[[0,9]]}}}],["minim",{"_index":3,"t":{"9":{"position":[[0,7]]}}}],["minut",{"_index":195,"t":{"435":{"position":[[9,6]]}}}],["mirror",{"_index":451,"t":{"990":{"position":[[0,6]]}}}],["mismatch",{"_index":596,"t":{"1316":{"position":[[11,8]]}}}],["miss",{"_index":520,"t":{"1219":{"position":[[15,7]]},"1257":{"position":[[9,7]]},"1307":{"position":[[29,7]]}}}],["mit",{"_index":351,"t":{"706":{"position":[[0,3]]}}}],["mndashboard",{"_index":460,"t":{"1032":{"position":[[0,11]]},"1093":{"position":[[0,11]]}}}],["mobil",{"_index":543,"t":{"1248":{"position":[[22,6]]}}}],["modal",{"_index":541,"t":{"1235":{"position":[[22,5]]}}}],["mode",{"_index":82,"t":{"121":{"position":[[22,4]]},"144":{"position":[[28,5]]},"146":{"position":[[16,4]]},"603":{"position":[[12,5]]},"1233":{"position":[[24,4]]}}}],["model",{"_index":435,"t":{"934":{"position":[[7,5]]}}}],["modifi",{"_index":407,"t":{"832":{"position":[[10,8]]},"1181":{"position":[[0,9]]}}}],["modul",{"_index":403,"t":{"818":{"position":[[9,7]]},"1280":{"position":[[19,6]]}}}],["monitor",{"_index":359,"t":{"729":{"position":[[21,10]]},"1061":{"position":[[7,7]]},"1122":{"position":[[7,7]]},"1385":{"position":[[7,7]]},"1512":{"position":[[16,10]]}}}],["more",{"_index":223,"t":{"459":{"position":[[11,4]]}}}],["morn",{"_index":486,"t":{"1081":{"position":[[0,7]]},"1142":{"position":[[0,7]]}}}],["mount",{"_index":514,"t":{"1213":{"position":[[21,5]]},"1316":{"position":[[0,5]]}}}],["mtl",{"_index":131,"t":{"253":{"position":[[20,6]]}}}],["mullvad",{"_index":630,"t":{"1395":{"position":[[0,7]]}}}],["multi",{"_index":119,"t":{"229":{"position":[[0,5]]},"867":{"position":[[0,5]]},"1250":{"position":[[7,5]]}}}],["mvg",{"_index":659,"t":{"1435":{"position":[[0,3]]},"1437":{"position":[[0,3]]}}}],["na",{"_index":283,"t":{"566":{"position":[[9,3]]},"1041":{"position":[[0,3]]},"1102":{"position":[[0,3]]}}}],["name",{"_index":305,"t":{"608":{"position":[[11,6]]},"630":{"position":[[13,4]]},"656":{"position":[[17,4]]}}}],["nav",{"_index":550,"t":{"1257":{"position":[[22,4]]}}}],["navig",{"_index":452,"t":{"1004":{"position":[[0,10]]}}}],["near",{"_index":563,"t":{"1272":{"position":[[26,4]]}}}],["netdata",{"_index":663,"t":{"1450":{"position":[[12,9]]},"1452":{"position":[[15,9]]},"1454":{"position":[[13,9]]}}}],["netlifi",{"_index":254,"t":{"512":{"position":[[0,7]]},"646":{"position":[[11,7]]},"648":{"position":[[7,7]]},"966":{"position":[[34,7]]},"1221":{"position":[[28,7]]}}}],["network",{"_index":372,"t":{"762":{"position":[[0,7]]},"1057":{"position":[[0,10]]},"1118":{"position":[[0,10]]},"1539":{"position":[[0,7]]},"1541":{"position":[[0,7]]}}}],["new",{"_index":318,"t":{"628":{"position":[[11,3]]},"636":{"position":[[12,3]]},"642":{"position":[[9,3]]},"800":{"position":[[8,3]]},"846":{"position":[[9,3]]},"848":{"position":[[12,3]]},"856":{"position":[[7,3]]},"1409":{"position":[[0,4]]}}}],["newest",{"_index":228,"t":{"468":{"position":[[0,6]]}}}],["next",{"_index":276,"t":{"556":{"position":[[5,4]]}}}],["nextcloud",{"_index":674,"t":{"1480":{"position":[[0,9]]},"1482":{"position":[[0,9]]},"1484":{"position":[[0,9]]},"1486":{"position":[[0,9]]},"1488":{"position":[[0,9]]},"1490":{"position":[[0,9]]}}}],["nginx",{"_index":381,"t":{"784":{"position":[[6,5]]},"822":{"position":[[0,5]]},"1237":{"position":[[0,5]]}}}],["ngrok",{"_index":600,"t":{"1330":{"position":[[42,5]]}}}],["nixo",{"_index":284,"t":{"568":{"position":[[0,5]]}}}],["node",{"_index":327,"t":{"652":{"position":[[16,4]]},"1276":{"position":[[0,4]]}}}],["non",{"_index":507,"t":{"1201":{"position":[[0,3]]}}}],["norri",{"_index":647,"t":{"1419":{"position":[[6,6]]}}}],["northflank",{"_index":266,"t":{"534":{"position":[[0,10]]}}}],["note",{"_index":18,"t":{"29":{"position":[[14,5]]},"179":{"position":[[9,5]]},"422":{"position":[[0,5]]},"703":{"position":[[0,5]]},"962":{"position":[[9,4]]}}}],["notif",{"_index":675,"t":{"1484":{"position":[[10,13]]}}}],["ntfi",{"_index":678,"t":{"1492":{"position":[[0,4]]}}}],["numer",{"_index":575,"t":{"1287":{"position":[[28,7]]},"1303":{"position":[[0,7]]}}}],["oauth",{"_index":133,"t":{"255":{"position":[[6,5]]}}}],["object",{"_index":573,"t":{"1283":{"position":[[34,7]]},"1309":{"position":[[14,7]]}}}],["offlin",{"_index":125,"t":{"245":{"position":[[17,7]]}}}],["oidc",{"_index":23,"t":{"39":{"position":[[0,4]]},"87":{"position":[[11,4]]},"225":{"position":[[6,4]]},"268":{"position":[[11,4]]},"325":{"position":[[11,4]]},"1282":{"position":[[7,4]]},"1287":{"position":[[0,4]]},"1297":{"position":[[50,5]]},"1311":{"position":[[0,4]]}}}],["old",{"_index":532,"t":{"1229":{"position":[[40,3]]},"1320":{"position":[[0,3]]}}}],["opcach",{"_index":677,"t":{"1490":{"position":[[14,7]]}}}],["open",{"_index":4,"t":{"11":{"position":[[0,7]]},"1020":{"position":[[8,7]]},"1026":{"position":[[0,7]]},"1257":{"position":[[36,4]]},"1362":{"position":[[7,4]]}}}],["option",{"_index":29,"t":{"45":{"position":[[19,7]]},"66":{"position":[[24,10]]},"95":{"position":[[30,10]]},"101":{"position":[[24,10]]},"280":{"position":[[24,10]]},"304":{"position":[[25,10]]},"341":{"position":[[24,10]]},"388":{"position":[[18,10]]},"390":{"position":[[8,10]]},"392":{"position":[[10,10]]},"394":{"position":[[15,10]]},"396":{"position":[[21,10]]},"398":{"position":[[24,10]]},"400":{"position":[[26,10]]},"402":{"position":[[20,10]]},"404":{"position":[[20,10]]},"406":{"position":[[25,10]]},"412":{"position":[[17,10]]},"414":{"position":[[16,10]]},"416":{"position":[[20,10]]},"642":{"position":[[13,6]]},"1177":{"position":[[0,6]]},"1179":{"position":[[0,6]]},"1583":{"position":[[10,7]]}}}],["origin",{"_index":114,"t":{"192":{"position":[[20,6]]}}}],["out",{"_index":580,"t":{"1299":{"position":[[5,3]]}}}],["over",{"_index":226,"t":{"463":{"position":[[12,4]]}}}],["overview",{"_index":59,"t":{"76":{"position":[[7,8]]},"220":{"position":[[7,8]]},"290":{"position":[[7,8]]},"351":{"position":[[7,8]]},"1443":{"position":[[11,8]]}}}],["ownership",{"_index":354,"t":{"714":{"position":[[5,9]]}}}],["page",{"_index":89,"t":{"127":{"position":[[35,5]]},"390":{"position":[[0,7]]},"516":{"position":[[8,5]]},"518":{"position":[[11,5]]},"654":{"position":[[7,4]]},"865":{"position":[[0,4]]},"867":{"position":[[6,4]]},"869":{"position":[[16,5]]},"871":{"position":[[17,5]]},"913":{"position":[[15,4]]},"1185":{"position":[[0,4]]},"1225":{"position":[[37,4]]},"1250":{"position":[[13,4]]},"1254":{"position":[[4,5]]},"1255":{"position":[[4,4]]},"1257":{"position":[[4,4]]},"1259":{"position":[[4,4]]},"1508":{"position":[[19,4]]}}}],["pageinfo",{"_index":168,"t":{"386":{"position":[[0,8]]}}}],["pageinfo.navlink",{"_index":169,"t":{"388":{"position":[[0,17]]}}}],["pass",{"_index":371,"t":{"758":{"position":[[0,7]]},"766":{"position":[[0,7]]}}}],["password",{"_index":73,"t":{"117":{"position":[[13,8]]}}}],["patch",{"_index":441,"t":{"946":{"position":[[9,5]]}}}],["path",{"_index":545,"t":{"1252":{"position":[[22,4]]}}}],["per",{"_index":94,"t":{"135":{"position":[[0,3],[16,3]]},"1519":{"position":[[10,3]]}}}],["perform",{"_index":315,"t":{"622":{"position":[[0,11]]},"722":{"position":[[9,11]]},"725":{"position":[[10,11]]}}}],["permiss",{"_index":338,"t":{"673":{"position":[[18,11]]},"714":{"position":[[19,11]]},"1211":{"position":[[0,10]]}}}],["php",{"_index":676,"t":{"1490":{"position":[[10,3]]}}}],["pi",{"_index":471,"t":{"1049":{"position":[[10,2]]},"1110":{"position":[[10,2]]},"1456":{"position":[[0,2]]},"1458":{"position":[[0,2]]},"1460":{"position":[[0,2]]},"1462":{"position":[[0,2]]},"1464":{"position":[[0,2]]},"1466":{"position":[[0,2]]}}}],["pictur",{"_index":653,"t":{"1425":{"position":[[10,7]]}}}],["ping",{"_index":669,"t":{"1468":{"position":[[5,4]]}}}],["placehold",{"_index":588,"t":{"1309":{"position":[[67,11]]}}}],["platform",{"_index":268,"t":{"536":{"position":[[17,8]]}}}],["pocket",{"_index":136,"t":{"263":{"position":[[10,6]]},"265":{"position":[[13,6]]},"276":{"position":[[12,6]]},"282":{"position":[[23,6]]}}}],["podman",{"_index":274,"t":{"554":{"position":[[0,6]]}}}],["polici",{"_index":105,"t":{"158":{"position":[[27,6]]},"363":{"position":[[9,6]]},"946":{"position":[[15,6]]}}}],["popul",{"_index":156,"t":{"368":{"position":[[0,8]]}}}],["portain",{"_index":286,"t":{"574":{"position":[[0,9]]}}}],["pr",{"_index":207,"t":{"445":{"position":[[9,2]]},"612":{"position":[[0,2]]}}}],["practic",{"_index":108,"t":{"162":{"position":[[8,9]]},"308":{"position":[[5,9]]}}}],["pre",{"_index":245,"t":{"499":{"position":[[0,3]]}}}],["prepar",{"_index":141,"t":{"298":{"position":[[0,7]]}}}],["prerequisit",{"_index":299,"t":{"595":{"position":[[0,13]]},"663":{"position":[[9,13]]},"952":{"position":[[3,13]]}}}],["prevent",{"_index":192,"t":{"429":{"position":[[0,10]]},"800":{"position":[[0,7]]}}}],["price",{"_index":625,"t":{"1389":{"position":[[13,5]]},"1413":{"position":[[6,5]]},"1415":{"position":[[8,6]]}}}],["privaci",{"_index":153,"t":{"361":{"position":[[0,7]]}}}],["privat",{"_index":474,"t":{"1055":{"position":[[4,7]]},"1116":{"position":[[4,7]]}}}],["privileg",{"_index":389,"t":{"800":{"position":[[12,10]]}}}],["process",{"_index":447,"t":{"973":{"position":[[15,7]]}}}],["profil",{"_index":657,"t":{"1429":{"position":[[7,7]]}}}],["project",{"_index":147,"t":{"323":{"position":[[11,7]]},"597":{"position":[[12,7]]},"599":{"position":[[0,7]]},"1066":{"position":[[0,7]]},"1127":{"position":[[0,7]]}}}],["prometheu",{"_index":702,"t":{"1558":{"position":[[0,10]]}}}],["protect",{"_index":81,"t":{"121":{"position":[[11,10]]}}}],["proven",{"_index":250,"t":{"505":{"position":[[0,10]]}}}],["provid",{"_index":63,"t":{"87":{"position":[[16,8]]},"229":{"position":[[13,9]]},"255":{"position":[[12,9]]},"257":{"position":[[14,9]]},"511":{"position":[[15,9]]},"712":{"position":[[0,9]]},"1351":{"position":[[51,9]]}}}],["proxi",{"_index":109,"t":{"175":{"position":[[15,5]]},"241":{"position":[[8,5]]},"1305":{"position":[[46,6]]},"1341":{"position":[[5,5]]},"1343":{"position":[[5,5]]},"1567":{"position":[[0,8]]}}}],["proxmox",{"_index":279,"t":{"562":{"position":[[0,7]]},"1494":{"position":[[0,7]]}}}],["public",{"_index":144,"t":{"304":{"position":[[11,6]]},"1351":{"position":[[0,6]]},"1381":{"position":[[0,6]]},"1403":{"position":[[0,6]]}}}],["pull",{"_index":399,"t":{"810":{"position":[[20,7]]},"999":{"position":[[0,4]]},"1320":{"position":[[23,4]]}}}],["pwa",{"_index":121,"t":{"233":{"position":[[13,3]]}}}],["queri",{"_index":666,"t":{"1460":{"position":[[8,7]]},"1462":{"position":[[8,7]]}}}],["quick",{"_index":70,"t":{"113":{"position":[[0,5]]},"366":{"position":[[0,5]]},"481":{"position":[[0,5]]}}}],["quot",{"_index":648,"t":{"1419":{"position":[[13,6]]}}}],["quota",{"_index":384,"t":{"792":{"position":[[13,6]]}}}],["railway",{"_index":263,"t":{"528":{"position":[[0,7]]}}}],["rais",{"_index":210,"t":{"449":{"position":[[0,5]]},"1591":{"position":[[0,7]]}}}],["ram",{"_index":557,"t":{"1270":{"position":[[12,3]]}}}],["raspberri",{"_index":470,"t":{"1049":{"position":[[0,9]]},"1110":{"position":[[0,9]]}}}],["rate",{"_index":634,"t":{"1401":{"position":[[9,5]]}}}],["ratty222",{"_index":464,"t":{"1037":{"position":[[0,8]]},"1098":{"position":[[0,8]]}}}],["read",{"_index":275,"t":{"556":{"position":[[0,4]]},"806":{"position":[[4,4]]},"1211":{"position":[[21,4]]},"1213":{"position":[[30,4]]},"1233":{"position":[[14,4]]}}}],["realm",{"_index":110,"t":{"190":{"position":[[20,5]]},"196":{"position":[[8,5]]}}}],["recent",{"_index":667,"t":{"1464":{"position":[[8,6]]},"1466":{"position":[[8,6]]}}}],["redirect",{"_index":326,"t":{"652":{"position":[[3,8]]},"1285":{"position":[[9,8]]},"1289":{"position":[[0,8]]}}}],["refer",{"_index":616,"t":{"1357":{"position":[[9,9]]}}}],["refus",{"_index":539,"t":{"1235":{"position":[[0,7]]},"1293":{"position":[[45,7]]}}}],["regist",{"_index":336,"t":{"669":{"position":[[9,8]]}}}],["registri",{"_index":402,"t":{"816":{"position":[[0,8]]}}}],["releas",{"_index":246,"t":{"499":{"position":[[10,7]]},"741":{"position":[[12,7]]},"786":{"position":[[10,8]]},"972":{"position":[[15,8]]},"975":{"position":[[11,7]]},"986":{"position":[[0,7]]}}}],["remot",{"_index":374,"t":{"772":{"position":[[0,6]]},"871":{"position":[[6,6]]},"1263":{"position":[[0,6]]}}}],["render",{"_index":262,"t":{"526":{"position":[[0,6]]}}}],["renew",{"_index":50,"t":{"66":{"position":[[16,7]]},"101":{"position":[[16,7]]},"237":{"position":[[13,7]]},"280":{"position":[[16,7]]},"341":{"position":[[16,7]]}}}],["report",{"_index":204,"t":{"441":{"position":[[21,7]]},"915":{"position":[[16,9]]},"948":{"position":[[0,9]]},"1360":{"position":[[18,6]]}}}],["request",{"_index":213,"t":{"453":{"position":[[0,7]]},"899":{"position":[[9,8]]},"999":{"position":[[5,8]]},"1567":{"position":[[9,8]]},"1587":{"position":[[0,10]]}}}],["requir",{"_index":234,"t":{"485":{"position":[[0,12]]}}}],["rescuetim",{"_index":661,"t":{"1443":{"position":[[0,10]]}}}],["reset",{"_index":617,"t":{"1358":{"position":[[7,5]]}}}],["resourc",{"_index":237,"t":{"488":{"position":[[7,9]]},"614":{"position":[[0,9]]},"701":{"position":[[21,9]]},"792":{"position":[[4,8]]},"1512":{"position":[[7,8]]},"1543":{"position":[[0,8]]}}}],["respect",{"_index":337,"t":{"673":{"position":[[0,10]]}}}],["respons",{"_index":701,"t":{"1556":{"position":[[4,8]]}}}],["restart",{"_index":534,"t":{"1231":{"position":[[21,7]]}}}],["restor",{"_index":152,"t":{"359":{"position":[[0,9]]}}}],["restrict",{"_index":67,"t":{"95":{"position":[[0,8]]},"873":{"position":[[0,12]]}}}],["return",{"_index":521,"t":{"1219":{"position":[[26,7]]},"1223":{"position":[[21,7]]},"1261":{"position":[[17,6]]},"1297":{"position":[[71,8]]}}}],["revers",{"_index":122,"t":{"241":{"position":[[0,7]]},"776":{"position":[[0,7]]}}}],["review",{"_index":221,"t":{"457":{"position":[[24,6]]}}}],["right",{"_index":438,"t":{"942":{"position":[[22,5]]}}}],["rmm",{"_index":688,"t":{"1510":{"position":[[9,3]]}}}],["role",{"_index":93,"t":{"133":{"position":[[5,5]]},"196":{"position":[[14,4]]},"198":{"position":[[23,4]]},"208":{"position":[[14,5]]},"327":{"position":[[17,4]]},"329":{"position":[[10,4]]},"331":{"position":[[4,5]]}}}],["root",{"_index":386,"t":{"794":{"position":[[13,4]]}}}],["rout",{"_index":331,"t":{"654":{"position":[[33,6]]},"656":{"position":[[11,5]]},"1245":{"position":[[6,7]]}}}],["rss",{"_index":620,"t":{"1377":{"position":[[0,3]]}}}],["run",{"_index":103,"t":{"156":{"position":[[3,3]]},"300":{"position":[[0,3]]},"540":{"position":[[13,3]]},"550":{"position":[[7,3]]},"597":{"position":[[0,7]]},"648":{"position":[[3,3]]},"716":{"position":[[0,7]]},"794":{"position":[[6,3]]},"832":{"position":[[0,7]]},"1330":{"position":[[26,7]]}}}],["runtim",{"_index":261,"t":{"524":{"position":[[10,8]]}}}],["runtipi",{"_index":289,"t":{"580":{"position":[[0,7]]}}}],["sabnzbd",{"_index":680,"t":{"1496":{"position":[[0,7]]}}}],["saltbox",{"_index":294,"t":{"590":{"position":[[0,7]]}}}],["sass",{"_index":568,"t":{"1276":{"position":[[5,4]]}}}],["save",{"_index":191,"t":{"427":{"position":[[7,6]]},"1209":{"position":[[11,6]]},"1217":{"position":[[21,4]]},"1219":{"position":[[0,4]]},"1221":{"position":[[0,4]]},"1225":{"position":[[24,6]]},"1227":{"position":[[8,4]]},"1229":{"position":[[0,5]]},"1231":{"position":[[40,6]]},"1293":{"position":[[56,4]]}}}],["scale",{"_index":278,"t":{"560":{"position":[[8,5]]}}}],["scan",{"_index":401,"t":{"814":{"position":[[19,8]]}}}],["schedul",{"_index":367,"t":{"750":{"position":[[0,10]]}}}],["scope",{"_index":62,"t":{"85":{"position":[[18,5]]}}}],["score",{"_index":639,"t":{"1407":{"position":[[7,6]]}}}],["screen",{"_index":544,"t":{"1248":{"position":[[34,6]]}}}],["script",{"_index":253,"t":{"507":{"position":[[8,6]]}}}],["search",{"_index":428,"t":{"911":{"position":[[4,6]]},"1002":{"position":[[0,9]]},"1014":{"position":[[4,6]]},"1016":{"position":[[8,6]]},"1018":{"position":[[13,6]]},"1024":{"position":[[14,6]]},"1028":{"position":[[9,6]]},"1441":{"position":[[7,6]]}}}],["secret",{"_index":37,"t":{"54":{"position":[[9,7]]},"1569":{"position":[[9,7]]}}}],["section",{"_index":12,"t":{"23":{"position":[[11,8]]},"135":{"position":[[4,7]]},"408":{"position":[[0,7]]},"879":{"position":[[9,7]]},"881":{"position":[[15,7]]},"1189":{"position":[[12,7]]}}}],["section.displaydata",{"_index":181,"t":{"416":{"position":[[0,19]]}}}],["section.displaydata.hideforkeycloakus",{"_index":184,"t":{"420":{"position":[[0,41]]}}}],["section.displaydata.showforkeycloakus",{"_index":185,"t":{"420":{"position":[[42,41]]}}}],["section.icon",{"_index":182,"t":{"418":{"position":[[0,12]]}}}],["section.item",{"_index":178,"t":{"410":{"position":[[0,12]]}}}],["section.item.icon",{"_index":183,"t":{"418":{"position":[[17,17]]}}}],["section.widget",{"_index":180,"t":{"414":{"position":[[0,15]]}}}],["secur",{"_index":97,"t":{"141":{"position":[[0,8]]},"179":{"position":[[0,8]]},"361":{"position":[[10,8]]},"788":{"position":[[10,8]]},"814":{"position":[[10,8]]},"816":{"position":[[9,8]]},"818":{"position":[[0,8]]},"921":{"position":[[0,8]]},"923":{"position":[[0,8]]},"948":{"position":[[12,8]]},"1162":{"position":[[10,8]]}}}],["self",{"_index":155,"t":{"365":{"position":[[0,4]]},"756":{"position":[[10,4]]},"758":{"position":[[10,4]]},"1051":{"position":[[14,4]]},"1112":{"position":[[14,4]]},"1447":{"position":[[0,4]]}}}],["selfh.st",{"_index":344,"t":{"687":{"position":[[0,8]]}}}],["selinux",{"_index":515,"t":{"1215":{"position":[[0,7]]}}}],["server",{"_index":57,"t":{"74":{"position":[[0,6]]},"125":{"position":[[0,6]]},"146":{"position":[[0,6]]},"167":{"position":[[0,6]]},"216":{"position":[[0,6]]},"235":{"position":[[4,6]]},"251":{"position":[[4,6]]},"288":{"position":[[0,6]]},"313":{"position":[[0,6]]},"349":{"position":[[0,6]]},"365":{"position":[[24,6]]},"648":{"position":[[19,6]]},"820":{"position":[[4,6]]},"1445":{"position":[[10,6]]}}}],["servic",{"_index":123,"t":{"245":{"position":[[0,7]]},"501":{"position":[[8,7]]},"968":{"position":[[40,8]]},"1057":{"position":[[11,8]]},"1118":{"position":[[11,8]]},"1447":{"position":[[12,8]]}}}],["session",{"_index":90,"t":{"129":{"position":[[0,9]]},"893":{"position":[[0,7]]}}}],["set",{"_index":295,"t":{"594":{"position":[[0,7]]},"768":{"position":[[0,7]]},"792":{"position":[[0,3]]},"808":{"position":[[0,3]]},"838":{"position":[[0,7]]},"1016":{"position":[[0,7]]},"1020":{"position":[[0,7]]},"1160":{"position":[[0,7]]},"1183":{"position":[[0,7]]},"1358":{"position":[[19,8]]},"1571":{"position":[[0,7]]}}}],["setup",{"_index":140,"t":{"266":{"position":[[17,5]]}}}],["share",{"_index":197,"t":{"437":{"position":[[0,5]]}}}],["show",{"_index":531,"t":{"1229":{"position":[[30,5]]},"1255":{"position":[[9,5]]},"1345":{"position":[[7,5]]}}}],["side",{"_index":56,"t":{"72":{"position":[[7,4]]},"74":{"position":[[7,4]]},"123":{"position":[[7,4]]},"144":{"position":[[23,4]]},"167":{"position":[[7,4]]},"214":{"position":[[7,4]]},"216":{"position":[[7,4]]},"235":{"position":[[11,4]]},"286":{"position":[[7,4]]},"288":{"position":[[7,4]]},"313":{"position":[[7,4]]},"347":{"position":[[7,4]]},"349":{"position":[[7,4]]}}}],["sigkil",{"_index":567,"t":{"1274":{"position":[[27,9]]}}}],["sign",{"_index":370,"t":{"756":{"position":[[15,6]]},"758":{"position":[[15,6]]},"1299":{"position":[[0,4]]}}}],["signal",{"_index":566,"t":{"1274":{"position":[[20,6]]}}}],["signincallback",{"_index":579,"t":{"1297":{"position":[[56,14]]}}}],["silent",{"_index":49,"t":{"66":{"position":[[3,6]]},"101":{"position":[[3,6]]},"237":{"position":[[0,6]]},"280":{"position":[[3,6]]},"341":{"position":[[3,6]]}}}],["simpl",{"_index":342,"t":{"681":{"position":[[0,6]]}}}],["small",{"_index":200,"t":{"439":{"position":[[7,5]]}}}],["socket",{"_index":396,"t":{"804":{"position":[[31,6]]}}}],["sourc",{"_index":244,"t":{"497":{"position":[[11,6]]},"739":{"position":[[20,6]]},"964":{"position":[[39,6]]}}}],["space",{"_index":692,"t":{"1529":{"position":[[5,5]]}}}],["specif",{"_index":366,"t":{"748":{"position":[[6,8]]},"1185":{"position":[[5,8]]}}}],["specifi",{"_index":387,"t":{"796":{"position":[[0,7]]},"812":{"position":[[0,7]]}}}],["speedomet",{"_index":690,"t":{"1517":{"position":[[18,11]]},"1525":{"position":[[21,11]]}}}],["sponsor",{"_index":227,"t":{"466":{"position":[[0,8]]}}}],["sport",{"_index":638,"t":{"1407":{"position":[[0,6]]}}}],["spread",{"_index":216,"t":{"455":{"position":[[0,6]]}}}],["ssh",{"_index":376,"t":{"776":{"position":[[8,3]]}}}],["ssl",{"_index":368,"t":{"752":{"position":[[0,3]]},"754":{"position":[[5,3]]},"756":{"position":[[22,3]]},"926":{"position":[[0,3]]}}}],["sso",{"_index":132,"t":{"255":{"position":[[0,3]]}}}],["star",{"_index":218,"t":{"457":{"position":[[0,5]]},"463":{"position":[[0,4]]}}}],["stargaz",{"_index":229,"t":{"468":{"position":[[7,10]]}}}],["start",{"_index":71,"t":{"113":{"position":[[6,5]]},"366":{"position":[[6,5]]},"481":{"position":[[6,5]]},"731":{"position":[[5,8]]},"1314":{"position":[[8,8]]}}}],["startpag",{"_index":476,"t":{"1063":{"position":[[8,9]]},"1124":{"position":[[8,9]]}}}],["startup",{"_index":559,"t":{"1270":{"position":[[25,7]]},"1309":{"position":[[33,7]]}}}],["stat",{"_index":629,"t":{"1393":{"position":[[5,5]]},"1429":{"position":[[15,5]]},"1456":{"position":[[8,5]]},"1458":{"position":[[8,5]]},"1468":{"position":[[0,4]]},"1472":{"position":[[19,5]]},"1488":{"position":[[10,5]]},"1490":{"position":[[22,5]]}}}],["static",{"_index":87,"t":{"127":{"position":[[0,6]]},"511":{"position":[[0,6]]},"522":{"position":[[6,6]]},"1221":{"position":[[45,6]]},"1246":{"position":[[7,6]]}}}],["station",{"_index":671,"t":{"1470":{"position":[[18,7]]}}}],["statu",{"_index":427,"t":{"905":{"position":[[0,6]]},"1154":{"position":[[9,6]]},"1164":{"position":[[21,6]]},"1166":{"position":[[24,6]]},"1334":{"position":[[0,6]]},"1395":{"position":[[8,6]]},"1405":{"position":[[9,6]]},"1411":{"position":[[4,6]]},"1431":{"position":[[13,6]]},"1508":{"position":[[12,6]]}}}],["status",{"_index":670,"t":{"1468":{"position":[[10,8]]},"1482":{"position":[[15,8]]}}}],["stefantigro",{"_index":485,"t":{"1077":{"position":[[0,11]]},"1138":{"position":[[0,11]]}}}],["step",{"_index":117,"t":{"223":{"position":[[0,4],[8,4]]},"663":{"position":[[0,4]]},"665":{"position":[[0,4]]},"667":{"position":[[0,4]]},"669":{"position":[[0,4]]},"671":{"position":[[0,4]]},"1217":{"position":[[7,4]]}}}],["stock",{"_index":642,"t":{"1413":{"position":[[0,5]]}}}],["storag",{"_index":421,"t":{"889":{"position":[[8,7]]},"893":{"position":[[8,7]]},"895":{"position":[[6,7]]}}}],["store",{"_index":424,"t":{"897":{"position":[[9,6]]}}}],["strategi",{"_index":303,"t":{"605":{"position":[[4,8]]},"994":{"position":[[4,8]]}}}],["stream",{"_index":679,"t":{"1492":{"position":[[5,6]]}}}],["structur",{"_index":312,"t":{"619":{"position":[[12,9]]},"658":{"position":[[30,10]]},"875":{"position":[[4,9]]}}}],["stuck",{"_index":581,"t":{"1299":{"position":[[20,5]]}}}],["style",{"_index":311,"t":{"617":{"position":[[0,5]]},"632":{"position":[[14,6]]},"1185":{"position":[[14,6]]},"1326":{"position":[[0,6]]},"1577":{"position":[[7,7]]}}}],["stylesheet",{"_index":501,"t":{"1187":{"position":[[17,11]]}}}],["sub",{"_index":416,"t":{"869":{"position":[[12,3]]},"871":{"position":[[13,3]]},"883":{"position":[[0,3]]},"1225":{"position":[[33,3]]},"1252":{"position":[[18,3]]},"1254":{"position":[[0,3]]},"1255":{"position":[[0,3]]},"1257":{"position":[[0,3]]},"1259":{"position":[[0,3]]},"1261":{"position":[[0,3]]}}}],["submit",{"_index":206,"t":{"445":{"position":[[0,6]]},"1084":{"position":[[0,10]]},"1085":{"position":[[7,6]]},"1145":{"position":[[0,10]]},"1146":{"position":[[7,6]]}}}],["subresourc",{"_index":431,"t":{"924":{"position":[[0,11]]}}}],["successfulli",{"_index":530,"t":{"1229":{"position":[[6,12]]}}}],["summari",{"_index":42,"t":{"60":{"position":[[0,7]]},"204":{"position":[[0,7]]},"274":{"position":[[0,7]]},"335":{"position":[[0,7]]}}}],["support",{"_index":242,"t":{"494":{"position":[[8,7]]},"867":{"position":[[11,7]]},"1276":{"position":[[23,7]]}}}],["survey",{"_index":196,"t":{"435":{"position":[[16,6]]}}}],["switch",{"_index":497,"t":{"1174":{"position":[[10,9]]}}}],["synolog",{"_index":282,"t":{"566":{"position":[[0,8]]},"1470":{"position":[[0,8]]}}}],["system",{"_index":236,"t":{"488":{"position":[[0,6]]},"731":{"position":[[17,6]]},"1061":{"position":[[0,6]]},"1122":{"position":[[0,6]]},"1448":{"position":[[0,6]]},"1486":{"position":[[10,6]]},"1512":{"position":[[0,6]]},"1533":{"position":[[0,6]]},"1537":{"position":[[0,6]]}}}],["systemd",{"_index":247,"t":{"501":{"position":[[0,7]]}}}],["tab",{"_index":505,"t":{"1193":{"position":[[31,3]]}}}],["tactic",{"_index":687,"t":{"1510":{"position":[[0,8]]}}}],["tag",{"_index":400,"t":{"812":{"position":[[12,3]]},"973":{"position":[[11,3]]},"988":{"position":[[0,3]]},"1008":{"position":[[0,4]]},"1320":{"position":[[10,4]]}}}],["tailscal",{"_index":142,"t":{"298":{"position":[[8,9]]},"300":{"position":[[4,9]]},"310":{"position":[[23,9]]}}}],["take",{"_index":194,"t":{"435":{"position":[[0,4]]}}}],["taller",{"_index":419,"t":{"879":{"position":[[26,6]]}}}],["target",{"_index":506,"t":{"1199":{"position":[[0,8]]},"1343":{"position":[[11,6]]}}}],["tcp",{"_index":377,"t":{"778":{"position":[[0,3]]}}}],["temp",{"_index":696,"t":{"1547":{"position":[[4,4]]}}}],["templat",{"_index":489,"t":{"1089":{"position":[[0,8]]},"1150":{"position":[[0,8]]}}}],["tenant",{"_index":120,"t":{"229":{"position":[[6,6]]}}}],["test",{"_index":66,"t":{"93":{"position":[[7,4]]},"202":{"position":[[7,4]]},"272":{"position":[[7,4]]},"333":{"position":[[7,4]]}}}],["text",{"_index":412,"t":{"856":{"position":[[11,4]]},"858":{"position":[[18,4]]},"860":{"position":[[7,4]]}}}],["tfl",{"_index":641,"t":{"1411":{"position":[[0,3]]}}}],["theme",{"_index":319,"t":{"628":{"position":[[15,5]]},"630":{"position":[[7,5]]},"903":{"position":[[0,6]]},"1174":{"position":[[4,5]]},"1176":{"position":[[16,5]]},"1181":{"position":[[10,5]]},"1259":{"position":[[21,6]]}}}],["threat",{"_index":434,"t":{"934":{"position":[[0,6]]}}}],["three",{"_index":161,"t":{"378":{"position":[[10,5]]}}}],["through",{"_index":188,"t":{"423":{"position":[[15,7]]},"1330":{"position":[[34,7]]}}}],["time",{"_index":139,"t":{"266":{"position":[[6,4]]},"463":{"position":[[17,4]]}}}],["timeout",{"_index":704,"t":{"1571":{"position":[[8,7]]}}}],["tip",{"_index":164,"t":{"380":{"position":[[0,4]]}}}],["togeth",{"_index":101,"t":{"152":{"position":[[15,8]]},"296":{"position":[[12,8]]},"300":{"position":[[22,8]]}}}],["token",{"_index":9,"t":{"19":{"position":[[4,5]]},"66":{"position":[[10,5]]},"101":{"position":[[10,5]]},"237":{"position":[[7,5]]},"280":{"position":[[10,5]]},"341":{"position":[[10,5]]},"1389":{"position":[[7,5]]}}}],["tool",{"_index":314,"t":{"621":{"position":[[12,5]]}}}],["toolbox",{"_index":478,"t":{"1064":{"position":[[4,7]]},"1125":{"position":[[4,7]]}}}],["toomanyrequest",{"_index":598,"t":{"1318":{"position":[[10,15]]}}}],["top",{"_index":165,"t":{"384":{"position":[[0,3]]},"1197":{"position":[[0,3]]},"1478":{"position":[[13,3]]}}}],["traffic",{"_index":668,"t":{"1464":{"position":[[15,7]]},"1466":{"position":[[15,7]]},"1541":{"position":[[8,7]]}}}],["translat",{"_index":205,"t":{"443":{"position":[[4,12]]},"634":{"position":[[8,12]]},"638":{"position":[[3,9]]},"850":{"position":[[3,9]]},"854":{"position":[[9,11]]},"858":{"position":[[7,10]]},"1581":{"position":[[9,12]]}}}],["trend",{"_index":656,"t":{"1427":{"position":[[7,8]]},"1433":{"position":[[11,8]]}}}],["troubleshoot",{"_index":51,"t":{"68":{"position":[[0,15]]},"103":{"position":[[0,15]]},"139":{"position":[[0,15]]},"164":{"position":[[0,15]]},"177":{"position":[[0,15]]},"210":{"position":[[0,15]]},"282":{"position":[[0,15]]},"310":{"position":[[0,15]]},"343":{"position":[[0,15]]},"1166":{"position":[[0,15]]},"1589":{"position":[[0,15]]}}}],["truena",{"_index":277,"t":{"560":{"position":[[0,7]]}}}],["truncat",{"_index":584,"t":{"1303":{"position":[[26,9]]}}}],["trust",{"_index":25,"t":{"41":{"position":[[5,5]]},"243":{"position":[[5,5]]},"938":{"position":[[0,5]]},"1305":{"position":[[38,7]]}}}],["tunnel",{"_index":26,"t":{"41":{"position":[[11,7]]},"154":{"position":[[14,6]]},"164":{"position":[[34,6]]},"243":{"position":[[11,7]]},"776":{"position":[[12,6]]},"778":{"position":[[4,6]]}}}],["type",{"_index":595,"t":{"1316":{"position":[[6,4]]}}}],["typographi",{"_index":503,"t":{"1191":{"position":[[0,10]]}}}],["ubuntu",{"_index":599,"t":{"1324":{"position":[[22,6]]}}}],["ui",{"_index":189,"t":{"423":{"position":[[27,2]]},"840":{"position":[[7,2]]},"1183":{"position":[[26,2]]},"1229":{"position":[[27,2]]},"1583":{"position":[[7,2]]}}}],["umbrel",{"_index":292,"t":{"586":{"position":[[0,6]]}}}],["unabl",{"_index":548,"t":{"1255":{"position":[[15,7]]}}}],["unauthor",{"_index":585,"t":{"1305":{"position":[[13,13]]},"1307":{"position":[[13,13]]}}}],["unavail",{"_index":524,"t":{"1221":{"position":[[5,11]]}}}],["unraid",{"_index":281,"t":{"564":{"position":[[0,6]]}}}],["unreach",{"_index":570,"t":{"1278":{"position":[[0,11]]}}}],["untrust",{"_index":582,"t":{"1301":{"position":[[0,9]]}}}],["up",{"_index":296,"t":{"594":{"position":[[8,2]]},"743":{"position":[[8,2]]},"744":{"position":[[8,2]]},"746":{"position":[[8,2]]},"790":{"position":[[12,2]]}}}],["updat",{"_index":14,"t":{"25":{"position":[[0,8]]},"644":{"position":[[0,8]]},"733":{"position":[[0,8]]},"735":{"position":[[0,8]]},"737":{"position":[[17,7]]},"739":{"position":[[0,8]]},"907":{"position":[[0,6]]},"946":{"position":[[0,6]]},"1314":{"position":[[23,6]]},"1326":{"position":[[22,8]]},"1565":{"position":[[11,7]]}}}],["uptim",{"_index":685,"t":{"1506":{"position":[[0,6]]},"1508":{"position":[[0,6]]},"1535":{"position":[[0,6]]}}}],["upvot",{"_index":219,"t":{"457":{"position":[[6,6]]}}}],["url",{"_index":349,"t":{"693":{"position":[[9,3]]},"875":{"position":[[0,3]]},"1026":{"position":[[8,4]]},"1227":{"position":[[28,4]]},"1343":{"position":[[18,3]]}}}],["us",{"_index":75,"t":{"119":{"position":[[0,5]]},"200":{"position":[[14,3]]},"233":{"position":[[0,5]]},"245":{"position":[[25,3]]},"306":{"position":[[0,5]]},"363":{"position":[[5,3]]},"660":{"position":[[9,5]]},"782":{"position":[[0,5]]},"784":{"position":[[0,5]]},"806":{"position":[[0,3]]},"860":{"position":[[3,3]]},"869":{"position":[[0,5]]},"871":{"position":[[0,5]]},"1018":{"position":[[0,5]]},"1022":{"position":[[0,5]]},"1158":{"position":[[0,5]]},"1343":{"position":[[51,3]]}}}],["usag",{"_index":558,"t":{"1270":{"position":[[16,5]]},"1515":{"position":[[12,5]]},"1517":{"position":[[12,5]]},"1519":{"position":[[4,5]]},"1521":{"position":[[4,5]]},"1523":{"position":[[15,5]]},"1525":{"position":[[15,5]]},"1527":{"position":[[7,5]]},"1543":{"position":[[9,5]]},"1562":{"position":[[0,5]]},"1563":{"position":[[7,5]]}}}],["user",{"_index":39,"t":{"56":{"position":[[0,5]]},"93":{"position":[[12,5]]},"115":{"position":[[9,5]]},"125":{"position":[[29,5]]},"133":{"position":[[0,4]]},"202":{"position":[[12,5]]},"272":{"position":[[12,5]]},"329":{"position":[[20,4]]},"333":{"position":[[12,5]]},"796":{"position":[[10,4]]},"956":{"position":[[3,4]]},"1297":{"position":[[83,5]]},"1307":{"position":[[37,4]]},"1309":{"position":[[9,4]]},"1480":{"position":[[10,4]]},"1482":{"position":[[10,4]]}}}],["util",{"_index":232,"t":{"473":{"position":[[0,9]]}}}],["v6",{"_index":665,"t":{"1458":{"position":[[14,2]]},"1462":{"position":[[16,2]]},"1466":{"position":[[23,2]]}}}],["valid",{"_index":572,"t":{"1283":{"position":[[5,10]]},"1328":{"position":[[7,10]]}}}],["var",{"_index":77,"t":{"119":{"position":[[13,3]]}}}],["variabl",{"_index":301,"t":{"601":{"position":[[14,9]]},"660":{"position":[[29,9]]},"766":{"position":[[25,9]]},"1195":{"position":[[4,9]]},"1197":{"position":[[10,9]]},"1199":{"position":[[15,9]]},"1201":{"position":[[10,9]]}}}],["ve",{"_index":280,"t":{"562":{"position":[[8,2]]}}}],["vercel",{"_index":255,"t":{"514":{"position":[[0,6]]},"1221":{"position":[[20,7]]}}}],["verif",{"_index":249,"t":{"503":{"position":[[9,12]]}}}],["verifi",{"_index":362,"t":{"741":{"position":[[0,9]]},"786":{"position":[[0,9]]},"810":{"position":[[0,6]]}}}],["version",{"_index":408,"t":{"832":{"position":[[19,7]]},"972":{"position":[[0,10]]},"977":{"position":[[0,10]]}}}],["via",{"_index":85,"t":{"125":{"position":[[16,3]]},"453":{"position":[[18,3]]}}}],["view",{"_index":0,"t":{"3":{"position":[[0,5]]},"9":{"position":[[8,4]]},"1235":{"position":[[41,4]]}}}],["visibl",{"_index":47,"t":{"64":{"position":[[14,10]]},"99":{"position":[[14,10]]},"133":{"position":[[13,10]]},"135":{"position":[[25,10]]},"278":{"position":[[14,10]]},"339":{"position":[[14,10]]}}}],["visual",{"_index":58,"t":{"76":{"position":[[0,6]]},"220":{"position":[[0,6]]},"290":{"position":[[0,6]]},"351":{"position":[[0,6]]}}}],["volum",{"_index":365,"t":{"746":{"position":[[11,7]]},"806":{"position":[[14,7]]}}}],["vp",{"_index":271,"t":{"546":{"position":[[6,3]]},"1047":{"position":[[10,3]]},"1108":{"position":[[10,3]]}}}],["vpn",{"_index":126,"t":{"247":{"position":[[0,3]]},"1498":{"position":[[8,3]]}}}],["vulner",{"_index":632,"t":{"1399":{"position":[[0,13]]}}}],["wallet",{"_index":627,"t":{"1391":{"position":[[0,6]]}}}],["warn",{"_index":586,"t":{"1309":{"position":[[22,7]]},"1332":{"position":[[0,8]]}}}],["watch",{"_index":623,"t":{"1387":{"position":[[7,5]]}}}],["way",{"_index":162,"t":{"378":{"position":[[16,4]]}}}],["weather",{"_index":607,"t":{"1347":{"position":[[0,7]]},"1373":{"position":[[0,7]]},"1375":{"position":[[0,7]]}}}],["web",{"_index":129,"t":{"251":{"position":[[0,3]]},"522":{"position":[[13,3]]},"820":{"position":[[0,3]]},"911":{"position":[[0,3]]},"1014":{"position":[[0,3]]},"1024":{"position":[[10,3]]}}}],["week",{"_index":472,"t":{"1051":{"position":[[6,4]]},"1112":{"position":[[6,4]]}}}],["wider",{"_index":418,"t":{"879":{"position":[[17,5]]}}}],["widget",{"_index":334,"t":{"662":{"position":[[11,6]]},"665":{"position":[[16,6]]},"917":{"position":[[0,7]]},"1336":{"position":[[0,7]]},"1337":{"position":[[0,6]]},"1339":{"position":[[0,6]]},"1345":{"position":[[0,6]]},"1347":{"position":[[17,6]]},"1349":{"position":[[0,6]]},"1351":{"position":[[10,6]]},"1370":{"position":[[8,7]]},"1447":{"position":[[21,7]]},"1551":{"position":[[8,7]]},"1552":{"position":[[7,6]]},"1554":{"position":[[14,6]]},"1563":{"position":[[0,6]]},"1577":{"position":[[0,6]]},"1583":{"position":[[0,6]]},"1585":{"position":[[15,6]]},"1587":{"position":[[13,6]]},"1589":{"position":[[16,6]]}}}],["wireguard",{"_index":375,"t":{"774":{"position":[[0,9]]}}}],["within",{"_index":413,"t":{"860":{"position":[[12,6]]}}}],["won't",{"_index":551,"t":{"1257":{"position":[[30,5]]}}}],["word",{"_index":217,"t":{"455":{"position":[[11,4]]}}}],["work",{"_index":54,"t":{"70":{"position":[[7,5]]},"107":{"position":[[7,5]]},"143":{"position":[[7,5]]},"166":{"position":[[7,5]]},"181":{"position":[[7,5]]},"212":{"position":[[7,5]]},"235":{"position":[[28,5]]},"284":{"position":[[7,5]]},"312":{"position":[[7,5]]},"345":{"position":[[7,5]]},"355":{"position":[[7,5]]},"1170":{"position":[[7,5]]},"1174":{"position":[[20,5]]},"1293":{"position":[[6,5]]},"1297":{"position":[[6,5]]},"1351":{"position":[[21,7]]},"1355":{"position":[[22,7]]}}}],["worker",{"_index":124,"t":{"245":{"position":[[8,6]]}}}],["workflow",{"_index":449,"t":{"981":{"position":[[0,9]]}}}],["workspac",{"_index":2,"t":{"7":{"position":[[0,9]]},"1235":{"position":[[31,9]]}}}],["wrangler.toml",{"_index":157,"t":{"368":{"position":[[9,13]]}}}],["write",{"_index":320,"t":{"632":{"position":[[3,5]]},"634":{"position":[[0,7]]},"1215":{"position":[[31,5]]}}}],["xkcd",{"_index":649,"t":{"1421":{"position":[[0,4]]}}}],["yaml",{"_index":190,"t":{"425":{"position":[[6,4]]}}}],["yarn",{"_index":555,"t":{"1266":{"position":[[0,4]]},"1268":{"position":[[0,4]]}}}],["zero",{"_index":24,"t":{"41":{"position":[[0,4]]},"243":{"position":[[0,4]]}}}],["zitadel",{"_index":146,"t":{"319":{"position":[[10,7]]},"321":{"position":[[13,7]]},"337":{"position":[[12,7]]},"343":{"position":[[23,7]]}}}]],"pipeline":["stemmer"]}},{"documents":[{"i":1,"t":"Documentation | Dashy, a self-hosted dashboard for your homelab","s":"","u":"/docs/","p":1},{"i":2,"t":"Views","s":"Alternate Views & Opening Methods","u":"/docs/alternate-views","p":2},{"i":13,"t":"Dashy includes an optional REST API, for reading and writing your config programmatically — from the command line, scripts or third-party applications. It covers whole config files, as well as individual sections and items.","s":"REST API","u":"/docs/api","p":13},{"i":31,"t":"- Built-In Auth","s":"Authentication","u":"/docs/authentication","p":31},{"i":47,"t":"Dashy supports using Authelia as its OIDC provider.","s":"Authelia OIDC","u":"/docs/authentication/authelia-oidc","p":47},{"i":78,"t":"Dashy supports using Authentik as its OIDC provider.","s":"Authentik OIDC","u":"/docs/authentication/authentik","p":78},{"i":109,"t":"Dashy ships with a simple username + password auth system baked in, as well as the option to enforce this server-side with standard HTTP auth.","s":"Built-In Auth","u":"/docs/authentication/built-in","p":109},{"i":148,"t":"Dashy works well behind Cloudflare Tunnel with Cloudflare Access doing auth at the edge. This is one of the most common ways to expose a homelab dashboard to the public internet without opening any inbound ports, without managing TLS, and without standing up your own identity provider.","s":"Cloudflare Tunnel","u":"/docs/authentication/cloudflare-tunnel","p":148},{"i":169,"t":"Header authentication lets Dashy trust a reverse proxy in front of it to handle login. The proxy authenticates the user, then passes their username to Dashy in an HTTP header. Dashy reads that header and signs them in automatically, so there's no separate Dashy login page.","s":"Header Authentication","u":"/docs/authentication/header-auth","p":169},{"i":183,"t":"Dashy supports using a Keycloak (V17+) authentication server.","s":"Keycloak","u":"/docs/authentication/keycloak","p":183},{"i":222,"t":"Step-by-step Guides","s":"OIDC","u":"/docs/authentication/oidc","p":222},{"i":239,"t":"If OIDC or our built-in auth doesn't suit your needs, there's plenty of other ways of protecting your dashboard from unauthenticated access.","s":"Other Auth Methods","u":"/docs/authentication/other-auth-methods","p":239},{"i":259,"t":"Dashy supports using Pocket ID as its OIDC provider.","s":"Pocket ID OIDC","u":"/docs/authentication/pocketid","p":259},{"i":292,"t":"Tailscale is a popular way to put Dashy on a private network you can reach from your phone, laptop, or any other device you've added to your tailnet, without exposing it to the public internet. With Tailscale Serve in front, requests reaching Dashy already carry the user's identity in HTTP headers, which plugs straight into Dashy's header auth for auto-login.","s":"Tailscale","u":"/docs/authentication/tailscale","p":292},{"i":315,"t":"Dashy supports using Zitadel as its OIDC provider.","s":"Zitadel OIDC","u":"/docs/authentication/zitadel","p":315},{"i":353,"t":"Beyond the cloud backup/restore service, there are several other self-hosted options you can use to backup Dashy, and any other Docker container data. These are outlined in the Management docs, at: Docker Backup Options.","s":"Cloud Backup and Restore","u":"/docs/backup-restore","p":353},{"i":376,"t":"All app configuration is specified in /user-data/conf.yml which is in YAML Format format. If you're using Docker, this file can be passed in as a volume. Changes can either be made directly to this file, or done through the UI. From the UI you can also export, backup, reset, validate and download your configuration file.","s":"Configuring","u":"/docs/configuring","p":376},{"i":433,"t":"First off, thank you for considering contributing towards Dashy! 🙌","s":"Contributing","u":"/docs/contributing","p":433},{"i":465,"t":"Sponsors","s":"Credits","u":"/docs/credits","p":465},{"i":479,"t":"Welcome to Dashy, so glad you're here :) Deployment is super easy, and there are several methods available depending on what type of system you're using. If you're self-hosting, then deploying with Docker (or similar container engine) is the recommended approach.","s":"Deployment","u":"/docs/deployment","p":479},{"i":496,"t":"Build from Source","s":"Bare Metal","u":"/docs/deployment/bare-metal","p":496},{"i":509,"t":"Dashy is intended to be run locally, within your LAN/homelab. But it can also be deployed to the cloud for certain use cases.","s":"Cloud Services","u":"/docs/deployment/cloud","p":509},{"i":548,"t":"Docker is the recommended way of running Dashy.","s":"Docker","u":"/docs/deployment/docker","p":548},{"i":558,"t":"The following lists third-party/community-maintained apps and scripts for deploying Dashy on various device types.","s":"Self-Hosted Operating Systems","u":"/docs/deployment/self-hosted-os","p":558},{"i":572,"t":"Dashy has ready-made apps, templates, and one-click installs for a range of self-hosted control panels and home-server platforms. To deploy onto a specific operating system or NAS instead, see Self-Hosted Operating Systems.","s":"Self-Hosted Platforms","u":"/docs/deployment/self-hosted-platforms","p":572},{"i":592,"t":"This article outlines how to get Dashy running in a development environment, and outlines the basics of the architecture.","s":"Developing","u":"/docs/developing","p":592},{"i":626,"t":"A series of short tutorials, to guide you through the most common development tasks.","s":"Development Guides","u":"/docs/development-guides","p":626},{"i":675,"t":"Both sections and items can have an icon, which is specified using the icon attribute. Using icons improves the aesthetics of your UI and makes the app more intuitive to use. Dashy supports multiple different icon providers, usage instructions for which are explained here.","s":"Icons","u":"/docs/icons","p":675},{"i":705,"t":"MIT License","s":"License","u":"/docs/license","p":705},{"i":708,"t":"The following article is a primer on managing self-hosted apps. It covers everything from keeping the Dashy (or any other app) up-to-date, secure, backed up, to other topics like auto-starting, monitoring, log management, web server configuration and using custom domains.","s":"App Management","u":"/docs/management","p":708},{"i":836,"t":"Internationalization is the process of making an application available in other languages. This is important, as not everyone is a native English speaker. This page explains how you can switch languages, how to add a new language, and how to make text translatable when writing a new component.","s":"Internationalization","u":"/docs/multi-language-support","p":836},{"i":864,"t":"Page Metadata","s":"Pages and Sections","u":"/docs/pages-and-sections","p":864},{"i":885,"t":"Dashy was built with privacy in mind.","s":"Privacy & Security","u":"/docs/privacy","p":885},{"i":950,"t":"Welcome to Dashy! So glad you're here 😊 In a couple of minutes, you'll have your new dashboard up and running 🚀","s":"Quick Start","u":"/docs/quick-start","p":950},{"i":970,"t":"This document outlines our release schedule, workflows/automations, repository conventions and standards","s":"Releases and Workflows","u":"/docs/release-workflow","p":970},{"i":1001,"t":"Searching","s":"Search & Shortcuts","u":"/docs/searching","p":1001},{"i":1030,"t":"| 💗 Got a sweet dashboard? Submit it to the showcase! 👉 See How |","s":"Dashy Showcase 🌟","u":"/docs/showcase","p":1030},{"i":1091,"t":"| 💗 Got a sweet dashboard? Submit it to the showcase! 👉 See How |","s":"Dashy Showcase 🌟","u":"/docs/showcase/","p":1091},{"i":1152,"t":"Dashy has an optional feature that can display a small icon next to each of your running service or host, indicating it's current status (operation or online). This can be useful if you are using Dashy as your homelab's start page, as it gives you an overview of the health of each of your running services and hosts. The status and ping features will show response time, response code, online / offline check and if applicable, a relevant error message.","s":"Status Indicators","u":"/docs/status-indicators","p":1152},{"i":1172,"t":"By default Dashy comes with 40+ built-in themes, which can be applied from the dropdown menu in the UI.","s":"Theming","u":"/docs/theming","p":1172},{"i":1205,"t":"**This document contains common problems and their solutions.**","s":"Troubleshooting","u":"/docs/troubleshooting","p":1205},{"i":1366,"t":"Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets available out-of-the-box as well as support for custom widgets to display stats from almost any service with an API.","s":"Widgets","u":"/docs/widgets","p":1366}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/1",[0,3.122,1,1.879,2,0.603,3,2.56,4,2.352,5,2.352,6,3.541]],["t/2",[7,5.401]],["t/13",[1,1.144,2,0.367,8,2.543,9,1.711,10,2.543,11,2.156,12,2.156,13,2.156,14,3.783,15,2.543,16,2.543,17,2.543,18,2.156,19,2.156,20,2.543,21,1.901,22,2.156,23,2.543,24,2.156,25,1.711,26,2.543,27,2.156,28,2.156]],["t/31",[29,3.156,30,3.156]],["t/47",[2,0.626,31,2.259,32,1.598,33,4.341,34,2.66,35,2.444]],["t/78",[2,0.626,31,2.259,32,1.598,34,2.66,35,2.444,36,4.341]],["t/109",[1,1.364,2,0.437,9,2.039,25,2.039,30,2.634,37,3.031,38,3.031,39,2.57,40,3.031,41,2.266,42,3.031,43,3.031,44,2.039,45,3.031,46,2.57,47,2.266]],["t/148",[2,0.316,5,1.233,6,1.857,25,1.473,30,1.342,35,1.233,48,2.19,49,2.19,50,3.379,51,2.19,52,1.857,53,2.19,54,2.19,55,1.857,56,1.637,57,1.473,58,1.857,59,1.857,60,1.857,61,3.496,62,2.19,63,2.19,64,2.19,65,1.637,66,2.19,67,2.19,68,1.637,69,1.857]],["t/169",[2,0.69,12,1.974,39,1.974,47,1.741,70,3.632,71,3.002,72,2.329,73,2.329,74,2.329,75,3.541,76,1.974,77,2.329,78,3.002,79,1.974,80,1.974,81,2.329,82,2.329,83,1.974,84,2.329,85,1.566]],["t/183",[2,0.603,31,2.174,32,1.538,44,2.809,71,3.541,86,4.177,87,4.177]],["t/222",[88,5.905,89,4.172]],["t/239",[5,1.917,29,2.087,30,2.087,34,2.087,52,2.887,57,2.29,83,2.887,90,3.405,91,3.405,92,3.405,93,3.405,94,3.405,95,3.405]],["t/259",[2,0.603,31,2.174,32,1.538,34,2.56,35,2.352,96,4.177,97,4.177]],["t/292",[2,0.446,30,1.2,47,1.463,57,1.317,58,1.66,59,1.66,60,1.66,61,1.66,69,1.66,70,2.624,76,1.66,78,1.66,98,3.095,99,1.958,100,1.958,101,1.958,102,1.958,103,3.095,104,1.958,105,1.958,106,1.66,107,1.958,108,1.958,109,1.958,110,1.958,111,1.958,112,1.958,113,1.958,114,1.958,115,1.958,116,1.958,117,1.958,118,1.66]],["t/315",[2,0.626,31,2.259,32,1.598,34,2.66,35,2.444,119,4.341]],["t/353",[2,0.404,3,1.717,4,1.577,9,2.732,32,1.031,65,2.094,120,2.801,121,2.375,122,2.801,123,2.094,124,2.094,125,3.443,126,2.732,127,2.094,128,2.801,129,2.094,130,2.801]],["t/376",[24,3.586,32,0.84,79,1.934,80,1.934,125,1.934,126,1.534,131,1.398,132,2.955,133,1.934,134,2.281,135,2.281,136,3.485,137,1.705,138,2.281,139,2.281,140,1.934,141,2.281,142,2.281,143,1.934,144,2.605,145,2.281,146,2.281,147,2.281,148,2.281]],["t/433",[1,1.879,2,0.603,149,4.177,150,4.177,151,4.177,152,4.177,153,4.177]],["t/465",[154,5.401]],["t/479",[1,1.07,2,0.343,3,1.458,4,1.339,32,0.876,41,1.778,124,1.778,126,1.6,127,1.778,137,3.244,155,2.017,156,2.017,157,1.778,158,2.42,159,2.379,160,2.379,161,2.379,162,1.778,163,2.379,164,2.017,165,2.379,166,2.379,167,2.017,168,2.379]],["t/496",[169,5.15,170,5.15]],["t/509",[2,0.523,32,1.336,121,3.076,158,2.441,171,3.628,172,2.224,173,3.628,174,3.628,175,3.628,176,3.628,177,3.628]],["t/548",[2,0.652,57,3.039,126,3.039,167,3.831,172,2.769]],["t/558",[2,0.507,18,2.978,19,2.978,106,2.978,131,2.153,158,2.363,164,2.978,178,2.978,179,3.513,180,3.513,181,3.513,182,3.513]],["t/572",[2,0.336,3,2.17,4,1.994,41,2.647,44,1.566,55,1.974,131,1.427,140,1.974,158,1.566,183,2.329,184,2.329,185,2.329,186,2.329,187,2.329,188,2.329,189,2.329,190,2.329,191,2.329,192,2.329,193,2.329,194,3.002,195,2.329,196,2.329,197,1.741]],["t/592",[2,0.56,129,3.805,172,2.38,198,3.292,199,3.292,200,3.883,201,3.883,202,3.883]],["t/626",[56,3.009,89,3.412,143,3.412,199,3.412,203,4.025,204,4.025,205,4.025,206,4.025]],["t/675",[2,0.336,27,1.974,28,1.974,31,1.212,32,1.577,35,1.311,131,1.427,133,1.974,144,1.741,157,1.741,207,2.329,208,4.058,209,2.329,210,2.329,211,2.329,212,1.974,213,2.329,214,2.329,215,2.329,216,2.329,217,2.329,218,2.329,219,1.974]],["t/705",[220,5.15,221,5.15]],["t/708",[2,0.329,3,1.398,4,1.284,22,1.934,32,0.84,44,1.534,65,2.605,68,2.605,118,1.934,131,2.136,132,1.934,178,1.934,198,1.934,222,2.281,223,2.281,224,2.281,225,2.281,226,2.281,227,2.281,228,2.281,229,1.934,230,2.281,231,2.281,232,2.281,233,1.934,234,2.281]],["t/836",[13,2.156,21,1.901,85,1.711,162,1.901,212,3.207,219,2.156,235,2.543,236,2.543,237,4.517,238,2.543,239,2.543,240,2.543,241,2.543,242,2.543,243,2.543,244,2.543,245,3.207,246,2.543,247,2.543,248,2.543]],["t/864",[85,3.464,249,5.15]],["t/885",[2,0.68,29,2.887,250,4.711,251,4.711]],["t/950",[1,2.054,2,0.476,5,1.86,68,2.469,137,2.469,155,2.8,156,2.8,157,2.469,172,2.024,245,2.8,252,3.303,253,3.303,254,3.303]],["t/970",[0,3.009,46,3.412,129,3.009,255,4.025,256,4.025,257,4.025,258,4.025,259,4.025]],["t/1001",[260,5.401]],["t/1030",[1,2.711,5,2.187,197,2.903,261,3.292,262,3.292,263,3.292]],["t/1091",[1,2.711,5,2.187,197,2.903,261,3.292,262,3.292,263,3.292]],["t/1152",[1,0.76,2,0.396,4,1.548,9,1.136,21,1.262,32,1.012,85,1.136,123,2.055,172,1.684,194,1.432,208,1.432,229,1.432,264,2.749,265,1.432,266,1.689,267,1.689,268,2.749,269,1.689,270,1.689,271,1.689,272,2.749,273,2.749,274,1.689,275,1.689,276,1.689,277,1.689,278,1.689,279,1.689,280,2.749,281,1.689,282,1.689,283,1.689,284,1.689,285,1.689,286,1.689,287,1.689]],["t/1172",[2,0.541,29,2.299,144,2.804,288,3.752,289,3.752,290,3.752,291,3.752,292,3.752,293,3.752,294,3.752]],["t/1205",[0,3.377,56,3.377,127,3.377,295,4.518,296,4.518]],["t/1366",[2,0.394,11,2.316,25,1.837,29,1.674,31,2.075,123,2.042,124,2.042,162,2.042,233,2.316,265,3.381,297,2.732,298,2.732,299,2.732,300,4.71,301,2.732,302,2.732,303,2.732]]],"invertedIndex":[["",{"_index":1,"t":{"1":{"position":[[14,1]]},"13":{"position":[[90,1]]},"109":{"position":[[35,1]]},"433":{"position":[[65,2]]},"479":{"position":[[38,2]]},"950":{"position":[[38,2],[111,2]]},"1030":{"position":[[0,1],[2,2],[55,2],[66,1]]},"1091":{"position":[[0,1],[2,2],[55,2],[66,1]]},"1152":{"position":[[394,1]]}}}],["40",{"_index":290,"t":{"1172":{"position":[[28,3]]}}}],["access",{"_index":52,"t":{"148":{"position":[[58,6]]},"239":{"position":[[133,7]]}}}],["ad",{"_index":108,"t":{"292":{"position":[[127,5]]}}}],["add",{"_index":244,"t":{"836":{"position":[[211,3]]}}}],["aesthet",{"_index":211,"t":{"675":{"position":[[112,10]]}}}],["alreadi",{"_index":112,"t":{"292":{"position":[[249,7]]}}}],["api",{"_index":11,"t":{"13":{"position":[[32,4]]},"1366":{"position":[[220,4]]}}}],["app",{"_index":131,"t":{"376":{"position":[[4,3]]},"558":{"position":[[53,4]]},"572":{"position":[[21,5]]},"675":{"position":[[148,3]]},"708":{"position":[[58,5],[122,4]]}}}],["appli",{"_index":292,"t":{"1172":{"position":[[62,7]]}}}],["applic",{"_index":21,"t":{"13":{"position":[[138,13]]},"836":{"position":[[49,11]]},"1152":{"position":[[417,11]]}}}],["approach",{"_index":168,"t":{"479":{"position":[[254,9]]}}}],["architectur",{"_index":202,"t":{"592":{"position":[[108,13]]}}}],["articl",{"_index":198,"t":{"592":{"position":[[5,7]]},"708":{"position":[[14,7]]}}}],["attribut",{"_index":209,"t":{"675":{"position":[[76,10]]}}}],["auth",{"_index":30,"t":{"31":{"position":[[11,4]]},"109":{"position":[[46,4],[137,5]]},"148":{"position":[[71,4]]},"239":{"position":[[24,4]]},"292":{"position":[[341,4]]}}}],["authelia",{"_index":33,"t":{"47":{"position":[[21,8]]}}}],["authent",{"_index":71,"t":{"169":{"position":[[7,14],[97,13]]},"183":{"position":[[39,14]]}}}],["authentik",{"_index":36,"t":{"78":{"position":[[21,9]]}}}],["auto",{"_index":118,"t":{"292":{"position":[[350,4]]},"708":{"position":[[179,4]]}}}],["automat",{"_index":82,"t":{"169":{"position":[[218,14]]}}}],["avail",{"_index":162,"t":{"479":{"position":[[97,9]]},"836":{"position":[[61,9]]},"1366":{"position":[[108,9]]}}}],["back",{"_index":227,"t":{"708":{"position":[[147,6]]}}}],["backup",{"_index":125,"t":{"353":{"position":[[100,6],[205,6]]},"376":{"position":[[261,7]]}}}],["backup/restor",{"_index":122,"t":{"353":{"position":[[17,14]]}}}],["bake",{"_index":42,"t":{"109":{"position":[[58,5]]}}}],["basic",{"_index":201,"t":{"592":{"position":[[94,6]]}}}],["behind",{"_index":49,"t":{"148":{"position":[[17,6]]}}}],["beyond",{"_index":120,"t":{"353":{"position":[[0,6]]}}}],["both",{"_index":207,"t":{"675":{"position":[[0,4]]}}}],["box",{"_index":302,"t":{"1366":{"position":[[129,3]]}}}],["build",{"_index":169,"t":{"496":{"position":[[0,5]]}}}],["built",{"_index":29,"t":{"31":{"position":[[2,5]]},"239":{"position":[[15,5]]},"885":{"position":[[10,5]]},"1172":{"position":[[32,5]]},"1366":{"position":[[91,5]]}}}],["carri",{"_index":113,"t":{"292":{"position":[[257,5]]}}}],["case",{"_index":177,"t":{"509":{"position":[[119,6]]}}}],["certain",{"_index":176,"t":{"509":{"position":[[107,7]]}}}],["chang",{"_index":139,"t":{"376":{"position":[[154,7]]}}}],["check",{"_index":284,"t":{"1152":{"position":[[404,5]]}}}],["click",{"_index":185,"t":{"572":{"position":[[46,5]]}}}],["cloud",{"_index":121,"t":{"353":{"position":[[11,5]]},"509":{"position":[[97,5]]}}}],["cloudflar",{"_index":50,"t":{"148":{"position":[[24,10],[47,10]]}}}],["code",{"_index":282,"t":{"1152":{"position":[[381,5]]}}}],["come",{"_index":289,"t":{"1172":{"position":[[17,5]]}}}],["command",{"_index":16,"t":{"13":{"position":[[101,7]]}}}],["common",{"_index":56,"t":{"148":{"position":[[113,6]]},"626":{"position":[[59,6]]},"1205":{"position":[[25,6]]}}}],["compon",{"_index":248,"t":{"836":{"position":[[284,10]]}}}],["config",{"_index":14,"t":{"13":{"position":[[66,6],[168,6]]}}}],["configur",{"_index":132,"t":{"376":{"position":[[8,13],[303,13]]},"708":{"position":[[233,13]]}}}],["consid",{"_index":151,"t":{"433":{"position":[[25,11]]}}}],["contain",{"_index":127,"t":{"353":{"position":[[135,9]]},"479":{"position":[[217,9]]},"1205":{"position":[[16,8]]}}}],["content",{"_index":298,"t":{"1366":{"position":[[41,7]]}}}],["contribut",{"_index":152,"t":{"433":{"position":[[37,12]]}}}],["control",{"_index":188,"t":{"572":{"position":[[88,7]]}}}],["convent",{"_index":259,"t":{"970":{"position":[[79,11]]}}}],["coupl",{"_index":252,"t":{"950":{"position":[[46,6]]}}}],["cover",{"_index":22,"t":{"13":{"position":[[155,6]]},"708":{"position":[[67,6]]}}}],["current",{"_index":271,"t":{"1152":{"position":[[122,7]]}}}],["custom",{"_index":233,"t":{"708":{"position":[[257,6]]},"1366":{"position":[[156,6]]}}}],["dashboard",{"_index":5,"t":{"1":{"position":[[37,9]]},"148":{"position":[[145,9]]},"239":{"position":[[102,9]]},"950":{"position":[[86,9]]},"1030":{"position":[[17,10]]},"1091":{"position":[[17,10]]}}}],["dashi",{"_index":2,"t":{"1":{"position":[[16,6]]},"13":{"position":[[0,5]]},"47":{"position":[[0,5]]},"78":{"position":[[0,5]]},"109":{"position":[[0,5]]},"148":{"position":[[0,5]]},"169":{"position":[[27,5],[151,5],[176,5],[256,5]]},"183":{"position":[[0,5]]},"259":{"position":[[0,5]]},"292":{"position":[[34,5],[243,5]]},"315":{"position":[[0,5]]},"353":{"position":[[107,6]]},"433":{"position":[[58,6]]},"479":{"position":[[11,6]]},"509":{"position":[[0,5]]},"548":{"position":[[41,6]]},"558":{"position":[[84,5]]},"572":{"position":[[0,5]]},"592":{"position":[[33,5]]},"675":{"position":[[175,5]]},"708":{"position":[[102,5]]},"885":{"position":[[0,5]]},"950":{"position":[[11,6]]},"1152":{"position":[[0,5],[196,5]]},"1172":{"position":[[11,5]]},"1366":{"position":[[0,5]]}}}],["dashy'",{"_index":117,"t":{"292":{"position":[[326,7]]}}}],["data",{"_index":128,"t":{"353":{"position":[[145,5]]}}}],["data/conf.yml",{"_index":134,"t":{"376":{"position":[[44,13]]}}}],["date",{"_index":225,"t":{"708":{"position":[[133,5]]}}}],["default",{"_index":288,"t":{"1172":{"position":[[3,7]]}}}],["depend",{"_index":163,"t":{"479":{"position":[[107,9]]}}}],["deploy",{"_index":158,"t":{"479":{"position":[[41,10],[183,9]]},"509":{"position":[[81,8]]},"558":{"position":[[74,9]]},"572":{"position":[[133,6]]}}}],["develop",{"_index":199,"t":{"592":{"position":[[52,11]]},"626":{"position":[[66,11]]}}}],["devic",{"_index":106,"t":{"292":{"position":[[113,6]]},"558":{"position":[[101,6]]}}}],["differ",{"_index":216,"t":{"675":{"position":[[199,9]]}}}],["directli",{"_index":141,"t":{"376":{"position":[[181,8]]}}}],["display",{"_index":265,"t":{"1152":{"position":[[39,7]]},"1366":{"position":[[22,10],[174,7]]}}}],["do",{"_index":53,"t":{"148":{"position":[[65,5]]}}}],["doc",{"_index":130,"t":{"353":{"position":[[188,5]]}}}],["docker",{"_index":126,"t":{"353":{"position":[[128,6],[198,6]]},"376":{"position":[[106,7]]},"479":{"position":[[198,6]]},"548":{"position":[[0,6]]}}}],["document",{"_index":0,"t":{"1":{"position":[[0,13]]},"970":{"position":[[5,8]]},"1205":{"position":[[7,8]]}}}],["doesn't",{"_index":90,"t":{"239":{"position":[[29,7]]}}}],["domain",{"_index":234,"t":{"708":{"position":[[264,8]]}}}],["done",{"_index":142,"t":{"376":{"position":[[207,4]]}}}],["download",{"_index":148,"t":{"376":{"position":[[289,8]]}}}],["dropdown",{"_index":293,"t":{"1172":{"position":[[79,8]]}}}],["dynam",{"_index":297,"t":{"1366":{"position":[[33,7]]}}}],["each",{"_index":268,"t":{"1152":{"position":[[68,4],[277,4]]}}}],["easi",{"_index":160,"t":{"479":{"position":[[61,5]]}}}],["edg",{"_index":54,"t":{"148":{"position":[[83,5]]}}}],["enforc",{"_index":43,"t":{"109":{"position":[[93,7]]}}}],["engin",{"_index":166,"t":{"479":{"position":[[227,7]]}}}],["english",{"_index":241,"t":{"836":{"position":[[138,7]]}}}],["environ",{"_index":200,"t":{"592":{"position":[[64,12]]}}}],["error",{"_index":286,"t":{"1152":{"position":[[440,5]]}}}],["everyon",{"_index":239,"t":{"836":{"position":[[117,8]]}}}],["everyth",{"_index":223,"t":{"708":{"position":[[74,10]]}}}],["explain",{"_index":219,"t":{"675":{"position":[[258,9]]},"836":{"position":[[165,8]]}}}],["export",{"_index":145,"t":{"376":{"position":[[253,7]]}}}],["expos",{"_index":58,"t":{"148":{"position":[[128,6]]},"292":{"position":[[158,8]]}}}],["featur",{"_index":264,"t":{"1152":{"position":[[22,7],[338,8]]}}}],["file",{"_index":24,"t":{"13":{"position":[[175,6]]},"376":{"position":[[119,4],[198,5],[317,5]]}}}],["first",{"_index":149,"t":{"433":{"position":[[0,5]]}}}],["follow",{"_index":178,"t":{"558":{"position":[[4,9]]},"708":{"position":[[4,9]]}}}],["form",{"_index":299,"t":{"1366":{"position":[[56,4]]}}}],["format",{"_index":136,"t":{"376":{"position":[[75,6],[82,7]]}}}],["front",{"_index":76,"t":{"169":{"position":[[58,5]]},"292":{"position":[[218,6]]}}}],["give",{"_index":275,"t":{"1152":{"position":[[238,5]]}}}],["glad",{"_index":156,"t":{"479":{"position":[[21,4]]},"950":{"position":[[21,4]]}}}],["guid",{"_index":89,"t":{"222":{"position":[[13,6]]},"626":{"position":[[32,5]]}}}],["handl",{"_index":77,"t":{"169":{"position":[[73,6]]}}}],["header",{"_index":70,"t":{"169":{"position":[[0,6],[168,7],[193,6]]},"292":{"position":[[291,8],[334,6]]}}}],["health",{"_index":277,"t":{"1152":{"position":[[267,6]]}}}],["here",{"_index":157,"t":{"479":{"position":[[33,4]]},"675":{"position":[[268,5]]},"950":{"position":[[33,4]]}}}],["home",{"_index":190,"t":{"572":{"position":[[107,4]]}}}],["homelab",{"_index":6,"t":{"1":{"position":[[56,7]]},"148":{"position":[[137,7]]}}}],["homelab'",{"_index":274,"t":{"1152":{"position":[[210,9]]}}}],["host",{"_index":4,"t":{"1":{"position":[[30,6]]},"353":{"position":[[70,6]]},"479":{"position":[[169,8]]},"572":{"position":[[81,6],[198,6]]},"708":{"position":[[51,6]]},"1152":{"position":[[100,5],[311,6]]}}}],["http",{"_index":47,"t":{"109":{"position":[[132,4]]},"169":{"position":[[163,4]]},"292":{"position":[[286,4]]}}}],["icon",{"_index":208,"t":{"675":{"position":[[36,5],[71,4],[93,5],[209,4]]},"1152":{"position":[[55,4]]}}}],["id",{"_index":97,"t":{"259":{"position":[[28,2]]}}}],["ident",{"_index":69,"t":{"148":{"position":[[268,8]]},"292":{"position":[[274,8]]}}}],["import",{"_index":238,"t":{"836":{"position":[[99,10]]}}}],["improv",{"_index":210,"t":{"675":{"position":[[99,8]]}}}],["inbound",{"_index":63,"t":{"148":{"position":[[198,7]]}}}],["includ",{"_index":8,"t":{"13":{"position":[[6,8]]}}}],["indic",{"_index":269,"t":{"1152":{"position":[[106,10]]}}}],["individu",{"_index":26,"t":{"13":{"position":[[193,10]]}}}],["instal",{"_index":186,"t":{"572":{"position":[[52,8]]}}}],["instead",{"_index":196,"t":{"572":{"position":[[180,8]]}}}],["instruct",{"_index":218,"t":{"675":{"position":[[231,12]]}}}],["intend",{"_index":171,"t":{"509":{"position":[[9,8]]}}}],["internation",{"_index":235,"t":{"836":{"position":[[0,20]]}}}],["internet",{"_index":60,"t":{"148":{"position":[[169,8]]},"292":{"position":[[184,9]]}}}],["intuit",{"_index":214,"t":{"675":{"position":[[157,9]]}}}],["it'",{"_index":270,"t":{"1152":{"position":[[117,4]]}}}],["item",{"_index":28,"t":{"13":{"position":[[217,6]]},"675":{"position":[[18,5]]}}}],["keep",{"_index":224,"t":{"708":{"position":[[90,7]]}}}],["keycloak",{"_index":86,"t":{"183":{"position":[[23,8]]}}}],["lan/homelab",{"_index":175,"t":{"509":{"position":[[49,12]]}}}],["languag",{"_index":237,"t":{"836":{"position":[[80,10],[193,10],[221,9]]}}}],["laptop",{"_index":105,"t":{"292":{"position":[[92,7]]}}}],["let",{"_index":72,"t":{"169":{"position":[[22,4]]}}}],["licens",{"_index":221,"t":{"705":{"position":[[4,7]]}}}],["line",{"_index":17,"t":{"13":{"position":[[109,5]]}}}],["list",{"_index":179,"t":{"558":{"position":[[14,5]]}}}],["local",{"_index":173,"t":{"509":{"position":[[28,8]]}}}],["log",{"_index":231,"t":{"708":{"position":[[206,3]]}}}],["login",{"_index":78,"t":{"169":{"position":[[80,6],[262,5]]},"292":{"position":[[355,6]]}}}],["made",{"_index":140,"t":{"376":{"position":[[176,4]]},"572":{"position":[[16,4]]}}}],["maintain",{"_index":181,"t":{"558":{"position":[[42,10]]}}}],["make",{"_index":212,"t":{"675":{"position":[[138,5]]},"836":{"position":[[39,6],[242,4]]}}}],["manag",{"_index":65,"t":{"148":{"position":[[221,8]]},"353":{"position":[[177,10]]},"708":{"position":[[37,8],[210,11]]}}}],["menu",{"_index":294,"t":{"1172":{"position":[[88,4]]}}}],["messag",{"_index":287,"t":{"1152":{"position":[[446,8]]}}}],["metadata",{"_index":249,"t":{"864":{"position":[[5,8]]}}}],["method",{"_index":161,"t":{"479":{"position":[[89,7]]}}}],["mind",{"_index":251,"t":{"885":{"position":[[32,5]]}}}],["minut",{"_index":253,"t":{"950":{"position":[[56,8]]}}}],["mit",{"_index":220,"t":{"705":{"position":[[0,3]]}}}],["monitor",{"_index":230,"t":{"708":{"position":[[194,11]]}}}],["more",{"_index":213,"t":{"675":{"position":[[152,4]]}}}],["multipl",{"_index":215,"t":{"675":{"position":[[190,8]]}}}],["na",{"_index":195,"t":{"572":{"position":[[176,3]]}}}],["nativ",{"_index":240,"t":{"836":{"position":[[131,6]]}}}],["need",{"_index":92,"t":{"239":{"position":[[47,6]]}}}],["network",{"_index":102,"t":{"292":{"position":[[53,7]]}}}],["new",{"_index":245,"t":{"836":{"position":[[217,3],[280,3]]},"950":{"position":[[82,3]]}}}],["next",{"_index":267,"t":{"1152":{"position":[[60,4]]}}}],["offlin",{"_index":283,"t":{"1152":{"position":[[396,7]]}}}],["oidc",{"_index":34,"t":{"47":{"position":[[37,4]]},"78":{"position":[[38,4]]},"239":{"position":[[3,4]]},"259":{"position":[[38,4]]},"315":{"position":[[36,4]]}}}],["on",{"_index":55,"t":{"148":{"position":[[97,3]]},"572":{"position":[[42,3]]}}}],["onlin",{"_index":273,"t":{"1152":{"position":[[151,8],[387,6]]}}}],["onto",{"_index":192,"t":{"572":{"position":[[140,4]]}}}],["open",{"_index":62,"t":{"148":{"position":[[186,7]]}}}],["oper",{"_index":194,"t":{"572":{"position":[[156,9],[205,9]]},"1152":{"position":[[137,10]]}}}],["option",{"_index":9,"t":{"13":{"position":[[18,8]]},"109":{"position":[[83,6]]},"353":{"position":[[77,7],[212,8]]},"1152":{"position":[[13,8]]}}}],["out",{"_index":301,"t":{"1366":{"position":[[118,3]]}}}],["outlin",{"_index":129,"t":{"353":{"position":[[161,8]]},"592":{"position":[[13,8],[81,8]]},"970":{"position":[[14,8]]}}}],["overview",{"_index":276,"t":{"1152":{"position":[[251,8]]}}}],["page",{"_index":85,"t":{"169":{"position":[[268,5]]},"836":{"position":[[160,4]]},"864":{"position":[[0,4]]},"1152":{"position":[[226,5]]}}}],["panel",{"_index":189,"t":{"572":{"position":[[96,6]]}}}],["parti",{"_index":20,"t":{"13":{"position":[[132,5]]}}}],["party/commun",{"_index":180,"t":{"558":{"position":[[26,15]]}}}],["pass",{"_index":80,"t":{"169":{"position":[[126,6]]},"376":{"position":[[131,6]]}}}],["password",{"_index":40,"t":{"109":{"position":[[37,8]]}}}],["phone",{"_index":104,"t":{"292":{"position":[[85,6]]}}}],["ping",{"_index":278,"t":{"1152":{"position":[[333,4]]}}}],["platform",{"_index":191,"t":{"572":{"position":[[119,10]]}}}],["plenti",{"_index":93,"t":{"239":{"position":[[62,6]]}}}],["plug",{"_index":115,"t":{"292":{"position":[[306,5]]}}}],["pocket",{"_index":96,"t":{"259":{"position":[[21,6]]}}}],["popular",{"_index":99,"t":{"292":{"position":[[15,7]]}}}],["port",{"_index":64,"t":{"148":{"position":[[206,6]]}}}],["primer",{"_index":222,"t":{"708":{"position":[[27,6]]}}}],["privaci",{"_index":250,"t":{"885":{"position":[[21,7]]}}}],["privat",{"_index":101,"t":{"292":{"position":[[45,7]]}}}],["problem",{"_index":295,"t":{"1205":{"position":[[32,8]]}}}],["process",{"_index":236,"t":{"836":{"position":[[28,7]]}}}],["programmat",{"_index":15,"t":{"13":{"position":[[73,16]]}}}],["protect",{"_index":94,"t":{"239":{"position":[[86,10]]}}}],["provid",{"_index":35,"t":{"47":{"position":[[42,9]]},"78":{"position":[[43,9]]},"148":{"position":[[277,9]]},"259":{"position":[[43,9]]},"315":{"position":[[41,9]]},"675":{"position":[[214,10]]}}}],["proxi",{"_index":75,"t":{"169":{"position":[[49,5],[91,5]]}}}],["public",{"_index":59,"t":{"148":{"position":[[162,6]]},"292":{"position":[[177,6]]}}}],["put",{"_index":100,"t":{"292":{"position":[[30,3]]}}}],["rang",{"_index":187,"t":{"572":{"position":[[67,5]]}}}],["reach",{"_index":103,"t":{"292":{"position":[[69,5],[234,8]]}}}],["read",{"_index":12,"t":{"13":{"position":[[41,7]]},"169":{"position":[[182,5]]}}}],["readi",{"_index":183,"t":{"572":{"position":[[10,5]]}}}],["recommend",{"_index":167,"t":{"479":{"position":[[242,11]]},"548":{"position":[[14,11]]}}}],["releas",{"_index":255,"t":{"970":{"position":[[27,7]]}}}],["relev",{"_index":285,"t":{"1152":{"position":[[431,8]]}}}],["repositori",{"_index":258,"t":{"970":{"position":[[68,10]]}}}],["request",{"_index":111,"t":{"292":{"position":[[225,8]]}}}],["reset",{"_index":146,"t":{"376":{"position":[[269,6]]}}}],["respons",{"_index":280,"t":{"1152":{"position":[[357,8],[372,8]]}}}],["rest",{"_index":10,"t":{"13":{"position":[[27,4]]}}}],["revers",{"_index":74,"t":{"169":{"position":[[41,7]]}}}],["run",{"_index":172,"t":{"509":{"position":[[24,3]]},"548":{"position":[[33,7]]},"592":{"position":[[39,7]]},"950":{"position":[[103,7]]},"1152":{"position":[[81,7],[290,7]]}}}],["schedul",{"_index":256,"t":{"970":{"position":[[35,9]]}}}],["script",{"_index":18,"t":{"13":{"position":[[115,7]]},"558":{"position":[[62,7]]}}}],["search",{"_index":260,"t":{"1001":{"position":[[0,9]]}}}],["section",{"_index":27,"t":{"13":{"position":[[204,8]]},"675":{"position":[[5,8]]}}}],["secur",{"_index":226,"t":{"708":{"position":[[139,7]]}}}],["see",{"_index":197,"t":{"572":{"position":[[189,3]]},"1030":{"position":[[58,3]]},"1091":{"position":[[58,3]]}}}],["self",{"_index":3,"t":{"1":{"position":[[25,4]]},"353":{"position":[[65,4]]},"479":{"position":[[164,4]]},"572":{"position":[[76,4],[193,4]]},"708":{"position":[[46,4]]}}}],["separ",{"_index":84,"t":{"169":{"position":[[247,8]]}}}],["seri",{"_index":203,"t":{"626":{"position":[[2,6]]}}}],["serv",{"_index":110,"t":{"292":{"position":[[209,5]]}}}],["server",{"_index":44,"t":{"109":{"position":[[106,6]]},"183":{"position":[[54,7]]},"572":{"position":[[112,6]]},"708":{"position":[[226,6]]}}}],["servic",{"_index":123,"t":{"353":{"position":[[32,8]]},"1152":{"position":[[89,7],[298,8]]},"1366":{"position":[[204,7]]}}}],["sever",{"_index":124,"t":{"353":{"position":[[51,7]]},"479":{"position":[[81,7]]},"1366":{"position":[[83,7]]}}}],["ship",{"_index":37,"t":{"109":{"position":[[6,5]]}}}],["short",{"_index":204,"t":{"626":{"position":[[12,5]]}}}],["show",{"_index":279,"t":{"1152":{"position":[[352,4]]}}}],["showcas",{"_index":263,"t":{"1030":{"position":[[45,9]]},"1091":{"position":[[45,9]]}}}],["side",{"_index":45,"t":{"109":{"position":[[113,4]]}}}],["sign",{"_index":81,"t":{"169":{"position":[[204,5]]}}}],["similar",{"_index":165,"t":{"479":{"position":[[209,7]]}}}],["simpl",{"_index":38,"t":{"109":{"position":[[19,6]]}}}],["small",{"_index":266,"t":{"1152":{"position":[[49,5]]}}}],["solut",{"_index":296,"t":{"1205":{"position":[[51,12]]}}}],["sourc",{"_index":170,"t":{"496":{"position":[[11,6]]}}}],["speaker",{"_index":242,"t":{"836":{"position":[[146,8]]}}}],["specif",{"_index":193,"t":{"572":{"position":[[147,8]]}}}],["specifi",{"_index":133,"t":{"376":{"position":[[25,9]]},"675":{"position":[[51,9]]}}}],["sponsor",{"_index":154,"t":{"465":{"position":[[0,8]]}}}],["stand",{"_index":67,"t":{"148":{"position":[[247,8]]}}}],["standard",{"_index":46,"t":{"109":{"position":[[123,8]]},"970":{"position":[[95,9]]}}}],["start",{"_index":229,"t":{"708":{"position":[[184,9]]},"1152":{"position":[[220,5]]}}}],["stat",{"_index":303,"t":{"1366":{"position":[[182,5]]}}}],["statu",{"_index":272,"t":{"1152":{"position":[[130,6],[322,6]]}}}],["step",{"_index":88,"t":{"222":{"position":[[0,4],[8,4]]}}}],["straight",{"_index":116,"t":{"292":{"position":[[312,8]]}}}],["submit",{"_index":262,"t":{"1030":{"position":[[28,6]]},"1091":{"position":[[28,6]]}}}],["suit",{"_index":91,"t":{"239":{"position":[[37,4]]}}}],["super",{"_index":159,"t":{"479":{"position":[[55,5]]}}}],["support",{"_index":31,"t":{"47":{"position":[[6,8]]},"78":{"position":[[6,8]]},"183":{"position":[[6,8]]},"259":{"position":[[6,8]]},"315":{"position":[[6,8]]},"675":{"position":[[181,8]]},"1366":{"position":[[10,7],[144,7]]}}}],["sweet",{"_index":261,"t":{"1030":{"position":[[11,5]]},"1091":{"position":[[11,5]]}}}],["switch",{"_index":243,"t":{"836":{"position":[[186,6]]}}}],["system",{"_index":41,"t":{"109":{"position":[[51,6]]},"479":{"position":[[133,6]]},"572":{"position":[[166,6],[215,8]]}}}],["tailnet",{"_index":109,"t":{"292":{"position":[[141,8]]}}}],["tailscal",{"_index":98,"t":{"292":{"position":[[0,9],[199,9]]}}}],["task",{"_index":206,"t":{"626":{"position":[[78,6]]}}}],["templat",{"_index":184,"t":{"572":{"position":[[27,10]]}}}],["text",{"_index":246,"t":{"836":{"position":[[247,4]]}}}],["thank",{"_index":150,"t":{"433":{"position":[[11,5]]}}}],["theme",{"_index":291,"t":{"1172":{"position":[[41,7]]}}}],["there'",{"_index":83,"t":{"169":{"position":[[236,7]]},"239":{"position":[[54,7]]}}}],["third",{"_index":19,"t":{"13":{"position":[[126,5]]},"558":{"position":[[20,5]]}}}],["through",{"_index":143,"t":{"376":{"position":[[212,7]]},"626":{"position":[[42,7]]}}}],["time",{"_index":281,"t":{"1152":{"position":[[366,5]]}}}],["tl",{"_index":66,"t":{"148":{"position":[[230,4]]}}}],["topic",{"_index":228,"t":{"708":{"position":[[167,6]]}}}],["toward",{"_index":153,"t":{"433":{"position":[[50,7]]}}}],["translat",{"_index":247,"t":{"836":{"position":[[252,12]]}}}],["trust",{"_index":73,"t":{"169":{"position":[[33,5]]}}}],["tunnel",{"_index":51,"t":{"148":{"position":[[35,6]]}}}],["tutori",{"_index":205,"t":{"626":{"position":[[18,10]]}}}],["type",{"_index":164,"t":{"479":{"position":[[125,4]]},"558":{"position":[[108,6]]}}}],["ui",{"_index":144,"t":{"376":{"position":[[224,3],[237,2]]},"675":{"position":[[131,2]]},"1172":{"position":[[100,3]]}}}],["unauthent",{"_index":95,"t":{"239":{"position":[[117,15]]}}}],["up",{"_index":68,"t":{"148":{"position":[[256,2]]},"708":{"position":[[127,2],[154,3]]},"950":{"position":[[96,2]]}}}],["us",{"_index":32,"t":{"47":{"position":[[15,5]]},"78":{"position":[[15,5]]},"183":{"position":[[15,5]]},"259":{"position":[[15,5]]},"315":{"position":[[15,5]]},"353":{"position":[[93,3]]},"376":{"position":[[100,5]]},"479":{"position":[[147,6]]},"509":{"position":[[115,3]]},"675":{"position":[[61,5],[87,5],[170,4]]},"708":{"position":[[251,5]]},"1152":{"position":[[172,6],[190,5]]}}}],["usag",{"_index":217,"t":{"675":{"position":[[225,5]]}}}],["user",{"_index":79,"t":{"169":{"position":[[115,5]]},"376":{"position":[[38,5]]}}}],["user'",{"_index":114,"t":{"292":{"position":[[267,6]]}}}],["usernam",{"_index":39,"t":{"109":{"position":[[26,8]]},"169":{"position":[[139,8]]}}}],["v17",{"_index":87,"t":{"183":{"position":[[32,6]]}}}],["valid",{"_index":147,"t":{"376":{"position":[[276,8]]}}}],["variou",{"_index":182,"t":{"558":{"position":[[93,7]]}}}],["view",{"_index":7,"t":{"2":{"position":[[0,5]]}}}],["volum",{"_index":138,"t":{"376":{"position":[[146,7]]}}}],["way",{"_index":57,"t":{"148":{"position":[[120,4]]},"239":{"position":[[78,4]]},"292":{"position":[[23,3]]},"548":{"position":[[26,3]]}}}],["web",{"_index":232,"t":{"708":{"position":[[222,3]]}}}],["welcom",{"_index":155,"t":{"479":{"position":[[0,7]]},"950":{"position":[[0,7]]}}}],["well",{"_index":25,"t":{"13":{"position":[[185,4]]},"109":{"position":[[71,4]]},"148":{"position":[[12,4]]},"1366":{"position":[[136,4]]}}}],["whole",{"_index":23,"t":{"13":{"position":[[162,5]]}}}],["widget",{"_index":300,"t":{"1366":{"position":[[64,8],[100,7],[163,7]]}}}],["within",{"_index":174,"t":{"509":{"position":[[37,6]]}}}],["without",{"_index":61,"t":{"148":{"position":[[178,7],[213,7],[239,7]]},"292":{"position":[[150,7]]}}}],["work",{"_index":48,"t":{"148":{"position":[[6,5]]}}}],["workflows/autom",{"_index":257,"t":{"970":{"position":[[45,22]]}}}],["write",{"_index":13,"t":{"13":{"position":[[53,7]]},"836":{"position":[[270,7]]}}}],["yaml",{"_index":135,"t":{"376":{"position":[[70,4]]}}}],["you'll",{"_index":254,"t":{"950":{"position":[[65,6]]}}}],["you'r",{"_index":137,"t":{"376":{"position":[[93,6]]},"479":{"position":[[26,6],[140,6],[157,6]]},"950":{"position":[[26,6]]}}}],["you'v",{"_index":107,"t":{"292":{"position":[[120,6]]}}}],["zitadel",{"_index":119,"t":{"315":{"position":[[21,7]]}}}]],"pipeline":["stemmer"]}},{"documents":[{"i":1,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"","u":"/docs/","p":1},{"i":2,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Alternate Views & Opening Methods","u":"/docs/alternate-views","p":2},{"i":13,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"REST API","u":"/docs/api","p":13},{"i":31,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Authentication","u":"/docs/authentication","p":31},{"i":47,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Authelia OIDC","u":"/docs/authentication/authelia-oidc","p":47},{"i":78,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Authentik OIDC","u":"/docs/authentication/authentik","p":78},{"i":109,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Built-In Auth","u":"/docs/authentication/built-in","p":109},{"i":148,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Cloudflare Tunnel","u":"/docs/authentication/cloudflare-tunnel","p":148},{"i":169,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Header Authentication","u":"/docs/authentication/header-auth","p":169},{"i":183,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Keycloak","u":"/docs/authentication/keycloak","p":183},{"i":222,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"OIDC","u":"/docs/authentication/oidc","p":222},{"i":239,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Other Auth Methods","u":"/docs/authentication/other-auth-methods","p":239},{"i":259,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Pocket ID OIDC","u":"/docs/authentication/pocketid","p":259},{"i":292,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Tailscale","u":"/docs/authentication/tailscale","p":292},{"i":315,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Zitadel OIDC","u":"/docs/authentication/zitadel","p":315},{"i":353,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Cloud Backup and Restore","u":"/docs/backup-restore","p":353},{"i":376,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Configuring","u":"/docs/configuring","p":376},{"i":433,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Contributing","u":"/docs/contributing","p":433},{"i":465,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Credits","u":"/docs/credits","p":465},{"i":479,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Deployment","u":"/docs/deployment","p":479},{"i":496,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Bare Metal","u":"/docs/deployment/bare-metal","p":496},{"i":509,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Cloud Services","u":"/docs/deployment/cloud","p":509},{"i":548,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Docker","u":"/docs/deployment/docker","p":548},{"i":558,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Self-Hosted Operating Systems","u":"/docs/deployment/self-hosted-os","p":558},{"i":572,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Self-Hosted Platforms","u":"/docs/deployment/self-hosted-platforms","p":572},{"i":592,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Developing","u":"/docs/developing","p":592},{"i":626,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Development Guides","u":"/docs/development-guides","p":626},{"i":675,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Icons","u":"/docs/icons","p":675},{"i":705,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"License","u":"/docs/license","p":705},{"i":708,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"App Management","u":"/docs/management","p":708},{"i":836,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Internationalization","u":"/docs/multi-language-support","p":836},{"i":864,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Pages and Sections","u":"/docs/pages-and-sections","p":864},{"i":885,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Privacy & Security","u":"/docs/privacy","p":885},{"i":950,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Quick Start","u":"/docs/quick-start","p":950},{"i":970,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Releases and Workflows","u":"/docs/release-workflow","p":970},{"i":1001,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Search & Shortcuts","u":"/docs/searching","p":1001},{"i":1030,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Dashy Showcase 🌟","u":"/docs/showcase","p":1030},{"i":1091,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Dashy Showcase 🌟","u":"/docs/showcase/","p":1091},{"i":1152,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Status Indicators","u":"/docs/status-indicators","p":1152},{"i":1172,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Theming","u":"/docs/theming","p":1172},{"i":1205,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Troubleshooting","u":"/docs/troubleshooting","p":1205},{"i":1366,"t":"dashy, dashboard, homelab, self-hosted, docker, homepage","s":"Widgets","u":"/docs/widgets","p":1366}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/1",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/2",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/13",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/31",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/47",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/78",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/109",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/148",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/169",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/183",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/222",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/239",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/259",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/292",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/315",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/353",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/376",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/433",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/465",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/479",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/496",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/509",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/548",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/558",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/572",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/592",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/626",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/675",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/705",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/708",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/836",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/864",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/885",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/950",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/970",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/1001",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/1030",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/1091",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/1152",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/1172",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/1205",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]],["t/1366",[0,0.012,1,0.012,2,0.012,3,0.012,4,0.012,5,0.012,6,0.012]]],"invertedIndex":[["dashboard",{"_index":1,"t":{"1":{"position":[[8,10]]},"2":{"position":[[8,10]]},"13":{"position":[[8,10]]},"31":{"position":[[8,10]]},"47":{"position":[[8,10]]},"78":{"position":[[8,10]]},"109":{"position":[[8,10]]},"148":{"position":[[8,10]]},"169":{"position":[[8,10]]},"183":{"position":[[8,10]]},"222":{"position":[[8,10]]},"239":{"position":[[8,10]]},"259":{"position":[[8,10]]},"292":{"position":[[8,10]]},"315":{"position":[[8,10]]},"353":{"position":[[8,10]]},"376":{"position":[[8,10]]},"433":{"position":[[8,10]]},"465":{"position":[[8,10]]},"479":{"position":[[8,10]]},"496":{"position":[[8,10]]},"509":{"position":[[8,10]]},"548":{"position":[[8,10]]},"558":{"position":[[8,10]]},"572":{"position":[[8,10]]},"592":{"position":[[8,10]]},"626":{"position":[[8,10]]},"675":{"position":[[8,10]]},"705":{"position":[[8,10]]},"708":{"position":[[8,10]]},"836":{"position":[[8,10]]},"864":{"position":[[8,10]]},"885":{"position":[[8,10]]},"950":{"position":[[8,10]]},"970":{"position":[[8,10]]},"1001":{"position":[[8,10]]},"1030":{"position":[[8,10]]},"1091":{"position":[[8,10]]},"1152":{"position":[[8,10]]},"1172":{"position":[[8,10]]},"1205":{"position":[[8,10]]},"1366":{"position":[[8,10]]}}}],["dashi",{"_index":0,"t":{"1":{"position":[[0,6]]},"2":{"position":[[0,6]]},"13":{"position":[[0,6]]},"31":{"position":[[0,6]]},"47":{"position":[[0,6]]},"78":{"position":[[0,6]]},"109":{"position":[[0,6]]},"148":{"position":[[0,6]]},"169":{"position":[[0,6]]},"183":{"position":[[0,6]]},"222":{"position":[[0,6]]},"239":{"position":[[0,6]]},"259":{"position":[[0,6]]},"292":{"position":[[0,6]]},"315":{"position":[[0,6]]},"353":{"position":[[0,6]]},"376":{"position":[[0,6]]},"433":{"position":[[0,6]]},"465":{"position":[[0,6]]},"479":{"position":[[0,6]]},"496":{"position":[[0,6]]},"509":{"position":[[0,6]]},"548":{"position":[[0,6]]},"558":{"position":[[0,6]]},"572":{"position":[[0,6]]},"592":{"position":[[0,6]]},"626":{"position":[[0,6]]},"675":{"position":[[0,6]]},"705":{"position":[[0,6]]},"708":{"position":[[0,6]]},"836":{"position":[[0,6]]},"864":{"position":[[0,6]]},"885":{"position":[[0,6]]},"950":{"position":[[0,6]]},"970":{"position":[[0,6]]},"1001":{"position":[[0,6]]},"1030":{"position":[[0,6]]},"1091":{"position":[[0,6]]},"1152":{"position":[[0,6]]},"1172":{"position":[[0,6]]},"1205":{"position":[[0,6]]},"1366":{"position":[[0,6]]}}}],["docker",{"_index":5,"t":{"1":{"position":[[44,7]]},"2":{"position":[[44,7]]},"13":{"position":[[44,7]]},"31":{"position":[[44,7]]},"47":{"position":[[44,7]]},"78":{"position":[[44,7]]},"109":{"position":[[44,7]]},"148":{"position":[[44,7]]},"169":{"position":[[44,7]]},"183":{"position":[[44,7]]},"222":{"position":[[44,7]]},"239":{"position":[[44,7]]},"259":{"position":[[44,7]]},"292":{"position":[[44,7]]},"315":{"position":[[44,7]]},"353":{"position":[[44,7]]},"376":{"position":[[44,7]]},"433":{"position":[[44,7]]},"465":{"position":[[44,7]]},"479":{"position":[[44,7]]},"496":{"position":[[44,7]]},"509":{"position":[[44,7]]},"548":{"position":[[44,7]]},"558":{"position":[[44,7]]},"572":{"position":[[44,7]]},"592":{"position":[[44,7]]},"626":{"position":[[44,7]]},"675":{"position":[[44,7]]},"705":{"position":[[44,7]]},"708":{"position":[[44,7]]},"836":{"position":[[44,7]]},"864":{"position":[[44,7]]},"885":{"position":[[44,7]]},"950":{"position":[[44,7]]},"970":{"position":[[44,7]]},"1001":{"position":[[44,7]]},"1030":{"position":[[44,7]]},"1091":{"position":[[44,7]]},"1152":{"position":[[44,7]]},"1172":{"position":[[44,7]]},"1205":{"position":[[44,7]]},"1366":{"position":[[44,7]]}}}],["homelab",{"_index":2,"t":{"1":{"position":[[20,8]]},"2":{"position":[[20,8]]},"13":{"position":[[20,8]]},"31":{"position":[[20,8]]},"47":{"position":[[20,8]]},"78":{"position":[[20,8]]},"109":{"position":[[20,8]]},"148":{"position":[[20,8]]},"169":{"position":[[20,8]]},"183":{"position":[[20,8]]},"222":{"position":[[20,8]]},"239":{"position":[[20,8]]},"259":{"position":[[20,8]]},"292":{"position":[[20,8]]},"315":{"position":[[20,8]]},"353":{"position":[[20,8]]},"376":{"position":[[20,8]]},"433":{"position":[[20,8]]},"465":{"position":[[20,8]]},"479":{"position":[[20,8]]},"496":{"position":[[20,8]]},"509":{"position":[[20,8]]},"548":{"position":[[20,8]]},"558":{"position":[[20,8]]},"572":{"position":[[20,8]]},"592":{"position":[[20,8]]},"626":{"position":[[20,8]]},"675":{"position":[[20,8]]},"705":{"position":[[20,8]]},"708":{"position":[[20,8]]},"836":{"position":[[20,8]]},"864":{"position":[[20,8]]},"885":{"position":[[20,8]]},"950":{"position":[[20,8]]},"970":{"position":[[20,8]]},"1001":{"position":[[20,8]]},"1030":{"position":[[20,8]]},"1091":{"position":[[20,8]]},"1152":{"position":[[20,8]]},"1172":{"position":[[20,8]]},"1205":{"position":[[20,8]]},"1366":{"position":[[20,8]]}}}],["homepag",{"_index":6,"t":{"1":{"position":[[53,8]]},"2":{"position":[[53,8]]},"13":{"position":[[53,8]]},"31":{"position":[[53,8]]},"47":{"position":[[53,8]]},"78":{"position":[[53,8]]},"109":{"position":[[53,8]]},"148":{"position":[[53,8]]},"169":{"position":[[53,8]]},"183":{"position":[[53,8]]},"222":{"position":[[53,8]]},"239":{"position":[[53,8]]},"259":{"position":[[53,8]]},"292":{"position":[[53,8]]},"315":{"position":[[53,8]]},"353":{"position":[[53,8]]},"376":{"position":[[53,8]]},"433":{"position":[[53,8]]},"465":{"position":[[53,8]]},"479":{"position":[[53,8]]},"496":{"position":[[53,8]]},"509":{"position":[[53,8]]},"548":{"position":[[53,8]]},"558":{"position":[[53,8]]},"572":{"position":[[53,8]]},"592":{"position":[[53,8]]},"626":{"position":[[53,8]]},"675":{"position":[[53,8]]},"705":{"position":[[53,8]]},"708":{"position":[[53,8]]},"836":{"position":[[53,8]]},"864":{"position":[[53,8]]},"885":{"position":[[53,8]]},"950":{"position":[[53,8]]},"970":{"position":[[53,8]]},"1001":{"position":[[53,8]]},"1030":{"position":[[53,8]]},"1091":{"position":[[53,8]]},"1152":{"position":[[53,8]]},"1172":{"position":[[53,8]]},"1205":{"position":[[53,8]]},"1366":{"position":[[53,8]]}}}],["host",{"_index":4,"t":{"1":{"position":[[35,7]]},"2":{"position":[[35,7]]},"13":{"position":[[35,7]]},"31":{"position":[[35,7]]},"47":{"position":[[35,7]]},"78":{"position":[[35,7]]},"109":{"position":[[35,7]]},"148":{"position":[[35,7]]},"169":{"position":[[35,7]]},"183":{"position":[[35,7]]},"222":{"position":[[35,7]]},"239":{"position":[[35,7]]},"259":{"position":[[35,7]]},"292":{"position":[[35,7]]},"315":{"position":[[35,7]]},"353":{"position":[[35,7]]},"376":{"position":[[35,7]]},"433":{"position":[[35,7]]},"465":{"position":[[35,7]]},"479":{"position":[[35,7]]},"496":{"position":[[35,7]]},"509":{"position":[[35,7]]},"548":{"position":[[35,7]]},"558":{"position":[[35,7]]},"572":{"position":[[35,7]]},"592":{"position":[[35,7]]},"626":{"position":[[35,7]]},"675":{"position":[[35,7]]},"705":{"position":[[35,7]]},"708":{"position":[[35,7]]},"836":{"position":[[35,7]]},"864":{"position":[[35,7]]},"885":{"position":[[35,7]]},"950":{"position":[[35,7]]},"970":{"position":[[35,7]]},"1001":{"position":[[35,7]]},"1030":{"position":[[35,7]]},"1091":{"position":[[35,7]]},"1152":{"position":[[35,7]]},"1172":{"position":[[35,7]]},"1205":{"position":[[35,7]]},"1366":{"position":[[35,7]]}}}],["self",{"_index":3,"t":{"1":{"position":[[30,4]]},"2":{"position":[[30,4]]},"13":{"position":[[30,4]]},"31":{"position":[[30,4]]},"47":{"position":[[30,4]]},"78":{"position":[[30,4]]},"109":{"position":[[30,4]]},"148":{"position":[[30,4]]},"169":{"position":[[30,4]]},"183":{"position":[[30,4]]},"222":{"position":[[30,4]]},"239":{"position":[[30,4]]},"259":{"position":[[30,4]]},"292":{"position":[[30,4]]},"315":{"position":[[30,4]]},"353":{"position":[[30,4]]},"376":{"position":[[30,4]]},"433":{"position":[[30,4]]},"465":{"position":[[30,4]]},"479":{"position":[[30,4]]},"496":{"position":[[30,4]]},"509":{"position":[[30,4]]},"548":{"position":[[30,4]]},"558":{"position":[[30,4]]},"572":{"position":[[30,4]]},"592":{"position":[[30,4]]},"626":{"position":[[30,4]]},"675":{"position":[[30,4]]},"705":{"position":[[30,4]]},"708":{"position":[[30,4]]},"836":{"position":[[30,4]]},"864":{"position":[[30,4]]},"885":{"position":[[30,4]]},"950":{"position":[[30,4]]},"970":{"position":[[30,4]]},"1001":{"position":[[30,4]]},"1030":{"position":[[30,4]]},"1091":{"position":[[30,4]]},"1152":{"position":[[30,4]]},"1172":{"position":[[30,4]]},"1205":{"position":[[30,4]]},"1366":{"position":[[30,4]]}}}]],"pipeline":["stemmer"]}},{"documents":[{"i":4,"t":"Dashy has three different views: Default View - This is the main homepage with sections in a grid layout Workspace View - Items displayed on the side, and are launched within Dashy Minimal View - A clean + simple tabbed view You can switch between views using the dropdown in the top-right corner. Set your chosen Starting View with appConfig.startingView. Click the page title at any time to go back to your selected starting view.","s":"Views","u":"/docs/alternate-views","h":"#views","p":2},{"i":6,"t":"This is the main page that you will land on when you first launch the application. Here all of your sections (with items + widgets) are visible in a grid layout. Example of Default View","s":"Default","u":"/docs/alternate-views","h":"#default","p":2},{"i":8,"t":"The workspace view displays your links in a sidebar on the left-hand side, and apps are launched inside an iframe without having to leave Dashy. This enables you to use all of your self-hosted apps from one place, and makes multi-tasking easy. You can specify a default app to be opened when you land on the workspace, by setting appConfig.workspaceLandingUrl: https://app-to-open/. If this app exists within your sections.items, then the corresponding section will also be expanded. You can also opt to keep previously opened websites/ apps open in the background, by setting appConfig.enableMultiTasking: true. This comes at the cost of performance, but does mean that your session with each app is preserved, enabling you to quickly switch between them. Example of Workspace View","s":"Workspace","u":"/docs/alternate-views","h":"#workspace","p":2},{"i":10,"t":"The minimal view aims to be super fast and simple, and can be used as a browser startpage. Items are grouped into a tab view, and the last opened tab will be remembered. Similar to the main view, you can search and launch items just by typing, and right-clicking will show more options (like open in modal, workspace or new tab). Example of Minimal View","s":"Minimal View","u":"/docs/alternate-views","h":"#minimal-view","p":2},{"i":12,"t":"Dashy supports several different ways to launch your apps. The primary opening method for each app can be specified using the target attribute, with a value of one of the following: sametab - The app will be launched in the current tab newtab - The app will be launched in a new tab top - Opens in the top-most browsing context, useful if you're accessing Dashy through an iframe modal - Launch app in a resizable/ movable popup modal on the current page workspace - Changes to Workspace view, and launches app You can also set a default opening method, which will be applied to all items that don't have a specified target, using appConfig.defaultOpeningMethod, to one of the above values. Even if the target is not set (or is set to sametab), you can still launch any given app in an alternative method. Either right-click to see all options, or use one of the keyboard shortcuts: Alt + Click will open the modal, and Ctrl + Click will open in a new tab. If you don't like the custom context menu, it can be disabled by setting appConfig.disableContextMenu: true. If you get a 'Refused to Connect' error in the modal or workspace views, then the target app has it's X-Frame-Options HTTP set to block requests from embedded content. You can easily fix this by setting this header to ALLOW, for instructions on how to do so, see the Troubleshooting Docs.","s":"Opening Methods","u":"/docs/alternate-views","h":"#opening-methods","p":2},{"i":14,"t":"Dashy includes an optional REST API, for reading and writing your config programmatically — from the command line, scripts or third-party applications. It covers whole config files, as well as individual sections and items. note The API is served by Dashy's Node server, so it's available with Docker and bare-metal deployments, but not on static hosting providers (Netlify, Vercel, EdgeOne, CDN).","s":"REST API","u":"/docs/api","h":"","p":13},{"i":16,"t":"The API is disabled by default. To enable it, set the ENABLE_API environmental variable to true. For example, with Docker Compose: environment: - ENABLE_API=true Or with docker run, pass -e ENABLE_API=true. While disabled, all /api/* requests return a 404.","s":"Enabling the API","u":"/docs/api","h":"#enabling-the-api","p":13},{"i":18,"t":"By default, the API uses Dashy's existing server-side authentication. Read endpoints require any authenticated user, and write endpoints require an admin. If no auth is configured, the API is open — the same as Dashy's other endpoints. # With HTTP Basic Auth (ENABLE_HTTP_AUTH or BASIC_AUTH_USERNAME / BASIC_AUTH_PASSWORD) curl -u alice:hunter2 http://localhost:8080/api/config # With OIDC / Keycloak, pass your ID token curl -H 'Authorization: Bearer ' http://localhost:8080/api/config","s":"Authentication","u":"/docs/api","h":"#authentication","p":13},{"i":20,"t":"If you have no auth configured, or would prefer a dedicated credential for the API, set the API_TOKEN environmental variable and send it as a bearer token. A valid token grants full (admin) access, and works alongside any other configured auth method. environment: - ENABLE_API=true - API_TOKEN=your-long-random-secret curl -H 'Authorization: Bearer your-long-random-secret' http://localhost:8080/api/config note Setting API_TOKEN also secures the API on deployments that have no other auth — anonymous requests are then rejected. Use a long, random value (e.g. openssl rand -hex 32) and only send it over HTTPS. The token applies to the API only, not Dashy's other endpoints.","s":"API token","u":"/docs/api","h":"#api-token","p":13},{"i":22,"t":":filename is any YAML config file in your user-data directory (e.g. conf.yml, or a sub-page like home-lab.yml). :key is one of the top-level config keys: pageInfo, appConfig, sections or pages. Method Path Description GET /api/config List config files GET /api/config/:filename Get a full config file, as JSON PUT /api/config/:filename Replace a full config file GET /api/config/:filename/:key Get a top-level key PUT /api/config/:filename/:key Replace a top-level key POST /api/config/:filename/sections Add a section (name required) GET /api/config/:filename/sections/:sid Get a section PATCH /api/config/:filename/sections/:sid Update fields on a section DELETE /api/config/:filename/sections/:sid Delete a section GET /api/config/:filename/sections/:sid/items List a section's items POST /api/config/:filename/sections/:sid/items Add an item (title required) GET /api/config/:filename/sections/:sid/items/:iid Get an item PATCH /api/config/:filename/sections/:sid/items/:iid Update fields on an item DELETE /api/config/:filename/sections/:sid/items/:iid Delete an item All bodies are JSON. Errors return { \"success\": false, \"message\": \"...\" } with an appropriate status code (400 bad input, 401/403 auth, 404 not found).","s":"Endpoints","u":"/docs/api","h":"#endpoints","p":13},{"i":24,"t":":sid and :iid can be either a zero-based index (0, 1, ...) or an exact match on the section's name / item's title (URL-encoded). If multiple entries share a name, the first match wins. A section literally named 2 can only be addressed by index.","s":"Addressing Sections and Items","u":"/docs/api","h":"#addressing-sections-and-items","p":13},{"i":26,"t":"PATCH does a shallow merge: only the fields you send are changed, but nested values (like a section's items array) are replaced wholesale if included. PUT replaces the target entirely.","s":"Updating","u":"/docs/api","h":"#updating","p":13},{"i":28,"t":"# List config files curl http://localhost:8080/api/config # Get your main config as JSON curl http://localhost:8080/api/config/conf.yml # Add an item to the first section curl -X POST -H 'Content-Type: application/json' \\ -d '{\"title\": \"Grafana\", \"url\": \"https://grafana.local\", \"icon\": \"hl-grafana\"}' \\ http://localhost:8080/api/config/conf.yml/sections/0/items # Rename a section curl -X PATCH -H 'Content-Type: application/json' \\ -d '{\"name\": \"Monitoring\"}' \\ 'http://localhost:8080/api/config/conf.yml/sections/Old%20Name' # Update the theme curl -X PUT -H 'Content-Type: application/json' \\ -d '{\"theme\": \"nord-frost\"}' \\ http://localhost:8080/api/config/conf.yml/appConfig After modifying your config, refresh the page to see changes.","s":"Examples","u":"/docs/api","h":"#examples","p":13},{"i":30,"t":"Writes re-serialize the YAML file, so comments, anchors and custom formatting are discarded (the same applies to saving via the UI). A timestamped backup is saved to user-data/config-backups/ before every write, unless DISABLE_CONFIG_BACKUPS=true Writes to conf.yml are validated against the schema and rejected if invalid. Sub-page files are not schema-validated, since they may contain only a subset of fields Config files are capped at 256 KB Concurrent writes are last-write-wins; there is no locking or optimistic concurrency","s":"Limitations & Notes","u":"/docs/api","h":"#limitations--notes","p":13},{"i":32,"t":"Built-In Auth Keycloak Header Authentication OIDC Auth Authentik Authelia Zitadel Pocket ID Zero-Trust Tunnels Cloudflare Tunnel Tailscale / Headscale Alternative Authentication Methods Reverse Proxy Auth VPN IP-Based Access Web Server Authentication Client Certificates (mTLS) SSO / OAuth Providers Cloud Hosting Providers important It is your responsibility to properly secure your Dashy instance. Never expose your Dashy instance to the public internet or untrusted users without sufficient authentication and authorization in place.","s":"Authentication","u":"/docs/authentication","h":"","p":31},{"i":34,"t":"Dashy includes a built-in username/password login, with optional server-side HTTP Basic Auth. This is the easiest way to get a login page, without needing to spin up any other services. The full guide, covering password hashing, env-var passwords, guest access, user roles, visibility controls and security notes, is in the Built-In Auth guide. To enable, simply add an array of users under appConfig.auth.users, each with a username (user) and a SHA256 hash of their password (hash). appConfig: disableConfigurationForNonAdmin: true auth: users: - user: alicia hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5 type: admin Optionally set this env var to enforce this on the server-side too ENABLE_HTTP_AUTH=true","s":"Built-In Auth","u":"/docs/authentication","h":"#built-in-auth","p":31},{"i":36,"t":"Dashy supports Keycloak (v17+) as an authentication provider. See the Keycloak guide for the full deploy and configuration walkthrough. appConfig: disableConfigurationForNonAdmin: true auth: enableKeycloak: true keycloak: serverUrl: http://localhost:9100 realm: dashy clientId: dashy adminRole: dashy-admin","s":"Keycloak","u":"/docs/authentication","h":"#keycloak","p":31},{"i":38,"t":"Dashy can defer authentication to a reverse proxy that injects the user's identity in a request header. See the Header Authentication guide. appConfig: auth: enableHeaderAuth: true users: - user: alice hash: 0a7b1d4c2e... type: admin headerAuth: userHeader: Remote-User proxyWhitelist: - 172.18.0.2","s":"Header Authentication","u":"/docs/authentication","h":"#header-authentication","p":31},{"i":40,"t":"Dashy has full support for OIDC based auth, with scoped permissions. See either the generic OIDC docs, or our provider-specific guides: Authentik Authelia Zitadel Pocket ID Keycloak appConfig: disableConfigurationForNonAdmin: true # Hide the config editor from non-admins (recommended) enableGuestAccess: false # Optional: view the dashboard read-only without signing in enableServiceWorker: true # Optional: enables the PWA and offline support enableAuthProxyCompat: true # Recover the PWA after a session expires (needs the service worker) auth: enableOidc: true # Turn OIDC on oidc: clientId: dashy # Client ID from your provider endpoint: https://auth.example.com/application/o/dashy/ # The issuer URL, not the .well-known one scope: openid profile email groups # Scopes to request (groups for adminGroup, roles for adminRole) adminGroup: dashy-admins # Members of this group are admins adminRole: dashy-admin # Or grant admin by role instead enableSilentRenew: true # Refresh the session in the background before it expires","s":"OIDC","u":"/docs/authentication","h":"#oidc","p":31},{"i":42,"t":"Dashy works well with third-party tunnel based auth, allowing you to access your dashboard remotely. Cloudflare Tunnel Tailscale / Headscale","s":"Zero-Trust Tunnels","u":"/docs/authentication","h":"#zero-trust-tunnels","p":31},{"i":44,"t":"These are alternatives to Dashy's built-in auth, Keycloak, and OIDC. Most of them sit in front of Dashy at the network or reverse proxy level, which is generally the better approach for anything internet-facing. Reverse Proxy Auth - Authelia, Authentik, or similar sitting in front of Dashy VPN - Keep Dashy off the internet entirely IP-Based Access - Restrict by source IP in your web server Web Server Authentication - HTTP basic auth at the proxy level Client Certificates (mTLS) - Require a client TLS certificate to connect SSO / OAuth Providers - Cloud-hosted identity providers Cloud Hosting Providers - Built-in auth on hosting platforms","s":"Alternative Authentication Methods","u":"/docs/authentication","h":"#alternative-authentication-methods","p":31},{"i":46,"t":"Method Type Description Complexity Security Best for No Auth Built-in This is the default state Dashy ships with 🟢 Easy 🔴 Weak Internal usage Built-In Auth Built-in Username/password list in your config, optionally enforced server-side 🟢 Easy 🟠 Medium A quick login screen on a trusted LAN Header Auth Built-in Trusts a username header from a proxy that already did the login 🟠 Medium 🟠 Medium Reusing an existing proxy or forward-auth session OIDC (generic) OIDC Any OpenID Connect provider, with server-side token checks and admin roles 🟠 Medium 🟢 Strong Standards-based SSO with any IdP Authentik OIDC Self-hosted IdP with a full admin UI, MFA and group policies 🟠 Medium 🟢 Strong One login across many self-hosted apps Authelia OIDC Lightweight self-hosted IdP, configured from a single YAML file 🟠 Medium 🟢 Strong Self-hosters who prefer file-based config Keycloak OIDC Heavyweight enterprise IdP with realms, roles and social login 🔴 Hard 🟢 Strong Larger or enterprise deployments Pocket ID OIDC Minimal passkey-only IdP, a single Go binary 🟠 Medium 🟢 Strong Passwordless homelab SSO Zitadel OIDC Go/Postgres IdP with project roles (needs an Action to map groups) 🔴 Hard 🟢 Strong Role-based access across projects Cloudflare Tunnel Tunnel Outbound tunnel to Cloudflare's edge, paired with Access for login 🟠 Medium 🟢 Strong Public access with no open ports Tailscale / Headscale Tunnel Private WireGuard mesh, with optional Funnel for public access 🟢 Easy 🟢 Strong Private remote access between your devices Reverse Proxy Auth Proxy An auth server (Authelia, Authentik, OAuth2 Proxy) in front via forward-auth 🟠 Medium 🟢 Strong Protecting many apps behind one proxy Web Server Auth Proxy HTTP basic auth handled by your reverse proxy 🟢 Easy 🟠 Medium A fast password prompt over HTTPS Client Certificates (mTLS) Proxy Require a client TLS certificate to connect, enforced at the proxy 🔴 Hard 🟢 Strong A small fixed set of trusted devices IP-Based Access Network Allow only certain source IPs at the web server 🟢 Easy 🟠 Medium An extra layer on a static IP or VPN VPN Network Keep Dashy off the internet, reach it over WireGuard, Tailscale or OpenVPN 🟠 Medium 🟢 Strong Private access with zero public exposure SSO / OAuth Providers OIDC Cloud IdPs (Auth0, Okta, Google) wired in through Dashy's OIDC 🟠 Medium 🟢 Strong Offloading identity to a managed provider Cloud Hosting Providers Platform Platform-level auth (Cloudflare Access, Netlify, Vercel) outside Dashy 🟢 Easy 🟢 Strong Dashboards hosted on a cloud platform ⬆️ Back to Top","s":"Comparison of Auth Options","u":"/docs/authentication","h":"#comparison-of-auth-options","p":31},{"i":48,"t":"Dashy supports using Authelia as its OIDC provider. Authelia is an open source authentication and authorization server for self-hosted services. It speaks OIDC, supports MFA, and is configured via a single YAML file. Users live in a flat file, LDAP, or your own SQL store. It's a popular pairing with reverse proxies for forward-auth across many apps, and the same instance can act as your Dashy OIDC provider.","s":"Authelia OIDC","u":"/docs/authentication/authelia-oidc","h":"","p":47},{"i":50,"t":"1. Deploy Authelia 2. Configure Authelia Generate secrets and keys Users database Main configuration 3. Enabling Authelia in Dashy 4. Groups and Visibility 5. Silent token renewal (optional) Troubleshooting How it Works Client side Server side Visual Overview","s":"Contents","u":"/docs/authentication/authelia-oidc","h":"#contents","p":47},{"i":52,"t":"Authelia v4.38+ requires HTTPS for the session cookie URLs. In production, terminate TLS at a reverse proxy on a real domain. For local testing the example below uses a self-signed cert and lvh.me, a wildcard DNS name that resolves to 127.0.0.1 everywhere, so no /etc/hosts edits are needed. Example docker-compose.yml name: dashy-authelia services: authelia: image: authelia/authelia:4.38 restart: unless-stopped ports: - \"9091:9091\" volumes: - ./config:/config environment: TZ: UTC healthcheck: test: [\"CMD-SHELL\", \"wget --no-check-certificate -qO- https://127.0.0.1:9091/api/health >/dev/null 2>&1\"] start_period: 20s interval: 10s timeout: 5s retries: 10 The config/ directory holds Authelia's configuration.yml, the user database, the OIDC signing key, and (for local testing) the TLS cert. Generate them in step 2.","s":"1. Deploy Authelia","u":"/docs/authentication/authelia-oidc","h":"#1-deploy-authelia","p":47},{"i":55,"t":"Authelia needs four secrets: a session secret, a storage encryption key, an OIDC HMAC secret, and an RSA private key for signing id_tokens. For local testing also generate a self-signed TLS cert. mkdir -p config SESSION_SECRET=$(openssl rand -hex 64) STORAGE_KEY=$(openssl rand -hex 32) HMAC_SECRET=$(openssl rand -hex 64) openssl genrsa -out config/oidc-private.pem 2048 # Local testing only. In production use a real cert from your reverse proxy. openssl req -x509 -newkey rsa:2048 -nodes \\ -keyout config/tls.key -out config/tls.crt \\ -days 365 -subj \"/CN=authelia.lvh.me\" \\ -addext \"subjectAltName=DNS:authelia.lvh.me,DNS:*.lvh.me\" Keep the three shell variables around: you'll paste them into configuration.yml below.","s":"Generate secrets and keys","u":"/docs/authentication/authelia-oidc","h":"#generate-secrets-and-keys","p":47},{"i":57,"t":"Hash a password with Authelia's CLI: docker run --rm authelia/authelia:4.38 \\ authelia crypto hash generate argon2 --password 'YourPasswordHere' Copy the Digest: value into config/users_database.yml: users: authelia-admin: disabled: false displayname: \"Authelia Admin\" password: \"$argon2id$v=19$m=65536,t=3,p=4$...\" email: admin@example.com groups: - admins authelia-user: disabled: false displayname: \"Authelia User\" password: \"$argon2id$v=19$m=65536,t=3,p=4$...\" email: user@example.com groups: - users The admins group name is what Dashy's adminGroup will match.","s":"Users database","u":"/docs/authentication/authelia-oidc","h":"#users-database","p":47},{"i":59,"t":"config/configuration.yml: theme: light server: address: 'tcp://:9091' tls: certificate: /config/tls.crt key: /config/tls.key log: level: info totp: issuer: authelia.lvh.me authentication_backend: password_reset: disable: true file: path: /config/users_database.yml password: algorithm: argon2id access_control: default_policy: one_factor session: secret: cookies: - domain: 'lvh.me' authelia_url: 'https://authelia.lvh.me:9091' regulation: max_retries: 3 find_time: 2m ban_time: 5m storage: encryption_key: local: path: /config/db.sqlite3 notifier: filesystem: filename: /config/notifications.txt identity_providers: oidc: hmac_secret: cors: endpoints: - authorization - token - revocation - introspection - userinfo allowed_origins: - http://localhost:4000 jwks: - key_id: dashy algorithm: RS256 use: sig key: | -----BEGIN PRIVATE KEY----- (contents of config/oidc-private.pem) -----END PRIVATE KEY----- clients: - client_id: dashy client_name: Dashy public: true authorization_policy: one_factor require_pkce: true pkce_challenge_method: S256 redirect_uris: - http://localhost:4000 - http://localhost:4000/ scopes: - openid - profile - email - groups userinfo_signed_response_alg: none Key things to note: public: true and require_pkce: true are required: Dashy is a SPA, so it must be a public client with PKCE Register the redirect URI both with and without a trailing slash, since browsers may strip or add one The groups scope is required for the adminGroup check userinfo_signed_response_alg: none keeps the userinfo response as JSON, which is what oidc-client-ts (Dashy's OIDC client) expects The cors block is required: Dashy is a SPA and makes cross-origin calls from the browser to Authelia's token and userinfo endpoints, and without these origins allow-listed the browser blocks the token exchange after login. allowed_origins must list Dashy's exact origin (scheme + host + port) For production, swap server.tls out for a reverse proxy doing HTTPS, change session.cookies to your real domain (e.g. domain: example.com, authelia_url: https://auth.example.com), and pick a real user backend (LDAP or SQL). Bring Authelia up: docker compose up -d Once it's healthy, https://authelia.lvh.me:9091/.well-known/openid-configuration should return JSON with issuer: https://authelia.lvh.me:9091. That issuer URL is what Dashy needs.","s":"Main configuration","u":"/docs/authentication/authelia-oidc","h":"#main-configuration","p":47},{"i":61,"t":"Authelia is now configured with an OIDC client for Dashy.","s":"Summary","u":"/docs/authentication/authelia-oidc","h":"#summary","p":47},{"i":63,"t":"In /user-data/conf.yml: appConfig: ... disableConfigurationForNonAdmin: true auth: enableOidc: true oidc: clientId: dashy endpoint: https://authelia.lvh.me:9091 adminGroup: admins scope: openid profile email groups Where: disableConfigurationForNonAdmin - Prevent read/write config access to non-admin users auth.enableOidc - Set the auth mode to OIDC clientId - Must match the client_id field of the Authelia client endpoint - The Authelia base URL (the OIDC issuer). Dashy appends /.well-known/openid-configuration itself adminGroup - Name of the Authelia group that grants admin in Dashy scope - Must include groups for the adminGroup check to work Restart Dashy for these changes to take effect. If Authelia uses a self-signed cert, Dashy's server has to trust it before it can fetch the discovery doc or JWKS. Start Dashy with NODE_EXTRA_CA_CERTS=/path/to/tls.crt, or mount your CA bundle into the Dashy container and point that env var at it. With a real cert this isn't needed. Your browser also needs to trust the cert (visit https://authelia.lvh.me:9091 once and accept the warning, or import the cert into the browser's trust store). Everything should now be fully configured and working 🎉 When you load Dashy, you'll be redirected to Authelia's login page. After signing in, you'll land back on Dashy's homepage with full access, and all of Dashy's client, server and asset endpoints will be locked behind authentication.","s":"3. Enabling Authelia in Dashy","u":"/docs/authentication/authelia-oidc","h":"#3-enabling-authelia-in-dashy","p":47},{"i":65,"t":"Once group membership is in the id_token, you can use it to hide or show pages, sections and items. The property name is hideForKeycloakUsers / showForKeycloakUsers (the name is historical; it works for any OIDC provider, including Authelia). To make an Admin section visible only to members of admins: displayData: showForKeycloakUsers: groups: - admins Both showForKeycloakUsers and hideForKeycloakUsers accept lists of groups and roles. If a user matches an entry they're allowed or excluded as defined. sections: - name: Internal Tools displayData: showForKeycloakUsers: groups: ['admins'] hideForKeycloakUsers: groups: ['users'] items: - title: Hidden from interns displayData: hideForKeycloakUsers: groups: ['interns']","s":"4. Groups and Visibility","u":"/docs/authentication/authelia-oidc","h":"#4-groups-and-visibility","p":47},{"i":67,"t":"By default, when your token expires Dashy sends you back through Authelia's login to get a new one. Set enableSilentRenew: true to have Dashy refresh the session quietly in the background instead, using a refresh token: oidc: clientId: dashy endpoint: https://authelia.lvh.me:9091 adminGroup: admins scope: openid profile email groups enableSilentRenew: true Dashy adds the offline_access scope to its request automatically, but Authelia only issues a refresh token if the client is allowed to. Add offline_access to the client's scopes and refresh_token to its grant_types: clients: - client_id: dashy scopes: - openid - profile - email - groups - offline_access grant_types: - authorization_code - refresh_token It's off by default, and if a refresh ever fails Dashy falls back to the normal sign-in. See silent token renewal for the full notes and caveats.","s":"5. Silent token renewal (optional)","u":"/docs/authentication/authelia-oidc","h":"#5-silent-token-renewal-optional","p":47},{"i":69,"t":"Config validation fails on session cookies​ Problem: Authelia refuses to start, complaining option 'authelia_url' does not have a secure scheme or option 'domain' is not a valid cookie domain. Solution: v4.38+ requires HTTPS for authelia_url and a cookie domain with at least one period. Use a real domain (example.com, auth.example.com) or lvh.me for local testing, and enable TLS either on Authelia or in front of it. Dashy server can't fetch Authelia's discovery doc​ Problem: Dashy server logs show TLS errors (self-signed certificate, UNABLE_TO_VERIFY_LEAF_SIGNATURE) when fetching .well-known/openid-configuration or the JWKS. Solution: Authelia is on a self-signed cert. Start Dashy with NODE_EXTRA_CA_CERTS=/path/to/tls.crt, or use a real certificate. The signing cert for OIDC tokens is separate from the TLS cert: it's the TLS cert that needs to be trusted by Node. \"Critical Configuration Load Error\" with a CORS message on first load​ Problem: Browser shows Cross-Origin Request Blocked ... at https://authelia.lvh.me:9091/.well-known/openid-configuration. (Reason: CORS request did not succeed). Status code: (null) before any redirect to Authelia, and Dashy renders a Critical Configuration Load Error. Solution: Misleading error: this is the self-signed cert, not CORS. When the browser sees an untrusted HTTPS cert during a JS fetch it blocks the request silently with no chance for you to click through. Open https://authelia.lvh.me:9091 directly in the same browser, click through the warning, then reload Dashy. For a less hacky setup, use mkcert to generate a locally-trusted cert, or terminate TLS at a reverse proxy with a real Let's Encrypt cert. CORS error on the token endpoint after login​ Problem: Login at Authelia succeeds, but the redirect back to Dashy fails with a CORS error on a POST to /api/oidc/token. Solution: Authelia's CORS allow-list doesn't include Dashy's origin. Add the identity_providers.oidc.cors block from step 2 with token (and ideally authorization, userinfo, revocation, introspection) under endpoints, and Dashy's origin under allowed_origins. Restart Authelia. Redirect loop after login​ Problem: Browser bounces between Dashy and Authelia repeatedly. Solution: endpoint in conf.yml probably includes .well-known/openid-configuration. Drop everything from .well-known onwards; Dashy appends it itself. invalid_request: redirect URI mismatch​ Problem: Authelia returns invalid_request saying the redirect URI is not registered. Solution: Authelia matches redirect URIs exactly. Register both the bare URL and the trailing-slash variant (http://localhost:4000 and http://localhost:4000/) on the client. Logged in but config saves return 403​ Problem: User authenticates fine, but saving the dashboard returns 403. Solution: The id_token isn't carrying the group claim. Paste the token (from localStorage, key ID_TOKEN) into jwt.io and look for groups. If it's missing, the client's scopes list doesn't include groups. If the claim is there but the user isn't in it, add them to the admins group in users_database.yml (or whatever backend you're using) and restart Authelia. Issuer mismatch behind a reverse proxy​ Problem: Server logs show unexpected \"iss\" claim value. The browser reaches Authelia at https://auth.example.com but Authelia advertises a different issuer. Solution: Make sure your reverse proxy forwards X-Forwarded-Proto: https and X-Forwarded-Host, and that session.cookies[].authelia_url matches the public URL exactly. Locked out after a few wrong passwords​ Problem: Login fails with \"User has been banned\" even with the correct password. Solution: The regulation block bans an account after max_retries failed attempts within find_time. Default ban is 5 minutes. Wait it out or docker compose restart authelia to clear the in-memory ban. Two-factor prompt appears unexpectedly​ Problem: After password entry Authelia asks for a TOTP code or other 2FA, but you wanted password-only for Dashy. Solution: access_control.default_policy is set to two_factor. Change it to one_factor, or add a specific rule with policy: one_factor matching the Dashy redirect. Username claim is a UUID, not the username​ Problem: Dashy logs show users with random-looking subject IDs. Solution: Authelia's sub claim is an opaque UUID per user. Dashy's OIDC client falls through preferred_username, email, then sub, so as long as the profile and email scopes are requested the username should be human-readable. Confirm both scopes are in the client's scopes list. Token expired / clock skew​ Problem: 401s with \"exp\" claim timestamp check failed or \"iat\" claim timestamp check failed, even just after login. Solution: Dashy allows 30 seconds of drift. Sync clocks on both hosts with NTP. Container clocks follow their host, so it's almost always the host that's drifted. Numeric Client ID truncated​ Problem: Audience mismatch when clientId in conf.yml is a long numeric string. Solution: Wrap numeric Client IDs in quotes (e.g. clientId: \"12345678901234567\"). Without quotes YAML parses the value as a JS number and loses precision past around 15 digits. Dashy server can't reach Authelia​ Problem: Auth'd API calls return 401 and Dashy logs show fetch errors for .well-known/openid-configuration. Solution: endpoint must be reachable from inside the Dashy container, not just from the browser. If both run in Docker, put them on the same network. Test with docker exec wget -qO- \"$ENDPOINT/.well-known/openid-configuration\". Config change to auth.oidc not picked up​ Problem: Updated clientId, endpoint, adminGroup or scope in conf.yml, but Dashy still uses the old values. Solution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under auth.oidc.","s":"Troubleshooting common Authelia Issues","u":"/docs/authentication/authelia-oidc","h":"#troubleshooting-common-authelia-issues","p":47},{"i":71,"t":"If you're a developer or contributor looking to understand or make changes to Dashy's OIDC implementation, the following outlines how it's wired together. The same OIDC pipeline backs Authelia, Authentik, Keycloak, and any other generic OIDC provider. The only Authelia-specific code is your configuration; everything else is shared.","s":"How it Works","u":"/docs/authentication/authelia-oidc","h":"#how-it-works","p":47},{"i":73,"t":"Boot starts in src/main.js. After the initial /conf.yml fetch parses the auth block, isOidcEnabled() decides whether to lazily import oidc-client-ts and call initOidcAuth(). src/utils/auth/OidcAuth.js wraps oidc-client-ts. On load it inspects the URL: if it sees a ?code= callback it runs userManager.signinCallback() to exchange the code (and PKCE verifier) for tokens, persists the user info, and hard-redirects to /. Otherwise it calls userManager.getUser(); if there's no usable session it falls through to userManager.signinRedirect() to send the browser to Authelia. A short-lived sessionStorage guard prevents the redirect loop that would otherwise occur if the IdP returns without a usable user. persistUserInfo() writes the raw id_token, the user's groups and roles, a derived isAdmin flag, and a username (falling back through preferred_username, email, and sub) to localStorage. The keys (ID_TOKEN, KEYCLOAK_INFO, USERNAME, ISADMIN) live in src/utils/config/defaults.js; the KEYCLOAK_INFO name is historical and reused for all OIDC providers, including Authelia. src/utils/auth/getApiAuthHeader.js builds the Authorization header for every internal API call. It does a client-side exp check and returns null for missing or expired tokens, so the next request triggers a fresh login rather than a 401. src/utils/IsVisibleToUser.js reads KEYCLOAK_INFO when evaluating showForKeycloakUsers and hideForKeycloakUsers rules.","s":"Client side","u":"/docs/authentication/authelia-oidc","h":"#client-side","p":47},{"i":75,"t":"services/auth-oidc.js contains the entire server-side auth surface, in five small pieces: loadOidcSettings() reads auth.oidc (or auth.keycloak) at boot and returns a normalised { issuer, clientId, adminGroup, adminRole }. For generic OIDC providers the issuer is whatever you set as endpoint in conf.yml, verbatim createOidcMiddleware() returns a Connect middleware. Permissive on no-token requests so the SPA can bootstrap; otherwise it verifies the Bearer token against the issuer's JWKS using jose. Checks cover signature, issuer (against the canonical value from the discovery doc), audience (must equal clientId), and expiry, with a 30-second clock-skew tolerance. Sets req.auth = { user, isAdmin, claims } on success, 401 on failure getIssuerContext() lazily fetches .well-known/openid-configuration on first use and wraps jwks_uri in createRemoteJWKSet, which handles JWKS caching and on-demand key rotation. The result is memoised per-issuer for the life of the process deriveIsAdmin() checks the token's groups claim against adminGroup, and the top-level roles claim against adminRole (for Keycloak it also folds in the nested realm_access.roles / resource_access..roles arrays). Authelia only emits groups, so the group path is what's used in practice maybeBootstrapConfig() is the stripped-response helper. When auth is configured, guest access is off, and an unauthenticated request hits the root /conf.yml, it returns a minimal copy with only appConfig.auth, appConfig.enableServiceWorker, and a pageInfo.title of Login | . Sections, items, hostnames and any other secrets never leave the server services/app.js wires it all together. The middleware mounts as protectConfig in front of every YAML route and config-mutating route. The /*.yml handler sets Cache-Control: private, no-store and Vary: Authorization whenever auth is configured (so intermediate caches can never mix auth states), then calls maybeBootstrapConfig; a stripped result is sent as-is, otherwise res.sendFile serves the full file. POST /config-manager/save is additionally guarded by requireAdmin, which returns 401 if req.auth is unset and 403 if req.auth.isAdmin is false.","s":"Server side","u":"/docs/authentication/authelia-oidc","h":"#server-side","p":47},{"i":77,"t":"End-to-end authentication flow Server-side request handling","s":"Visual Overview","u":"/docs/authentication/authelia-oidc","h":"#visual-overview","p":47},{"i":79,"t":"Dashy supports using Authentik as its OIDC provider. Authentik is an open source identity provider that speaks OIDC, OAuth 2.0, SAML 2.0 and LDAP. It runs in Docker, has a polished admin UI, and supports MFA, social login, and per-application group policies, which makes it a good fit for self-hosted setups where you want a single login across many services.","s":"Authentik OIDC","u":"/docs/authentication/authentik","h":"","p":78},{"i":81,"t":"1. Deploy Authentik 2. Configure Authentik Create the groups scope Create the OIDC provider Create the application Create the admin group Create test users Restrict who can access Dashy (optional) 3. Enabling Authentik in Dashy 4. Groups and Visibility 5. Silent token renewal (optional) Troubleshooting Config Example How it Works","s":"Contents","u":"/docs/authentication/authentik","h":"#contents","p":78},{"i":83,"t":"If you've not already done so, spin up an Authentik instance, following the official docs. The compose file below is a minimal local setup. A .env file alongside the compose file (generate fresh secrets with openssl rand -hex 32): AUTHENTIK_TAG=2024.12 PG_PASS=replace-me-with-random-hex AUTHENTIK_SECRET_KEY=replace-me-with-random-hex AUTHENTIK_BOOTSTRAP_PASSWORD=change-me-now AUTHENTIK_BOOTSTRAP_EMAIL=you@example.com AUTHENTIK_BOOTSTRAP_TOKEN=replace-me-with-random-hex AUTHENTIK_TAG pins the Authentik version. 2024.12 is a tested baseline; any 2024.10+ release works too (the Invalidation flow field below needs 2024.10 or newer). Example docker-compose.yml name: authentik services: postgresql: image: docker.io/library/postgres:16-alpine restart: unless-stopped healthcheck: test: [\"CMD-SHELL\", \"pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}\"] start_period: 20s interval: 10s retries: 5 timeout: 5s volumes: - ./data/postgres:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: ${PG_PASS} POSTGRES_USER: authentik POSTGRES_DB: authentik redis: image: docker.io/library/redis:7-alpine command: --save 60 1 --loglevel warning restart: unless-stopped healthcheck: test: [\"CMD-SHELL\", \"redis-cli ping | grep PONG\"] start_period: 20s interval: 10s retries: 5 timeout: 3s volumes: - ./data/redis:/data server: image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG} restart: unless-stopped command: server environment: &authentik-env AUTHENTIK_REDIS__HOST: redis AUTHENTIK_POSTGRESQL__HOST: postgresql AUTHENTIK_POSTGRESQL__USER: authentik AUTHENTIK_POSTGRESQL__NAME: authentik AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD} AUTHENTIK_BOOTSTRAP_TOKEN: ${AUTHENTIK_BOOTSTRAP_TOKEN} AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL} AUTHENTIK_ERROR_REPORTING__ENABLED: \"false\" ports: - \"9000:9000\" - \"9443:9443\" depends_on: postgresql: {condition: service_healthy} redis: {condition: service_healthy} worker: image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG} restart: unless-stopped command: worker environment: *authentik-env depends_on: postgresql: {condition: service_healthy} redis: {condition: service_healthy} Bring it up: docker compose up -d First boot runs database migrations and takes a minute or two. Once the server container is healthy, open http://localhost:9000 and sign in as akadmin with the bootstrap password.","s":"1. Deploy Authentik","u":"/docs/authentication/authentik","h":"#1-deploy-authentik","p":78},{"i":86,"t":"Authentik doesn't expose group membership in the id_token by default. Dashy needs it for the adminGroup check and for the showForKeycloakUsers / hideForKeycloakUsers visibility rules. Go to Customization > Property Mappings Click Create > Scope Mapping Set Name to groups Set Scope name to groups Set Expression to: return {\"groups\": [g.name for g in request.user.ak_groups.all()]} Click Finish","s":"Create the groups scope","u":"/docs/authentication/authentik","h":"#create-the-groups-scope","p":78},{"i":88,"t":"Go to Applications > Providers Click Create, pick OAuth2/OpenID Provider, click Next Set Name to Dashy Set Authorization flow to default-provider-authorization-implicit-consent (use default-provider-authorization-explicit-consent if you want users to confirm sign-in each time) Set Invalidation flow to default-provider-invalidation-flow (required on Authentik 2024.10 and newer) Under Protocol settings: Client type: Public Client ID: dashy, or leave the auto-generated value and copy it for later Redirect URIs with matching mode Strict, one URL per line. Register both the bare URL and the trailing-slash version: https://dashy.example.com https://dashy.example.com/ Signing Key: the built-in authentik Self-signed Certificate is fine Expand Advanced protocol settings: Add openid, profile, email, and the groups scope you just created to Selected Scopes Turn Include claims in id_token on Click Finish","s":"Create the OIDC provider","u":"/docs/authentication/authentik","h":"#create-the-oidc-provider","p":78},{"i":90,"t":"Go to Applications > Applications Click Create Set Name to Dashy Set Slug to dashy (this becomes part of the issuer URL: /application/o//) Set Provider to the Dashy provider you just made Click Create Now open the Dashy provider again (Applications > Providers > Dashy) and copy the OpenID Configuration Issuer URL shown on the page (e.g. https://auth.example.com/application/o/dashy/). The provider only displays a valid URL once it's bound to an application. You'll need this for Dashy's endpoint setting later.","s":"Create the application","u":"/docs/authentication/authentik","h":"#create-the-application","p":78},{"i":92,"t":"Go to Directory > Groups Click Create Set Name to dashy-admins Click Create Open the new group, click Users, and add any users who should have admin rights in Dashy","s":"Create the admin group","u":"/docs/authentication/authentik","h":"#create-the-admin-group","p":78},{"i":94,"t":"If you want separate accounts beyond akadmin: Go to Directory > Users Click Create, fill in Username, Name and Email, click Create On the new user's page, click Set password, set a password, click Update Add the user to dashy-admins for admin access, or leave them out for a non-admin","s":"Create test users","u":"/docs/authentication/authentik","h":"#create-test-users","p":78},{"i":96,"t":"By default any Authentik user can sign in to Dashy. To limit access to one or more groups, bind a group policy to the Dashy application; Authentik then denies sign-in to anyone outside those groups. This is separate from adminGroup, which only controls who gets admin rights inside Dashy, not who can access it at all. Go to Applications > Applications and open the Dashy application screenshot Open the Policy / Group / User Bindings tab and click Bind existing policy screenshot Switch to the Group tab, choose the group that should have access, make sure Enabled is on, and click Create screenshot Access is now limited to members of the bound group. Add another binding for each additional group that should be allowed in.","s":"Restrict who can access Dashy (optional)","u":"/docs/authentication/authentik","h":"#restrict-who-can-access-dashy-optional","p":78},{"i":98,"t":"Finally, you need to tell Dashy to use Authentik. This goes in the appConfig.auth section of your main /user-data/conf.yml. appConfig: ... disableConfigurationForNonAdmin: true auth: enableOidc: true oidc: clientId: dashy endpoint: https://auth.example.com/application/o/dashy/ adminGroup: dashy-admins scope: openid profile email groups Where: disableConfigurationForNonAdmin - Prevent read/write config access to non-admin users auth.enableOidc - Set the auth mode to OIDC clientId - The Client ID from the Authentik provider (exact, case-sensitive) endpoint - The OpenID Configuration Issuer URL from the provider page. Use the bare issuer, not the discovery URL; Dashy appends /.well-known/openid-configuration itself adminGroup - Name of the Authentik group that grants admin in Dashy (matches the dashy-admins group above). To use roles instead, set adminRole, but Authentik has no roles claim by default, so groups are the simpler path here scope - Space-separated list of scopes to request. Must include groups when adminGroup is set, otherwise the id_token won't carry the claim To let visitors view a read-only dashboard without signing in, add enableGuestAccess: true under auth; they skip the Authentik login, and admins still get edit access after signing in. See guest access for the details. Restart Dashy for these changes to take effect. If Authentik runs on a different host or behind a reverse proxy, make sure endpoint is reachable from inside the Dashy container, and that the issuer URL the provider advertises matches endpoint exactly. Everything should now be fully configured and working 🎉 When you load Dashy, you'll be redirected to Authentik's login page. After signing in you will land back on Dashy's homepage with full access, and all of Dashy's client, server and asset endpoints will be locked behind authentication.","s":"3. Enabling Authentik in Dashy","u":"/docs/authentication/authentik","h":"#3-enabling-authentik-in-dashy","p":78},{"i":100,"t":"Once group membership is in the id_token, you can use it to hide or show pages, sections and items in Dashy. The property name is hideForKeycloakUsers / showForKeycloakUsers (the name is historical; it works for any OIDC provider, including Authentik). To make an Admin section visible only to members of dashy-admins: displayData: showForKeycloakUsers: groups: - dashy-admins Both showForKeycloakUsers and hideForKeycloakUsers accept lists of groups and roles. If a user matches an entry they're allowed or excluded as defined. sections: - name: Internal Tools displayData: showForKeycloakUsers: groups: ['dashy-admins'] hideForKeycloakUsers: groups: ['guests'] items: - title: Hidden from interns displayData: hideForKeycloakUsers: groups: ['interns']","s":"4. Groups and Visibility","u":"/docs/authentication/authentik","h":"#4-groups-and-visibility","p":78},{"i":102,"t":"By default, when your token expires Dashy sends you back through Authentik's login to get a new one. Set enableSilentRenew: true to have Dashy refresh the session quietly in the background instead, using a refresh token: oidc: clientId: dashy endpoint: https://auth.example.com/application/o/dashy/ adminGroup: dashy-admins scope: openid profile email groups enableSilentRenew: true Dashy adds the offline_access scope to its request automatically. Authentik ships an offline_access scope mapping by default, so just make sure it's listed under the provider's Advanced protocol settings > Selected Scopes. It's off by default, and if a refresh ever fails Dashy falls back to the normal sign-in. See silent token renewal for the full notes and caveats. How often renewal fires is set by the provider's Access Token validity (and Refresh Token validity) under Advanced protocol settings in Authentik; the defaults suit most people.","s":"5. Silent token renewal (optional)","u":"/docs/authentication/authentik","h":"#5-silent-token-renewal-optional","p":78},{"i":104,"t":"Two places will tell you what went wrong. Client-side problems, like a token Dashy can't use or a renewal that didn't take, are logged to the browser console tagged SSO or OIDC, so open your browser's DevTools and check the Console tab. Token verification failures show up in the Dashy server logs instead. Check whichever fits what you're seeing. Migrations still running on first boot​ Problem: Authentik returns 502 or never reaches the login page right after docker compose up. Solution: First boot runs database migrations and can take a minute or two. Tail the logs with docker compose logs -f server and wait for the uvicorn startup line before opening the UI. Redirect loop after login​ Problem: Browser bounces between Dashy and Authentik repeatedly. Solution: endpoint in conf.yml probably includes .well-known/openid-configuration. Drop everything from .well-known onwards; Dashy appends it itself. invalid_redirect_uri​ Problem: Authentik shows \"invalid redirect URI\" after submitting credentials. Solution: The URL Dashy is being served from doesn't exactly match what's registered on the provider. Register both the bare URL and the trailing-slash variant (e.g. https://dashy.example.com and https://dashy.example.com/), keep matching mode on Strict, and make sure the scheme matches (http vs https). Logged in but config saves return 403​ Problem: User authenticates fine, but saving the dashboard returns 403. Solution: The id_token isn't carrying the group claim. Paste the token (from localStorage, key idToken) into jwt.io and look for groups. If it's missing, the groups scope mapping isn't attached to the provider's Selected Scopes or Include claims in id_token is off. If the claim is there but the user isn't in it, add them to the dashy-admins group. Issuer mismatch behind a reverse proxy​ Problem: Server logs show unexpected \"iss\" claim value. The browser reaches Authentik over HTTPS, but Authentik advertises an HTTP issuer in its discovery document. Solution: Set AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS on the Authentik server and worker containers to include your proxy's IP range (e.g. 172.16.0.0/12 for default Docker bridges), and make sure the proxy forwards X-Forwarded-Proto: https. Once Authentik trusts the proxy, its discovery document will advertise the public HTTPS URL. Audience mismatch on token verification​ Problem: Server logs show unexpected \"aud\" claim value. Every auth'd API call returns 401. Solution: clientId in conf.yml must exactly match the provider's Client ID field. If you let Authentik auto-generate one, copy the exact value (including case) from the provider page. \"SSO token is encrypted\"​ Problem: The browser console shows SSO token is encrypted. Dashy needs signed JWT tokens, not encrypted JWE tokens. and sign-in doesn't stick. Solution: The provider has an Encryption Key set, so Authentik hands Dashy an encrypted (JWE) token it can't read. Open the Dashy provider, expand Advanced protocol settings, clear the Encryption Key field so only the Signing Key stays set, and save. Dashy needs a signed token, not an encrypted one. Self-signed Authentik certificate rejected​ Problem: Fetching the discovery doc or JWKS fails and Dashy logs the generic [auth-oidc] token verification failed: fetch failed. Underneath that fetch failed is a TLS cert rejection (a self-signed or untrusted-CA cert on Authentik's HTTPS endpoint); the OpenSSL reason like self-signed certificate sits in the error cause, not the log line. Solution: Use a real certificate on the Authentik HTTPS endpoint (Let's Encrypt or your homelab CA), or mount your CA bundle into the Dashy container and set NODE_EXTRA_CA_CERTS=/path/to/ca.pem. Authentik's built-in authentik Self-signed Certificate is only used to sign tokens; the TLS cert is whatever's terminating HTTPS in front of Authentik. \"OIDC signinCallback returned no user\"​ Problem: Login submits, Authentik redirects back, then the browser console logs OIDC signinCallback returned no user and sign-in fails. Solution: The id_token came back without a usable username claim. Confirm profile and email are in the provider's Selected Scopes, that Include claims in id_token is on, and that the user has an email or username set in Authentik. Logout stuck on a consent screen​ Problem: Clicking Logout sends the user to Authentik's end-session endpoint, which prompts for confirmation and never returns. Solution: This is the default behaviour of default-provider-invalidation-flow. To skip the prompt, change the provider's Invalidation flow to one without a consent stage, or accept the extra click. Token expired / clock skew​ Problem: 401s with \"exp\" claim timestamp check failed, even just after login. Solution: Dashy allows 30 seconds of drift. Sync clocks on both hosts with NTP. Container clocks follow their host, so it's almost always the host that's drifted. Silent renewal never refreshes the session​ Problem: With enableSilentRenew: true the session still drops when the token expires, and the browser console mentions ensure offline_access is granted. Solution: Authentik isn't issuing a refresh token because the offline_access scope isn't granted. Open the Dashy provider, expand Advanced protocol settings, add the built-in offline_access scope to Selected Scopes, and save. Dashy requests offline_access on its own, so all Authentik has to do is allow it. Numeric Client ID truncated​ Problem: Audience mismatch when clientId in conf.yml is a long numeric string. Solution: Wrap numeric Client IDs in quotes (e.g. clientId: \"12345678901234567\"). Without quotes YAML parses the value as a JS number and loses precision past around 15 digits. Dashy server can't reach Authentik​ Problem: Auth'd API calls return 401 and Dashy logs show fetch errors for .well-known/openid-configuration. Solution: endpoint must be reachable from inside the Dashy container, not just from the browser. If both run in Docker, put them on the same network. Test with docker exec wget -qO- \"$ENDPOINT/.well-known/openid-configuration\". Config change to auth.oidc not picked up​ Problem: Updated clientId, endpoint, adminGroup or scope in conf.yml, but Dashy still uses the old values. Solution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under auth.oidc.","s":"Troubleshooting common Authentik Issues","u":"/docs/authentication/authentik","h":"#troubleshooting-common-authentik-issues","p":78},{"i":106,"t":"Below is an example of a configured local dashy instance (port 4000) for Authentik. Screenshots of Dashy config in Authentik","s":"Config Example","u":"/docs/authentication/authentik","h":"#config-example","p":78},{"i":108,"t":"Nothing here is specific to Authentik. Dashy speaks standard OIDC, so the same flow works with Keycloak or any other provider; only the config differs. Here's what happens when you open Dashy with OIDC enabled: Your browser asks the Dashy server for the config. You're not signed in yet, so the server only sends back the auth settings. Your sections, items and URLs stay on the server. Dashy sees OIDC is enabled and redirects you to Authentik to sign in, using the standard authorization code flow with PKCE. You enter your credentials (plus MFA if you've set it up). Authentik sends you back to Dashy with a one-time code, which the browser swaps for a signed token proving who you are and which groups you're in. The browser stores that token and attaches it to every request it makes to the Dashy server. The server checks each token against Authentik's published signing keys, and makes sure it was issued by your Authentik, for Dashy, and hasn't expired. A valid token gets the full config; no token or a bad one gets sent back to the login flow. Your Authentik groups ride along inside the token. Being in the adminGroup lets you edit and save the config, and groups also power the show/hide visibility rules. When the token expires you're bounced back through Authentik for a new one, which is usually instant since you still have a session there. With enableSilentRenew on, Dashy refreshes it in the background and you won't notice at all. To sign out, use Dashy's Logout control: it clears the stored token and sends you to Authentik's end-session endpoint (see Logout stuck on a consent screen if that asks for confirmation). If you want the implementation details, the client side lives in src/utils/auth/OidcAuth.js and the server-side token verification in services/auth-oidc.js.","s":"How it Works","u":"/docs/authentication/authentik","h":"#how-it-works","p":78},{"i":110,"t":"Dashy ships with a simple username + password auth system baked in, as well as the option to enforce this server-side with standard HTTP auth. This is the quickest way to setup a login screen, without the need to run any other auth services. No identity provider to run, no external dependencies, just a list of users in conf.yml. Useful for keeping casual eyes of your dashboard within a LAN, but is probably not recommended if your instance is exposed to the public internet. This guide covers everything the built-in auth can do, from the lightest \"show a login screen\" mode to a server-enforced setup that 401s every unauthenticated request. If you need OIDC, SSO, or anything internet-facing, see the other guides in this folder. Built-in auth is convenient, not hardened.","s":"Built-In Auth","u":"/docs/authentication/built-in","h":"","p":109},{"i":112,"t":"Quick Start Defining Users Choosing a Protection Mode Sessions, Guest Access & Logout User Roles & Visibility Auto-Authenticating the Frontend Troubleshooting Security Considerations How it Works","s":"Contents","u":"/docs/authentication/built-in","h":"#contents","p":109},{"i":114,"t":"Add a single user to appConfig.auth.users in conf.yml, with a SHA-256 hash of your password: appConfig: disableConfigurationForNonAdmin: true auth: users: - user: alicia hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5 type: admin ENABLE_HTTP_AUTH=true Restart Dashy for changes to take effect. You'll be bounced to a login screen. Enter your username and the plaintext password that hashes to the value above (12345 if you copy-pasted the example. Please change it).","s":"Quick Start","u":"/docs/authentication/built-in","h":"#quick-start","p":109},{"i":116,"t":"appConfig.auth.users is a list of user records. Each entry has a username, a password (as a hash or via an env var), and an optional admin flag. appConfig: auth: users: - user: alicia hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5 type: admin - user: guest hash: EF797C8118F02DFB649607DD5D3F8C7623048C9C063D532CC95C5ED7A898A64F type: normal Field reference: user - Username. Lowercased on lookup, so casing doesn't matter at login hash - SHA-256 hash of the password, hex, 64 chars. Either upper or lower case works type - admin or normal. Defaults to normal. Admins can save config changes; normal users cannot. See User Roles & Visibility for what else this gates password - Alternative to hash. The name of a VITE_APP_* environment variable holding the plaintext password. Dashy hashes the env var's value at runtime. This requires building the app from source (don't work with pre-built Docker image) Each user needs user plus exactly one of hash or password (not both!)","s":"Defining Users","u":"/docs/authentication/built-in","h":"#defining-users","p":109},{"i":118,"t":"If you're using hash, you need to create a SHA-256 hash ↗ of your password. Either run echo -n \"my-super-secure-password\" | sha256sum, or use an online tool, such as this one or CyberChef (which can be self-hosted and run locally).","s":"Generating a password hash","u":"/docs/authentication/built-in","h":"#generating-a-password-hash","p":109},{"i":120,"t":"If you'd rather not put hashed passwords to conf.yml, point at an env var: - user: alicia password: VITE_APP_ALICIA_PASSWORD Then run Dashy with VITE_APP_ALICIA_PASSWORD=supersecret. Dashy hashes the value at login time and compares it to what the user types. The env var name must start with VITE_APP_; anything else is rejected. warning Caveat: env-var passwords only resolve in the browser if the variable was set when the Dashy bundle was built. The official Docker image is pre-built, so it can't pick up env-var users you set at runtime for the frontend. For env-var users to work, either build Dashy yourself or use hash inline instead. The server-side enforcement does read env vars at runtime regardless.","s":"Using an env var instead of an inline hash","u":"/docs/authentication/built-in","h":"#using-an-env-var-instead-of-an-inline-hash","p":109},{"i":122,"t":"Defining users is one decision. Deciding what those users protect is another. There are three modes, depending on your use case: Mode What you set Login page? Server enforces? Client-side only auth.users in conf.yml ✅ yes ❌ no Server-enforced via conf.yml users auth.users + ENABLE_HTTP_AUTH=true ✅ yes ✅ yes Static credentials BASIC_AUTH_USERNAME + BASIC_AUTH_PASSWORD env vars ❌ no (browser prompt) ✅ yes","s":"Choosing a Protection Mode","u":"/docs/authentication/built-in","h":"#choosing-a-protection-mode","p":109},{"i":124,"t":"With just auth.users set in conf.yml, and no env vars, Dashy shows a login screen, and after a successful login it sets a session cookie containing a SHA-256 token. The SPA refuses to render the dashboard without that cookie. Suitable for: a friend or family member should land on the login screen rather than the dashboard, but you trust everyone on the network not to poke around in dev tools.","s":"Client-side login","u":"/docs/authentication/built-in","h":"#client-side-login","p":109},{"i":126,"t":"Same auth.users list, but the server enforces it too. Set this env var when starting Dashy: ENABLE_HTTP_AUTH=true Now every API endpoint, every .yml file, and every server-side route requires a valid session token in the Authorization header (delivered automatically by the SPA after login). Unauthenticated requests get a 401 with a basic-auth challenge. The login page is still the user-facing entry point. The flow is: User loads the page, SPA bundle renders the login screen User submits credentials, frontend hashes the password and validates against users[] On success, a session cookie is set Every subsequent API/asset request includes the cookie as a bearer token in the Authorization header The server validates the same token against users[] before letting the request through This is the recommended setup if you're using built-in auth.","s":"Server-enforced via conf.yml users","u":"/docs/authentication/built-in","h":"#server-enforced-via-confyml-users","p":109},{"i":128,"t":"If you don't want the Dashy login page at all and would rather have the browser handle auth via a native HTTP basic-auth dialog, skip auth.users and set: BASIC_AUTH_USERNAME=alicia BASIC_AUTH_PASSWORD=supersecret Now the server challenges every request, the browser handles credential entry, and once the user enters their creds the browser caches them for the session. No users[] block needed in conf.yml. Suitable for: single-user setups, machine-to-machine setups (e.g. monitoring or CI hitting /healthz), anywhere a basic-auth header is more convenient than a login page. Not suitable for: multi-user setups where you want admin/normal role separation. Static credentials are one shared identity. If your SPA needs to make API calls from JavaScript (most widgets do), you may also want to bake the credentials into the frontend bundle so the SPA can authenticate without bothering the user every page load. See Auto-Authenticating the Frontend for that. warning Do not combine BASIC_AUTH_* with conf.yml users. If both are set, Dashy prints a warning at startup. The server will reject the SPA's bearer-token requests (because it's expecting the static basic-auth password, not the user's session token), and you'll see auth failures all over the place. Pick one approach. To get conf.yml users with server-side enforcement, use ENABLE_HTTP_AUTH=true, not the static env vars.","s":"Static credentials (no Dashy login page)","u":"/docs/authentication/built-in","h":"#static-credentials-no-dashy-login-page","p":109},{"i":130,"t":"The session cookie's lifetime is picked at login time, not in config. The login form has a dropdown: Don't remember me - session cookie, dies when the browser tab closes Remember me for - choose either: 1 hour, 1 day, 1 week or forever The cookie's expires attribute is set accordingly. There's no admin-level \"force users to re-auth every N hours\" knob in conf.yml; if you want shorter sessions, that's up to the user picking from the dropdown. To log out, open the Config Menu in the top-right and click Logout. The cookie is cleared and the user is bounced back to the login screen.","s":"Sessions, Guest Access & Logout","u":"/docs/authentication/built-in","h":"#sessions-guest-access--logout","p":109},{"i":132,"t":"To let visitors browse the dashboard read-only without logging in, add: appConfig: auth: enableGuestAccess: true users: - user: alicia hash: 5994471ABB01112AFCC18159F6CC74B4F511B99806DA59B3CAF5A9C173CACFC5 type: admin Guests see whatever items aren't explicitly hidden from them (see next section). They can't enter edit mode, save config, or trigger admin-only actions. The login page gets a Continue as Guest button. Guest mode requires auth.users to also be set. With no users defined, auth is off entirely and the dashboard is open to everyone.","s":"Guest access","u":"/docs/authentication/built-in","h":"#guest-access","p":109},{"i":134,"t":"Two type values exist: admin and normal. Plus there's the implicit \"guest\" identity when enableGuestAccess is on. Different parts of the UI react differently to each: Capability Admin Normal user Guest Load the dashboard ✅ yes ✅ yes ✅ yes (if guest access on) Edit config in the UI ✅ yes ✅ yes (in memory only) ❌ no Save config to disk ✅ yes ❌ no ❌ no See the Logout button ✅ yes ✅ yes n/a (shows Login) A few appConfig flags control who can change config, and how: disableConfigurationForNonAdmin: true - hides the entire Config Menu from normal users and guests, leaving the dashboard view-only disableConfiguration: true - disables all config UI for everyone, including View Config and admins preventWriteToDisk: true - prevents any user from writing config changes to disk preventLocalSave: true - prevents config changes from being saved in the browser's local storage See appConfig for the full list of options.","s":"User Roles & Visibility","u":"/docs/authentication/built-in","h":"#user-roles--visibility","p":109},{"i":136,"t":"Beyond global roles, each section and each item can opt in or out of visibility for different identities via the displayData block. Useful for showing certain links only to certain users, or hiding sensitive sections from guests. sections: - name: Admin tools displayData: hideForGuests: true hideForUsers: [bob, charlie] items: - title: Server logs url: https://logs.example.com displayData: showForUsers: [alicia] The full set: hideForGuests - Hide from anyone in guest mode hideForUsers - List of usernames who should not see this showForUsers - List of usernames who exclusively see this (everyone else is hidden) These work on sections and items, and on whole pages via a page's displayData: pages: - name: Home Lab path: home-lab.yml displayData: showForUsers: [alicia] - name: Intranet path: intranet.yml displayData: hideForGuests: true Visibility rules are UI-only. Hiding an item from a user doesn't strip the destination URL from the served conf.yml if ENABLE_HTTP_AUTH is off and they fetch it directly. Don't use displayData as a substitute for actually-private resources.","s":"Per-section and per-item visibility","u":"/docs/authentication/built-in","h":"#per-section-and-per-item-visibility","p":109},{"i":138,"t":"For BASIC_AUTH_* mode specifically, the SPA can be configured to send the basic-auth Authorization header on every request automatically, so the user never sees the browser's native prompt. Set these at build time: VITE_APP_BASIC_AUTH_USERNAME=alicia VITE_APP_BASIC_AUTH_PASSWORD=supersecret Then rebuild Dashy (yarn build). The credentials are baked into the JS bundle. This is obviously for demo purposes only, or for use on a trusted private network where the alternative is no auth at all, but it does mean anyone with access to the bundle (i.e. anyone who can reach your Dashy instance unauthenticated, which shouldn't be anyone if you've configured this right) could extract the credentials. The official lissy93/dashy Docker image is pre-built, so this requires self-building. With ENABLE_HTTP_AUTH=true mode (conf.yml users), the SPA handles auth automatically via the session-cookie token, so these env vars aren't needed.","s":"Auto-Authenticating the Frontend","u":"/docs/authentication/built-in","h":"#auto-authenticating-the-frontend","p":109},{"i":140,"t":"Restart and \"Invalid user object\" appears in the server logs​ Problem: Dashy logs Invalid user object. Must have user and either a hash or password param. Solution: YAML parsed your hash: value as a number rather than a string, usually because every character in the hash happens to be a digit (0000...). Quote it: hash: \"0000...\". Login fails with the right password​ Problem: Username + password are correct but the login form says \"Incorrect password\". Solution: Check the hash. The most common cause is using a hashing tool that adds a trailing newline; on Linux/macOS use echo -n (with -n) or printf '%s' 'password' | sha256sum. You can confirm by computing the hash twice with and without -n and seeing if either matches users[].hash. Browser keeps prompting for basic auth on every page load​ Problem: Using BASIC_AUTH_* mode, the browser prompts on every refresh. Solution: This usually means the SPA is making API requests that don't include credentials, so the server 401s and the browser re-prompts. Either set VITE_APP_BASIC_AUTH_* and rebuild (Auto-Authenticating the Frontend), or switch to ENABLE_HTTP_AUTH=true with conf.yml users for a cleaner UX. Set ENABLE_HTTP_AUTH=true and nothing changed​ Problem: ENABLE_HTTP_AUTH=true is set but API endpoints are still unauthenticated. Solution: ENABLE_HTTP_AUTH only takes effect when auth.users is also set in conf.yml. With no users, the flag silently does nothing (no users means no one can authenticate). Add at least one user to auth.users and restart. Got the warning \"BASIC_AUTH env vars and appConfig.auth.users are both set\"​ Problem: Server logs a yellow warning at startup. Solution: You've got both BASIC_AUTH_* env vars and auth.users in conf.yml. These don't play together. Pick one: remove the env vars and set ENABLE_HTTP_AUTH=true to enforce conf.yml users server-side, or remove auth.users to use static creds only. Logged in as admin but Save Config is still disabled​ Problem: The user logs in fine, but the save button is greyed out. Solution: Confirm type: admin (lowercase) is set on the user in conf.yml. Default is normal. Also check disableConfigurationForNonAdmin is not unexpectedly applying to your account (it shouldn't if type: admin, but worth checking). Guest can still see hidden items via /conf.yml​ Problem: Hidden a section with displayData.hideForGuests but a guest can read it via curl https://dashy/conf.yml. Solution: displayData is a UI control, not server-side filtering. To gate conf.yml itself, you need server enforcement (ENABLE_HTTP_AUTH=true), which will require auth before serving the raw YAML. With auth enforced, an unauthenticated request gets a stripped version of the config (or 401, depending on guest mode setting). Want to keep passwords out of conf.yml entirely​ Problem: You don't want password hashes (even hashed) sitting in your config file. Solution: Use the password: VITE_APP_FOO form to reference an env var. Just note that env-var users only work in the frontend if the env var was present at build time; the official Docker image is pre-built. For runtime env vars to take effect on the frontend, you need to self-build. Auth works locally but not behind a reverse proxy​ Problem: Login works when hitting Dashy directly, but breaks behind nginx/Caddy/etc. Solution: Reverse proxies sometimes strip the Authorization header or mishandle Set-Cookie. Make sure your proxy is configured to pass Authorization through, and that Set-Cookie from Dashy reaches the browser unmodified. For nginx, proxy_pass_request_headers on; is the default, but custom proxy_set_header blocks can override it.","s":"Troubleshooting common built-in auth issues","u":"/docs/authentication/built-in","h":"#troubleshooting-common-built-in-auth-issues","p":109},{"i":142,"t":"A few things to be honest about. Built-in auth was designed for convenience on private networks, not as a hardened identity system. Concretely: Hashing is SHA-256, not bcrypt or scrypt. SHA-256 is fast, which is great for verifying integrity and bad for storing passwords. If conf.yml leaks, those hashes are crackable in seconds for any password short of high-entropy random. Don't reuse passwords across services Client-side only mode is bypassable. Without ENABLE_HTTP_AUTH=true, the login page is a UI overlay. Anyone can fetch /conf.yml, /system-info, /cors-proxy, etc. directly. For anything past \"keep my kids out of the dashboard\", set ENABLE_HTTP_AUTH=true No password reset, no lockout, no rate limiting. A determined attacker can hammer the login endpoint. Front Dashy with a proxy that does rate limiting if you care No 2FA, no SSO, no audit log. If you need any of those, use OIDC. Keycloak, Authentik, Authelia, and others integrate cleanly Anything internet-facing should not rely on built-in auth alone. Put Dashy behind Cloudflare Tunnel + Access, Tailscale, or a real identity provider, and treat built-in auth as defence-in-depth, not the only gate The right way to think about built-in auth: it's a soft barrier for trusted networks. For anything else, lean on a proper auth layer in front.","s":"Security Considerations","u":"/docs/authentication/built-in","h":"#security-considerations","p":109},{"i":145,"t":"The token is SHA-256(uppercase(username) + uppercase(password_hash)), stored as a cookie. On every page load, the SPA reads the cookie back, recomputes the token from each configured user, and looks for a match. If found, the user is \"logged in\" client-side. See src/utils/auth/Auth.js.","s":"The login flow (client-side mode)","u":"/docs/authentication/built-in","h":"#the-login-flow-client-side-mode","p":109},{"i":147,"t":"With ENABLE_HTTP_AUTH=true, services/app.js installs an express-basic-auth middleware in front of every API endpoint and every .yml file. The middleware's custom authorizer takes the bearer token, walks the users[] list, computes the expected token for each user (SHA-256(user.user.toUpperCase() + user.hash.toUpperCase())), and returns true if any match. The bearer token the SPA sends is the same SHA-256-of-user-plus-hash that's stored client-side as a cookie. There's no signature, no JWT, no expiry on the server side; the server just verifies the cookie-derived token reproduces under one of the configured user entries. For BASIC_AUTH_* mode, the same middleware is installed but with { users: { [username]: password } } rather than a custom authorizer. The browser handles the credential prompt natively via WWW-Authenticate: Basic. End-to-end flow with ENABLE_HTTP_AUTH=true","s":"Server-enforced mode","u":"/docs/authentication/built-in","h":"#server-enforced-mode","p":109},{"i":149,"t":"Dashy works well behind Cloudflare Tunnel with Cloudflare Access doing auth at the edge. This is one of the most common ways to expose a homelab dashboard to the public internet without opening any inbound ports, without managing TLS, and without standing up your own identity provider. The flow: Cloudflare authenticates the user (via Google, GitHub, email OTP, etc.) at the edge, signs an identity into request headers, and forwards the request to a cloudflared daemon you run on your network. cloudflared proxies to Dashy. Dashy reads the email from the header and matches the user against its config.","s":"Cloudflare Tunnel","u":"/docs/authentication/cloudflare-tunnel","h":"","p":148},{"i":151,"t":"1. How it fits together 2. Create the tunnel in Cloudflare 3. Run cloudflared 4. Add a Cloudflare Access policy 5. Configure Dashy 6. Best practices Troubleshooting How it Works","s":"Contents","u":"/docs/authentication/cloudflare-tunnel","h":"#contents","p":148},{"i":153,"t":"Three Cloudflare pieces, plus Dashy on your side: Cloudflare Tunnel exposes Dashy to the public internet over an outbound-only connection. No port forwarding, no inbound firewall rules Cloudflare Access sits in front of the tunnel and enforces an identity policy. Anyone reaching the tunnel has already authenticated to Cloudflare cloudflared is the daemon you run alongside Dashy. It dials out to Cloudflare and forwards authenticated requests to Dashy's local port Dashy's header auth reads the email Cloudflare sets in Cf-Access-Authenticated-User-Email and matches it to a user You need: A Cloudflare account A domain on Cloudflare (any TLD, free plan is fine) Docker (or any way to run cloudflared) Free tier covers up to 50 Access users","s":"1. How it fits together","u":"/docs/authentication/cloudflare-tunnel","h":"#1-how-it-fits-together","p":148},{"i":155,"t":"Cloudflare's dashboard handles most of this. Go to Zero Trust dashboard Open Networks > Tunnels Click Create a tunnel Pick Cloudflared as the connector, click Next Name it (e.g. homelab), click Save tunnel Copy the tunnel token shown on the install screen. You'll paste this into Docker compose in the next step. Keep it secret, it grants tunnel access Click Next without installing the package on the host (Docker is easier) Add a Public hostname: Subdomain: e.g. dashy Domain: your Cloudflare domain Type: HTTP URL: dashy:8080 (the in-network service name + port Dashy listens on) Click Save Cloudflare now routes https://dashy.example.com through the tunnel to your cloudflared daemon, which forwards to dashy:8080 on whatever Docker network they share.","s":"2. Create the tunnel in Cloudflare","u":"/docs/authentication/cloudflare-tunnel","h":"#2-create-the-tunnel-in-cloudflare","p":148},{"i":157,"t":"A minimal compose with Dashy + cloudflared together: Example docker-compose.yml name: dashy-cfaccess services: cloudflared: image: cloudflare/cloudflared:latest restart: unless-stopped command: tunnel --no-autoupdate run environment: TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN} depends_on: dashy: condition: service_healthy dashy: image: lissy93/dashy:4.1.5 restart: unless-stopped environment: NODE_ENV: production HOST: 0.0.0.0 PORT: 8080 volumes: - ./user-data:/app/user-data healthcheck: test: [\"CMD-SHELL\", \"wget -qO- http://127.0.0.1:8080/healthz >/dev/null 2>&1\"] interval: 10s timeout: 5s retries: 10 start_period: 30s Set the token via env or a .env file alongside the compose: CLOUDFLARE_TUNNEL_TOKEN=eyJhIjoiMDF...rest-of-token Bring it up: docker compose up -d Notice what's not in the compose: Dashy has no ports: mapping. It's only reachable from inside the Docker network, which means only cloudflared can talk to it. That's the first half of the security model.","s":"3. Run cloudflared","u":"/docs/authentication/cloudflare-tunnel","h":"#3-run-cloudflared","p":148},{"i":159,"t":"Without Access, the tunnel is open to anyone who knows the URL. Add a policy so Cloudflare enforces auth at the edge. In Zero Trust, open Access > Applications Click Add an application Choose Self-hosted Set Application name to Dashy Set Session duration to whatever feels right (e.g. 24 hours) Under Application domain, pick the subdomain and domain you used in step 2 (dashy.example.com) Click Next Add a policy: Policy name: Trusted users Action: Allow Include: Emails ending in @example.com, or Emails with specific addresses, or Everyone for open access Optionally add a second policy for admins with stricter rules Click Next and Add application Cloudflare now bounces anyone visiting https://dashy.example.com to its login page first, only forwarding the request after auth.","s":"4. Add a Cloudflare Access policy","u":"/docs/authentication/cloudflare-tunnel","h":"#4-add-a-cloudflare-access-policy","p":148},{"i":161,"t":"In /user-data/conf.yml: appConfig: ... disableConfigurationForNonAdmin: true auth: enableHeaderAuth: true users: - user: admin@example.com hash: \"0000000000000000000000000000000000000000000000000000000000000000\" type: admin - user: user@example.com hash: \"0000000000000000000000000000000000000000000000000000000000000000\" type: normal headerAuth: userHeader: Cf-Access-Authenticated-User-Email proxyWhitelist: - 172.18.0.2 Where: enableHeaderAuth - Turns on header auth mode users - Required. The email the proxy sends must match user here for the user to be admitted. The type field controls admin status in Dashy hash - Required by Dashy's user schema even though the password is never checked under header auth. Quote it to stop YAML parsing the all-zero placeholder as the number 0 userHeader - The header Cloudflare Access sets after a user authenticates. Matches case-insensitively proxyWhitelist - Required. The IP cloudflared connects from. Find it with docker compose exec dashy getent hosts cloudflared or check docker network inspect. This is the single biggest control: requests from any other IP get 401 before headers are even read Restart Dashy after editing. Open https://dashy.example.com. Cloudflare bounces you to its login page, you authenticate, and you land on the Dashy dashboard auto-logged-in as that email.","s":"5. Configure Dashy","u":"/docs/authentication/cloudflare-tunnel","h":"#5-configure-dashy","p":148},{"i":163,"t":"Don't bind Dashy's port to the host. Only cloudflared should reach it, which is the case if you leave the ports: block out of the dashy service Treat the tunnel token as a credential. Anyone with it can connect a cloudflared to your tunnel and impersonate it Set proxyWhitelist to the smallest possible set of IPs. For Docker compose that's typically just the one cloudflared container address Pin cloudflared to a version tag in production rather than :latest. Cloudflare updates the daemon often Configure Access policies before public release. A tunnel without an Access policy is wide open Use Access groups in the dashboard for \"who is an admin\" rather than relying solely on Dashy's type: admin, so identity changes don't require redeploying Dashy For machine-to-machine access (CI, healthchecks, monitoring), use Access Service Tokens and a separate bypass policy keyed on the token. Don't try to share user credentials If Dashy and cloudflared aren't on the same Docker network, force them on one with an explicit networks: block","s":"6. Best practices","u":"/docs/authentication/cloudflare-tunnel","h":"#6-best-practices","p":148},{"i":165,"t":"401 Unauthorized from Dashy after the Cloudflare login screen​ Problem: You authenticate at Cloudflare, get redirected to Dashy, but see \"Unauthorized - not from trusted proxy\". Solution: proxyWhitelist doesn't include cloudflared's container IP. Run docker compose exec dashy getent hosts cloudflared to find it, paste that IP into proxyWhitelist, restart Dashy. \"Unauthorized - missing user header\"​ Problem: Server logs show that header auth ran but the user header was empty. Solution: The request reached Dashy from a trusted IP but without Cf-Access-Authenticated-User-Email. This usually means the tunnel doesn't have an Access policy in front of it, so traffic is reaching Dashy unauthenticated. Open the Access application in Zero Trust and confirm there's at least one policy attached. User logs in but admin features stay locked​ Problem: You authenticate, the dashboard renders, but Save Config and other admin actions are disabled. Solution: The email Cloudflare sends must exactly match a users[].user entry with type: admin. Check the JWT in Cf-Access-Jwt-Assertion (decode at jwt.io) to see the actual email Cloudflare is sending. Often a personal Google account vs work account mismatch. Loop or repeated redirects to the Cloudflare login page​ Problem: Each refresh bounces back through Cloudflare's login. Solution: Session cookies are being blocked or stripped. Check that the tunnel's public hostname uses HTTPS (it does by default), that no upstream proxy strips third-party cookies, and that the Access application's session duration is non-zero. Stuck on a cached page after the Access session expires​ Problem: You have the service worker enabled (appConfig.enableServiceWorker), and once your Access session expires Dashy keeps showing a broken cached page instead of redirecting to the Cloudflare login. Solution: Set appConfig.enableAuthProxyCompat: true. Dashy will then detect the expiry on load and reload so Cloudflare can redirect you to log in again. \"Critical Configuration Load Error\" on first load​ Problem: SPA errors out before showing the dashboard. Solution: Usually a CORS or network reachability issue, but for Cloudflare Tunnel this is most often a cloudflared to dashy networking problem inside Docker. docker compose logs cloudflared will show connection attempts. Confirm both services are on the same Docker network and that the tunnel's public hostname URL matches the service name and port (e.g. dashy:8080, not localhost:8080). cloudflared can't connect to the tunnel​ Problem: cloudflared keeps logging connection errors. Solution: Check the token. Tokens are tunnel-specific, and rotating one in the dashboard invalidates the previous one. If you recreated the tunnel, paste the new token into .env and restart. Schema warning \"hash must be string\" on Dashy startup​ Problem: Dashy logs warn about user hashes not being strings. Solution: YAML parsed an all-zeros (or all-digit) hash as a number. Wrap the value in quotes: hash: \"0000...\" rather than hash: 0000.... Want to bypass Cloudflare Access for automation​ Problem: A monitoring or CI agent needs to hit /healthz or scrape Dashy, but Cloudflare Access blocks it. Solution: In the Access application, add a Service Token, then create a Bypass policy keyed on that token. The agent sends Cf-Access-Client-Id and Cf-Access-Client-Secret headers to authenticate as a machine. Don't share user logins.","s":"Troubleshooting common Cloudflare Tunnel issues","u":"/docs/authentication/cloudflare-tunnel","h":"#troubleshooting-common-cloudflare-tunnel-issues","p":148},{"i":168,"t":"When Dashy has enableHeaderAuth: true, services/app.js installs a small middleware in front of every API and asset route. The middleware does two things per request: Check req.socket.remoteAddress against proxyWhitelist. If the source IP isn't in the list, return 401 immediately. The header is never even inspected Read the header named by userHeader (case-insensitive). If empty, return 401. Otherwise set req.auth = { user: } and let the request through The IP check is the security boundary. Cloudflare adds Cf-Access-Authenticated-User-Email only after a successful Access policy match, and cloudflared is the only thing that can talk to Dashy (no exposed port). So the only way that header can be set on a request reaching Dashy is via the Cloudflare to cloudflared to dashy path, after a real Cloudflare Access authentication. The SPA fetches /get-user once on load, which returns whatever email the server saw. The client-side code in src/utils/auth/HeaderAuth.js matches that email against the configured users[] to determine type: admin vs normal, then sets the session cookie. The route guard in src/router.js skips its usual /login bounce when enableHeaderAuth is on, since the SPA can't tell \"not logged in\" from \"header auth still resolving\" and the server is authoritative anyway. Server-side auth middleware​ End-to-end authentication flow","s":"Server side","u":"/docs/authentication/cloudflare-tunnel","h":"#server-side","p":148},{"i":170,"t":"Header authentication lets Dashy trust a reverse proxy in front of it to handle login. The proxy authenticates the user, then passes their username to Dashy in an HTTP header. Dashy reads that header and signs them in automatically, so there's no separate Dashy login page. Use this when you already run something like Authelia, Authentik, Traefik's forwardAuth, Caddy's forward_auth, or nginx's auth_request in front of your services, and you want Dashy to pick up the same session.","s":"Header Authentication","u":"/docs/authentication/header-auth","h":"","p":169},{"i":172,"t":"Configure Dashy Configure your proxy Troubleshooting Security notes How it works","s":"Contents","u":"/docs/authentication/header-auth","h":"#contents","p":169},{"i":174,"t":"In /user-data/conf.yml, set the auth block under appConfig: appConfig: auth: enableHeaderAuth: true users: - user: alice hash: 0a7b1d4c2e... # SHA-256 hash, see below type: admin - user: bob hash: 3f8e2b1a9d... type: normal headerAuth: userHeader: Remote-User proxyWhitelist: - 172.18.0.2 enableHeaderAuth - turns the mode on userHeader - the header holding the username. Defaults to Remote-User. Authelia sends Remote-User, Authentik sends X-authentik-username; use whatever yours forwards. The name is case-insensitive proxyWhitelist - the IP(s) Dashy will accept the header from. Anything not on this list is rejected, which is what stops a client from setting the header itself. Required in practice: if it's empty, nothing gets through users - the people allowed in. The forwarded username is matched (case-insensitive) against this list to find their type (admin or normal). They still need an entry here even though the proxy did the actual auth The hash is a SHA-256 of any string. Nobody types it, since the proxy already logged them in, but Dashy uses it to derive the session token it keeps in the browser, so each user needs one. Generate it the same way as for built-in auth.","s":"Configure Dashy","u":"/docs/authentication/header-auth","h":"#configure-dashy","p":169},{"i":176,"t":"Two things the proxy has to do: Forward the username header on every request to Dashy (e.g. Remote-User: alice). On most forward-auth setups this is a one-line setting Connect to Dashy from an IP that's in proxyWhitelist The whitelist is the security boundary, so Dashy must only be reachable through the proxy. If someone can hit Dashy directly and their IP happens to be whitelisted, they can forge the header. See Security notes.","s":"Configure your proxy","u":"/docs/authentication/header-auth","h":"#configure-your-proxy","p":169},{"i":178,"t":"401 \"Unauthorized - not from trusted proxy\"​ The request reached Dashy from an IP that isn't in proxyWhitelist. The check is on the direct connection IP, so with Docker that's the proxy container's address on the shared network, not the host or the client's IP. Find it with docker inspect (or read the rejected IP from Dashy's logs) and add it. 401 \"Unauthorized - missing user header\"​ The request came from a trusted IP but had no username header. Either the proxy isn't forwarding it, or userHeader doesn't match the header name the proxy actually sends. Check the exact header in your proxy config. \"User '...' from upstream proxy was not found in conf.yml\"​ The proxy sent a username with no matching entry in auth.users. Add a user whose user matches it (case doesn't matter). Logged in but can't save the config​ That user's type isn't admin. Saving is admin-only, so set type: admin on their entry. Logging out just logs me back in​ Expected. Logout clears Dashy's cookie, but you're still signed in at the proxy, so the next page load re-authenticates from the header. To fully sign out, log out at the proxy.","s":"Troubleshooting","u":"/docs/authentication/header-auth","h":"#troubleshooting","p":169},{"i":180,"t":"Header auth is only as strong as the proxy in front of it. Dashy trusts any whitelisted IP that sends the header, so the whole model depends on Dashy being unreachable except through the proxy. Lock that down at the network or firewall level Keep proxyWhitelist tight: just the proxy's real connecting IP(s), nothing broader The username and role come from conf.yml. A user the proxy authenticates but that you haven't listed is refused, so removing someone from auth.users blocks them even if the proxy still lets them through","s":"Security notes","u":"/docs/authentication/header-auth","h":"#security-notes","p":169},{"i":182,"t":"The user hits Dashy through the proxy. The proxy authenticates them and adds the username header On load, Dashy's frontend calls /get-user. The server checks the request IP against proxyWhitelist, and if it's trusted, reads the username from userHeader and returns it The frontend looks that username up in auth.users, derives a session token from the user and hash, sets the auth cookie, and stores the username From there it's normal Dashy auth: isLoggedIn, the admin check, and the per-page, section and item visibility rules all work as usual Server-side, the same proxy-whitelist middleware guards the config and API routes, so a request from an untrusted IP is rejected before it reaches anything. Writes (saving config) additionally require the matched user to be type: admin. The relevant code is src/utils/auth/HeaderAuth.js on the frontend, and services/app.js with services/get-user.js on the server.","s":"How it works","u":"/docs/authentication/header-auth","h":"#how-it-works","p":169},{"i":184,"t":"Dashy supports using a Keycloak (V17+) authentication server. Keycloak is a Java-based open source, high-performance, secure authentication system, supported by RedHat. It can be deployed with Docker, and enables you to secure multiple self-hosted applications with single-sign-on using standard protocols (OpenID Connect, OAuth 2.0, SAML 2.0 and social login).","s":"Keycloak","u":"/docs/authentication/keycloak","h":"","p":183},{"i":186,"t":"1. Deploy Keycloak 2. Configure Keycloak 3. Enabling Keycloak in Dashy 4. Groups and Roles Troubleshooting How it Works","s":"Contents","u":"/docs/authentication/keycloak","h":"#contents","p":183},{"i":188,"t":"If you've not already done so, spin up a Keycloak instance. You can do this by following the Keycloak Docs, or use the following Docker examples: docker run -d \\ -p 9100:8080 \\ --name keycloak \\ -e KEYCLOAK_ADMIN=kc-admin \\ -e KEYCLOAK_ADMIN_PASSWORD=KeycloakAdmin2026! \\ quay.io/keycloak/keycloak:25.0 start-dev Example docker-compose.yml KEYCLOAK_ADMIN=kc-admin KEYCLOAK_ADMIN_PASSWORD=KeycloakAdmin2026! name: dashy-keycloak services: keycloak: image: quay.io/keycloak/keycloak:25.0 command: - start-dev - --http-port=9100 - --hostname-strict=false - --health-enabled=true restart: unless-stopped ports: - \"9100:9100\" - \"4000:8080\" volumes: - keycloak-data:/opt/keycloak/data environment: KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} KC_HTTP_ENABLED: \"true\" KC_HOSTNAME_STRICT: \"false\" KC_HEALTH_ENABLED: \"true\" healthcheck: test: [\"CMD-SHELL\", \"timeout 2 bash -c '/dev/null 2>&1\"] start_period: 30s interval: 10s timeout: 5s retries: 15 volumes: keycloak-data: You should now be able to access the Keycloak web interface at http://127.0.0.1:9100, log in with your admin credentials above, and create a new password when prompted.","s":"1. Deploy Keycloak","u":"/docs/authentication/keycloak","h":"#1-deploy-keycloak","p":183},{"i":191,"t":"Open http://localhost:9100. Click Administration Console. Log in as kc-admin. Open the realm selector in the top-left. Click Create realm. Set Realm name to dashy. Click Create.","s":"Create the Keycloak realm","u":"/docs/authentication/keycloak","h":"#create-the-keycloak-realm","p":183},{"i":193,"t":"In the dashy realm, open Realm settings. Open Security defenses. Open Headers. Clear X-Frame-Options. Set Content-Security-Policy to: frame-src 'self' http://localhost:4000 http://127.0.0.1:4000; frame-ancestors 'self' http://localhost:4000 http://127.0.0.1:4000; object-src 'none'; Click Save. If Dashy and Keycloak are served from the same origin in production, you can skip this step. Clearing X-Frame-Options and allow-listing origins is only needed when Dashy frames Keycloak's check-sso iframe across origins, as in this localhost setup.","s":"Allow Dashy's local origin","u":"/docs/authentication/keycloak","h":"#allow-dashys-local-origin","p":183},{"i":195,"t":"In the dashy realm, open Clients. Click Create client. Set Client type to OpenID Connect. Set Client ID to dashy. Click Next. Turn Client authentication off. Dashy is a SPA using PKCE, so it must be a public client. Leave Standard flow on. Leave Direct access grants on or off; Dashy does not require it. Click Next. Set Valid redirect URIs to: http://localhost:4000/* http://127.0.0.1:4000/* Set Valid post logout redirect URIs to: http://localhost:4000/* http://127.0.0.1:4000/* Set Web origins to: http://localhost:4000 http://127.0.0.1:4000 Click Save.","s":"Create the Dashy client","u":"/docs/authentication/keycloak","h":"#create-the-dashy-client","p":183},{"i":197,"t":"Dashy uses adminRole: dashy-admin in user-data/conf.yml. For server-side admin checks to work, Keycloak must include realm roles in the ID token Open Clients Click the dashy client Open Client scopes Click the dedicated scope row, usually named dashy-dedicated Click Add mapper Click By configuration Select User Realm Role Set Name to dashy-realm-roles Set Token Claim Name to realm_access.roles Turn Multivalued on Turn Add to ID token on Turn Add to access token on Turn Add to userinfo on Click Save","s":"Add the realm-role mapper","u":"/docs/authentication/keycloak","h":"#add-the-realm-role-mapper","p":183},{"i":199,"t":"In the dashy realm, open Realm roles Click Create role Set Role name to dashy-admin Click Save","s":"Create the Dashy admin role","u":"/docs/authentication/keycloak","h":"#create-the-dashy-admin-role","p":183},{"i":201,"t":"To grant admin via group membership instead of a realm role, add a second mapper of type Group Membership with claim name groups, create the group under Groups, assign your admin users to it, then set adminGroup: (in place of adminRole) in your Dashy config later.","s":"(Alternative) Use a group for admin","u":"/docs/authentication/keycloak","h":"#alternative-use-a-group-for-admin","p":183},{"i":203,"t":"On Keycloak 25 and newer, First name and Last name are required by the default user-profile schema. Skip them and the user can authenticate, but login then fails with \"Account is not fully set up\". The steps below set both. Create an admin user: Open Users Click Add user Set Username to keycloak-admin Set Email verified to on Set First name to Keycloak Set Last name to Admin Click Create Open the Credentials tab Click Set password Set a password, turn Temporary off, and save it Open the Role mapping tab Click Assign role Filter by realm roles, select dashy-admin, and assign it Create a normal user: Open Users Click Add user Set Username to keycloak-user Set Email verified to on Set First name to Keycloak Set Last name to User Click Create Open the Credentials tab Set a non-temporary password Do not assign dashy-admin","s":"Create test users","u":"/docs/authentication/keycloak","h":"#create-test-users","p":183},{"i":205,"t":"Keycloak should now be configured, and ready to go! The Keycloak UI is not super intuitive, so if you're struggling to find where to configure any of the above options, below is a full start-to-end walkthrough video: https://github.com/user-attachments/assets/12b6a596-1ec6-453a-9ff7-d4e2c3aa69f7 If you need to, you can make a backup of your Keycloak config, with their built-in backup tool. Something like: docker compose run --rm --no-deps \\ -v ./backup:/backup \\ keycloak export \\ --realm dashy \\ --dir /backup \\ --users realm_file \\ --optimized Here's an example of a configured dashy-realm.json backup.","s":"Summary","u":"/docs/authentication/keycloak","h":"#summary","p":183},{"i":207,"t":"Finally, you need to tell Dashy to use the new Keycloak setup, and only allow access from authorized Keycloak users. This is done in the appConfig.auth section of your main /user-data/conf.yml file. As an example, you can view this conf.yml, which is fully-configured with Keycloak auth using the info from the above steps. appConfig: ... disableConfigurationForNonAdmin: true auth: enableKeycloak: true keycloak: serverUrl: http://localhost:9100 realm: dashy clientId: dashy adminRole: dashy-admin Where: disableConfigurationForNonAdmin - Prevent read/write config access to non-admin Keycloak users auth.enableKeycloak - Set the auth mode to Keycloak serverUrl - The host (no paths) to your Keycloak instance, accessible from the Dashy container realm - The name (case sensitive) of the Keycloak realm to use clientId - Client ID that you created for Dashy adminRole - The role name that grants admin adminGroup - (Alternative to adminRole) Group name that grants admin idpHint - (Optional) Alias of an external IdP federated through Keycloak; skips the Keycloak login page and redirects straight to that provider Note that a restart is required for these changes to take effect. If Keycloak runs on a different host or behind a reverse proxy, make sure serverUrl is reachable from inside the Dashy container, and that the realm's redirect URIs and Web Origins match Dashy's public URL. Everything should now be fully configured and working 🎉 Now, when you load Dashy, you'll be redirected to your Keycloak login page, after logging in you will then land back on Dashy's homepage with full access! Until you're authenticated, Dashy's config and API endpoints return a 401 (a write attempt by a non-admin returns a 403).","s":"3. Enabling Keycloak in Dashy","u":"/docs/authentication/keycloak","h":"#3-enabling-keycloak-in-dashy","p":183},{"i":209,"t":"Keycloak allows you to assign users roles and groups. Dashy supports using these roles, to configure who can access various sections or items in Dashy's frontend. For example, to make any given section only visible to admins, simply add the following to the section's displayData section: displayData: showForKeycloakUsers: roles: - dashy-admin # ID of the admin role you created earlier Keycloak server administration and configuration is a deep topic; please refer to the server admin guide to see details about creating and assigning roles and groups. Once you have groups or roles assigned to users you can configure access under each section or item displayData.showForKeycloakUsers and displayData.hideForKeycloakUsers. Both show and hide configurations accept a list of groups and roles that limit access. If a users data matches one or more items in these lists they will be allowed or excluded as defined. sections: - name: DeveloperResources displayData: showForKeycloakUsers: roles: ['canViewDevResources'] hideForKeycloakUsers: groups: ['ProductTeam'] items: - title: Not Visible for developers displayData: hideForKeycloakUsers: groups: ['DevelopmentTeam']","s":"4. Groups and Roles","u":"/docs/authentication/keycloak","h":"#4-groups-and-roles","p":183},{"i":211,"t":"Client Authentication Issue​ Problem: Redirect loop, if client authentication is enabled. Solution: Switch off \"Client authentication\" in the dashy client's \"Advanced\" settings. Double URL​ Problem: If you get redirected to \"https://dashy.my.domain/#iss=https://keycloak.my.domain/realms/dashy\" Solution: Turn on \"Exclude Issuer From Authentication Response\" in the dashy client's \"Advanced\" -> \"OpenID Connect Compatibility Modes\". Problems with multiple Dashy Pages​ Problem: Refreshing or logging out of dashy results in an \"invalid_redirect_uri\" error. Solution: In the dashy client's \"Access settings\", set \"Root URL\" to https://dashy.my.domain/, and make sure the valid redirect URIs end in /*. 403 on login-status-iframe.html/init​ Problem: Browser console shows a 403 from Keycloak when the SPA loads. Solution: Open the dashy client's \"Web origins\" and remove any trailing /*. Web Origins must be bare origins (e.g. http://localhost:4000), not http://localhost:4000/*. CSP error for /3p-cookies/step1.html or \"Authentication failed (Keycloak)\"​ Problem: The hidden Keycloak iframe is blocked by frame-ancestors. Solution: In the dashy realm (not master), open Realm settings -> Security defenses -> Headers. Clear X-Frame-Options and set the Content-Security-Policy as described earlier in this section. Dashy server can't reach Keycloak​ Problem: SPA loads fine but every authenticated API call returns 401, and the Dashy server logs show [auth-oidc] token verification failed or fetch errors for .well-known/openid-configuration. Solution: serverUrl must be reachable from inside the Dashy container, not just from the browser. In Docker, put both services on the same network and use the service name (e.g. http://keycloak:8080). Test with docker exec wget -qO- \"$SERVER_URL/realms/dashy/.well-known/openid-configuration\". Logged in but config saves return 403​ Problem: User authenticates fine, but saving the dashboard returns 403. Solution: The id_token doesn't carry the admin claim. Confirm the Add to ID token toggle on the Step 2 mapper is on. Paste the token (from localStorage, key ID_TOKEN) into jwt.io and look for realm_access.roles (or groups if you're using adminGroup). Issuer mismatch behind a reverse proxy​ Problem: Server logs show unexpected \"iss\" claim value. The browser reaches Keycloak over HTTPS but Keycloak advertises an HTTP issuer in its discovery document. Solution: Set KC_HOSTNAME= on Keycloak so the issuer matches the public URL, and ensure your reverse proxy forwards X-Forwarded-Proto: https. Audience mismatch on token verification​ Problem: Server logs show unexpected \"aud\" claim value. Every auth'd API call returns 401. Solution: clientId in conf.yml must exactly match the Keycloak client's Client ID. Check for trailing whitespace, case mismatches, or accidentally using the client's internal UUID instead of the Client ID string. Self-signed Keycloak certificate rejected​ Problem: Dashy server logs show TLS errors (self-signed certificate, UNABLE_TO_VERIFY_LEAF_SIGNATURE) when fetching JWKS or discovery. Solution: Use a real certificate on Keycloak (Let's Encrypt, or your homelab CA), or mount your CA bundle into the Dashy container and set NODE_EXTRA_CA_CERTS=/path/to/ca.pem. Token expired / clock skew​ Problem: 401s with \"exp\" claim timestamp check failed or \"iat\" claim timestamp check failed, even just after login. Solution: Dashy allows 30 seconds of drift. Sync clocks on both hosts with NTP. Container clocks follow their host, so it's almost always the host that's drifted. Mixed content blocked by the browser​ Problem: Dashy served over HTTPS, Keycloak over HTTP. The browser blocks the token or JWKS endpoint with a mixed-content error. Solution: Terminate both behind HTTPS. For local testing, use HTTP on both, but never mix schemes in the same flow. Numeric Client ID truncated​ Problem: Token verification fails with audience mismatch when clientId in conf.yml is a long numeric string. Solution: Wrap numeric Client IDs in quotes (e.g. clientId: \"12345678901234567\"). Without quotes YAML parses the value as a JS number and loses precision past about 15 digits. Logout lands on a broken page​ Problem: Clicking Logout in Dashy ends on a Keycloak error or 404 instead of returning to Dashy. Solution: Add Dashy's URL to Valid post logout redirect URIs on the dashy client. Keycloak v18+ requires registered, exact-match post-logout URIs. check-sso hangs in strict browsers​ Problem: First load spins indefinitely. Browser console reports blocked third-party cookies. Common in Safari, Brave, or Firefox with strict tracking protection. Solution: Serve Dashy and Keycloak under the same registrable domain (e.g. dashy.example.com and auth.example.com) so the session cookie is first-party. Config change to auth.keycloak not picked up​ Problem: Updated serverUrl, realm, clientId, adminRole, or adminGroup in conf.yml, but Dashy still authenticates against the old values. Solution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under auth.keycloak.","s":"Troubleshooting common Keycloak Issues","u":"/docs/authentication/keycloak","h":"#troubleshooting-common-keycloak-issues","p":183},{"i":213,"t":"If you're a developer or contributor looking to understand or make changes to Dashy's Keycloak implementation, the following outlines how it's wired together. The same OIDC pipeline backs both Keycloak and generic OIDC providers. The only Keycloak-specific code is the SPA adapter, which uses keycloak-js so it can take advantage of check-sso and silent token renewal.","s":"How it Works","u":"/docs/authentication/keycloak","h":"#how-it-works","p":183},{"i":215,"t":"Boot starts in src/main.js. After the initial /conf.yml fetch parses the auth block, isKeycloakEnabled() decides whether to lazily import keycloak-js and call initKeycloakAuth(). src/utils/auth/KeycloakAuth.js wraps the adapter. On load it calls keycloakClient.init({ onLoad: 'check-sso', responseMode: 'query' }). If a Keycloak session already exists the user is silently authenticated; otherwise the SPA redirects to the login page with PKCE. On return, storeKeycloakInfo() persists the raw id_token, the user's groups and roles, preferred_username, and a derived isAdmin flag to localStorage, then hard-redirects to / so the SPA boots a second time with the Bearer token in place. src/utils/auth/getApiAuthHeader.js builds the Authorization header for every internal API call. It does a client-side exp check and returns null for missing or expired tokens, so the next request triggers a fresh login rather than a 401. The localStorage keys (ID_TOKEN, KEYCLOAK_INFO, USERNAME, ISADMIN) live in src/utils/config/defaults.js. src/utils/IsVisibleToUser.js reads KEYCLOAK_INFO when evaluating showForKeycloakUsers and hideForKeycloakUsers rules.","s":"Client side","u":"/docs/authentication/keycloak","h":"#client-side","p":183},{"i":217,"t":"services/auth-oidc.js contains the entire server-side auth surface, in five small pieces: loadOidcSettings() reads auth.keycloak (or auth.oidc) at boot and returns a normalised { issuer, clientId, adminGroup, adminRole }. For Keycloak the issuer is /realms/. createOidcMiddleware() returns a Connect middleware. Permissive on no-token requests so the SPA can bootstrap; otherwise it verifies the Bearer token against the realm's JWKS using jose. Checks cover signature, issuer (against the canonical value from the discovery doc), audience (must equal clientId), and expiry, with a 30-second clock-skew tolerance. Sets req.auth = { user, isAdmin, claims } on success, 401 on failure. getIssuerContext() lazily fetches .well-known/openid-configuration on first use and wraps jwks_uri in createRemoteJWKSet, which handles JWKS caching and on-demand key rotation. The result is memoised per-issuer for the life of the process. deriveIsAdmin() checks the token's groups claim against adminGroup, and the union of realm_access.roles and resource_access..roles against adminRole. Either match returns true. maybeBootstrapConfig() is the stripped-response helper. When auth is configured, guest access is off, and an unauthenticated request hits the root /conf.yml, it returns a minimal copy with only appConfig.auth, appConfig.enableServiceWorker, and a pageInfo.title of Login | . Sections, items, hostnames and any other secrets never leave the server. services/app.js wires it all together. The middleware mounts as protectConfig in front of every YAML route and config-mutating route. The /*.yml handler sets Cache-Control: private, no-store and Vary: Authorization whenever auth is configured (so intermediate caches can never mix auth states), then calls maybeBootstrapConfig; a stripped result is sent as-is, otherwise res.sendFile serves the full file. POST /config-manager/save is additionally guarded by requireAdmin, which returns 401 if req.auth is unset and 403 if req.auth.isAdmin is false.","s":"Server side","u":"/docs/authentication/keycloak","h":"#server-side","p":183},{"i":219,"t":"The server's admin check reads from the id_token only. Keycloak's default mapper adds realm roles to the access token but not the id token, so without the Step 2 Add to ID token toggle, realm_access.roles is absent and every user is treated as non-admin. The same applies to groups if you use adminGroup instead. This is the single most common cause of \"logged in fine, but can't save changes\".","s":"Why the mapper matters","u":"/docs/authentication/keycloak","h":"#why-the-mapper-matters","p":183},{"i":221,"t":"End-to-end authentication flow Server-side request handling","s":"Visual Overview","u":"/docs/authentication/keycloak","h":"#visual-overview","p":183},{"i":224,"t":"We have some provider-specific guides to walk you through configuring OIDC authentication with common services Authentik Authelia Zitadel Pocket ID Keycloak","s":"Step-by-step Guides","u":"/docs/authentication/oidc","h":"#step-by-step-guides","p":222},{"i":226,"t":"Dashy also supports using a general OIDC compatible authentication server. In order to use it, the authentication section needs to be configured: appConfig: disableConfigurationForNonAdmin: true # Hide the config editor from non-admins (recommended) enableServiceWorker: true # Optional: enables the PWA and offline support enableAuthProxyCompat: true # Recover the PWA after a session expires (needs the service worker) auth: enableOidc: true # Turn OIDC on enableGuestAccess: false # Optional: view the dashboard read-only without signing in oidc: clientId: dashy # Client ID from your provider endpoint: https://auth.example.com/application/o/dashy/ # The issuer URL, not the .well-known one scope: openid profile email groups # Scopes to request (groups for adminGroup, roles for adminRole) adminGroup: dashy-admins # Members of this group are admins adminRole: dashy-admin # Or grant admin by role instead enableSilentRenew: true # Refresh the session in the background before it expires # allowedIssuers: [] # Only for multi-tenant providers to override discovery document Because Dashy is a SPA, a public client registration with PKCE is needed. If you set adminGroup, include groups in scope (e.g. scope: 'openid profile email groups') so your IdP actually returns the claim in the id_token. Same goes for adminRole and a roles scope if your IdP needs one. Note, that if your clientId is numeric, you must place it in quotes. Otherwise YAML parses it as a number, and values longer than JavaScript's safe-integer range (around 15 digits) lose precision, which makes the client ID match fail. For the provider side (registering the client, redirect URIs, scope mappings), follow one of the step-by-step guides above.","s":"About OIDC","u":"/docs/authentication/oidc","h":"#about-oidc","p":222},{"i":228,"t":"Dashy works out who's an admin from the id_token. Set adminGroup to a group name, or adminRole to a role name, and anyone with a matching claim can save changes to the config. The server blocks everyone else from saving anyway, and disableConfigurationForNonAdmin: true also hides the editor from non-admins so the dashboard is view-only for them. The claim has to be in the id_token, not just the access token. Most providers include it once you ask for the groups (or roles) scope. For the group check Dashy reads the groups claim, plus GitLab's groups_direct. For the role check it reads roles, plus Keycloak's realm_access.roles and resource_access..roles. If your admins aren't being picked up, decode the id_token (paste it into jwt.io) and check the claim is there.","s":"Admin access","u":"/docs/authentication/oidc","h":"#admin-access","p":222},{"i":230,"t":"With a multi-tenant provider (e.g. Microsoft Entra's organizations / common endpoints), the issuer in the token doesn't match the one in the discovery document, so verification fails. Set allowedIssuers to the issuer URL(s) you want to accept: oidc: clientId: dashy endpoint: 'https://login.microsoftonline.com/organizations/v2.0/' allowedIssuers: - 'https://login.microsoftonline.com//v2.0' When set, tokens are accepted only if their iss matches one of these (signature, audience and expiry are still checked). Leave it unset for normal single-tenant providers.","s":"Multi-tenant providers","u":"/docs/authentication/oidc","h":"#multi-tenant-providers","p":222},{"i":232,"t":"Set enableGuestAccess: true to let people view the dashboard read-only without signing in. They get the full config but can't save anything, and sections or items marked hideForGuests stay hidden. With it off (the default), anyone who isn't signed in is sent to the login flow.","s":"Guest access","u":"/docs/authentication/oidc","h":"#guest-access","p":222},{"i":234,"t":"If you turn on the service worker for offline use (enableServiceWorker: true), turn on enableAuthProxyCompat: true as well. Without it, when your session expires the cached app can keep showing the old page instead of letting the login redirect through. With it on, Dashy spots the expired session on load, drops the service worker, and reloads so you can sign in again.","s":"Using with a PWA","u":"/docs/authentication/oidc","h":"#using-with-a-pwa","p":222},{"i":236,"t":"Dashy's server reads auth.oidc from conf.yml at boot, lazily fetches the OIDC discovery doc + JWKS from your endpoint, then verifies the id_token the SPA attaches to every API call as Authorization: Bearer . Tokens that fail signature / issuer / audience / expiry verification are rejected with 401. Write endpoints (POST /config-manager/save) additionally require the adminGroup (or adminRole) to be present in the token's groups / roles claims, and non-admins receive 403. Unauthenticated requests for /conf.yml get a stripped response containing only the auth block plus a minimal pageInfo, just enough for the SPA to bootstrap the login flow. The full config is only served to authenticated users. Your IdP must include groups / roles in the id_token, not only the access token, for the admin check to work (most IdPs do this when the groups scope is requested).","s":"How server-side enforcement works","u":"/docs/authentication/oidc","h":"#how-server-side-enforcement-works","p":222},{"i":238,"t":"By default, when your access token expires Dashy sends you back through the provider's login page to get a new one. On a long-lived session this is visible as a brief flash of the sign-out button and a reload. Setting enableSilentRenew: true avoids it: Dashy requests a refresh token and uses it to renew the session in the background, before the token expires, with no interactive round-trip. oidc: clientId: dashy endpoint: 'https://your-oidc-provider.example.com' scope: 'openid profile email groups' adminGroup: admin enableSilentRenew: true Notes: It is opt-in and off by default. With it off, nothing changes, existing setups are unaffected. Your provider must support the offline_access scope. When this is on, Dashy adds offline_access to the scope it requests at sign-in, so the provider issues a refresh token. Most providers support this (Authentik, Keycloak, Authelia, Okta, Auth0, Entra/Azure AD). A few do not, most notably Google, which uses access_type=offline instead and will reject the scope. Do not enable this flag against a provider that rejects offline_access, as it would break the interactive sign-in too. If unsure, test sign-in after enabling it. The provider's client/app may also need to be allowed to issue refresh tokens to a public (PKCE) client. In Authentik this is automatic once offline_access is an allowed scope. If a silent renewal fails for any reason (no refresh token issued, refresh token expired or revoked, the provider returns no fresh id_token, a provider error), Dashy falls back to the normal interactive sign-in. Renewal can save a round-trip, but the interactive flow always remains the safety net. Renewal is driven by the access token's lifetime. If your provider issues an id_token with a much shorter lifetime than the access token, renewal may lag; keeping the two lifetimes equal (the common default) works best. With multiple tabs open against a provider that rotates refresh tokens on use, tabs can briefly contend for the refresh token; the affected tab simply falls back to interactive sign-in. This is inherent to browser-based refresh tokens, not specific to Dashy.","s":"Silent token renewal","u":"/docs/authentication/oidc","h":"#silent-token-renewal","p":222},{"i":240,"t":"If OIDC or our built-in auth doesn't suit your needs, there's plenty of other ways of protecting your dashboard from unauthenticated access. Reverse Proxy Auth - Authelia, Authentik, or similar sitting in front of Dashy Zero-Trust Tunnels - Cloudflare Tunnel, Tailscale Funnel VPN - Keep Dashy off the internet entirely IP-Based Access - Restrict by source IP in your web server Web Server Authentication - HTTP basic auth at the proxy level Client Certificates (mTLS) - Require a client TLS certificate to connect SSO / OAuth Providers - Cloud-hosted identity providers Cloud Hosting Providers - Built-in auth on hosting platforms important Every method on this page except header auth authenticates at the network or proxy edge, not inside Dashy. Dashy's own server stays open to anything that can reach it directly, so don't expose its port publicly: bind it to localhost or an internal network and only let the proxy or tunnel through. If you want app-level auth with per-user roles, use OIDC, built-in auth, or header auth instead.","s":"Other Auth Methods","u":"/docs/authentication/other-auth-methods","h":"","p":239},{"i":242,"t":"The most common setup for self-hosters running multiple services. Your reverse proxy delegates login to an auth server (forward auth), which handles login, 2FA, and sessions for everything behind it. You configure it once, and all your apps get protected. Dashy has Header Authentication support, so when your proxy authenticates a user and forwards their identity via a header, Dashy picks up the username and maps it to a configured user automatically. No separate Dashy login needed. Authelia is lightweight and Docker-friendly. It supports 2FA, per-path access rules, and multiple user backends. To get started quickly: git clone https://github.com/authelia/authelia.git cd authelia/examples/compose/lite Edit users_database.yml, configuration.yml, and docker-compose.yml for your domain and users docker compose up -d See the Authelia docs for the full setup guide. Authentik is heavier but gives you a proper admin UI, built-in OIDC/SAML support, and user self-service (password resets, enrollment flows, etc). Good if you want a single identity provider across many apps. See the authentik Docker Compose install to get started, and the authentik guide for Dashy-specific OIDC config. OAuth2 Proxy (docs) is a thin forward-auth layer that puts any OIDC or OAuth2 provider (Google, GitHub, your own IdP) in front of apps that can't do it themselves. Point it at Dashy's header auth and it forwards the authenticated username, so you get provider login without Dashy needing to reach the provider directly.","s":"Reverse proxy auth","u":"/docs/authentication/other-auth-methods","h":"#reverse-proxy-auth","p":239},{"i":244,"t":"These let you expose Dashy to the internet without opening inbound ports or configuring port forwarding. Auth is handled by the tunnel provider before traffic ever reaches your server. Cloudflare Tunnel connects Dashy to Cloudflare's edge network via an outbound-only cloudflared daemon (runs nicely as a Docker sidecar). Cloudflare handles DNS, TLS, and DDoS protection. Pair it with Cloudflare Access to require identity provider login before anyone reaches Dashy. The free tier covers most home setups. Dashy has a full Cloudflare Tunnel guide, or see the upstream docs. Tailscale Funnel exposes Dashy through your Tailscale mesh to the public internet, with automatic TLS. Simpler to set up than Cloudflare but you get less control over access policies. See Dashy's Tailscale guide, or the upstream Funnel docs.","s":"Zero-trust tunnels","u":"/docs/authentication/other-auth-methods","h":"#zero-trust-tunnels","p":239},{"i":246,"t":"If you run Dashy behind any of the redirect-based proxies or tunnels above and also enable the service worker for offline use (appConfig.enableServiceWorker: true), set appConfig.enableAuthProxyCompat: true as well. Without it, when your proxy session expires the cached app can get stuck, with the service worker serving the old page instead of letting the proxy redirect you to its login screen. With it enabled, Dashy detects the expiry on load and reloads so you can sign in again.","s":"Service worker & offline use","u":"/docs/authentication/other-auth-methods","h":"#service-worker--offline-use","p":239},{"i":248,"t":"A VPN keeps Dashy off the public internet entirely. You connect to your home network remotely and access Dashy like you're on the LAN. No auth to configure, no attack surface to worry about. The downside: you need the VPN running to see anything, and some networks (corporate WiFi, hotels) block VPN traffic. WireGuard is fast and minimal. Most self-hosters run it through a UI like wg-easy, which gives you a web interface for managing peers and generating QR codes for mobile. Tailscale wraps WireGuard and takes care of NAT traversal, key exchange, and device management. No port forwarding needed, works across networks with zero config. There's a generous free tier, and Dashy has a dedicated Tailscale guide. Headscale is a self-hosted coordination server if you want to keep everything on your own infrastructure. OpenVPN still works fine if you already have it running, but for a new setup WireGuard or Tailscale are easier to get going.","s":"VPN","u":"/docs/authentication/other-auth-methods","h":"#vpn","p":239},{"i":250,"t":"If you have a static IP or are already on a VPN, you can restrict access to Dashy by source IP at the web server level. This works well as an extra layer on top of other auth methods. NGINX: location / { proxy_pass http://dashy:8080; allow 192.168.1.0/24; allow 203.0.113.50; deny all; } Caddy (request matchers docs): dashy.example.com { @blocked not remote_ip 192.168.1.0/24 203.0.113.50 respond @blocked \"Access denied\" 403 reverse_proxy dashy:8080 } Apache (2.4+): Require ip 192.168.1.0/24 Require ip 203.0.113.50 ","s":"IP-based access","u":"/docs/authentication/other-auth-methods","h":"#ip-based-access","p":239},{"i":252,"t":"Your reverse proxy can handle HTTP basic auth directly, no extra services needed. This gives you a browser login prompt in front of Dashy. Make sure you're using HTTPS, as basic auth sends credentials base64-encoded (not encrypted) with every request. NGINX (auth module docs): location / { auth_basic \"Dashy\"; auth_basic_user_file /etc/nginx/conf.d/.htpasswd; proxy_pass http://dashy:8080; } Generate the password file with htpasswd -c /etc/nginx/conf.d/.htpasswd alicia. Caddy (basicauth directive): dashy.example.com { basicauth { alicia $2a$14$... # generate with: caddy hash-password } reverse_proxy dashy:8080 } Apache: AuthType Basic AuthName \"Dashy\" AuthUserFile /path/to/.htpasswd Require valid-user Generate the password file with htpasswd -c /path/to/.htpasswd alicia.","s":"Web server authentication","u":"/docs/authentication/other-auth-methods","h":"#web-server-authentication","p":239},{"i":254,"t":"Instead of a password, you can require a client TLS certificate to reach Dashy. The browser presents a cert you've issued, the proxy rejects anyone without a valid one. It's strong and can't be phished, but you have to generate, hand out, and occasionally revoke the certs, so it fits a small fixed set of devices better than a large user base. You'll need your own CA to sign the client certs. mkcert is fine for a handful, step-ca if you want to manage them properly. NGINX: server { ssl_client_certificate /etc/nginx/certs/ca.crt; ssl_verify_client on; location / { proxy_pass http://dashy:8080; } } Caddy and Apache do mTLS too, via client_auth and SSLVerifyClient require respectively.","s":"Client certificates (mTLS)","u":"/docs/authentication/other-auth-methods","h":"#client-certificates-mtls","p":239},{"i":256,"t":"Cloud identity providers like Auth0, Okta, Ory, and Google Cloud Identity can work with Dashy through its OIDC support. If your provider speaks OIDC (most do), just configure it as described in the OIDC section and you're set. For providers that only support OAuth2 or SAML without an OIDC layer, you'll need something in between to translate. Authentik, Keycloak, and Authelia can all bridge from SAML/OAuth2 to OIDC.","s":"SSO / OAuth providers","u":"/docs/authentication/other-auth-methods","h":"#sso--oauth-providers","p":239},{"i":258,"t":"If you're running Dashy on a cloud platform, most have their own auth options you can enable without touching Dashy's config. See your provider's docs: Cloudflare Access, Netlify Password Protection, AWS Cognito, Azure App Service Authentication, and Vercel Password Protection.","s":"Cloud hosting providers","u":"/docs/authentication/other-auth-methods","h":"#cloud-hosting-providers","p":239},{"i":260,"t":"Dashy supports using Pocket ID as its OIDC provider. Pocket ID is a small open source identity provider built around passkeys. It's a single Go binary backed by SQLite (or Postgres if you want), no password storage, no email server needed for basic use. Popular in homelab setups where you want SSO across a handful of services without running Authentik or Keycloak.","s":"Pocket ID OIDC","u":"/docs/authentication/pocketid","h":"","p":259},{"i":262,"t":"1. Deploy Pocket ID 2. Configure Pocket ID First-time admin setup Create the OIDC client Create the admin group Create test users 3. Enabling Pocket ID in Dashy 4. Groups and Visibility 5. Silent token renewal (optional) Troubleshooting How it Works Client side Server side Visual Overview","s":"Contents","u":"/docs/authentication/pocketid","h":"#contents","p":259},{"i":264,"t":"A minimal compose: Example docker-compose.yml name: dashy-pocketid services: pocket-id: image: ghcr.io/pocket-id/pocket-id:v2.7.0 restart: unless-stopped ports: - \"1411:1411\" environment: APP_URL: http://localhost:1411 TRUST_PROXY: \"false\" DB_PROVIDER: sqlite DB_CONNECTION_STRING: /data/pocket-id.db ENCRYPTION_KEY: \"local-dev-key-32-bytes-padding!!\" volumes: - ./data:/data healthcheck: test: [\"CMD\", \"wget\", \"-qO-\", \"http://127.0.0.1:1411/healthz\"] interval: 10s timeout: 5s retries: 10 start_period: 30s A few things worth understanding before you start: APP_URL is the URL the browser uses to reach Pocket ID. It must match exactly, since Pocket ID puts that value in the OIDC issuer, in cookie domains, and in the WebAuthn relying party ENCRYPTION_KEY is required and must be at least 16 bytes. Generate a real one with openssl rand -hex 32 for production; the example above is for local testing only Pocket ID is passkey-only by default. Passkeys (WebAuthn) require a secure context, which means either HTTPS, or a literal localhost host. pocketid.lvh.me and similar lookalikes don't qualify, only the exact string localhost. For production use HTTPS with a real domain; for local testing stick to http://localhost For production: Put Pocket ID behind a reverse proxy with HTTPS on a real domain Set APP_URL: https://auth.example.com Set TRUST_PROXY: \"true\" so it honours forwarded headers Use a 32-byte random ENCRYPTION_KEY Consider swapping DB_PROVIDER to postgres for redundancy Bring Pocket ID up: docker compose up -d pocket-id Wait for it to be healthy (curl -sf http://localhost:1411/healthz), then continue.","s":"1. Deploy Pocket ID","u":"/docs/authentication/pocketid","h":"#1-deploy-pocket-id","p":259},{"i":267,"t":"There's no env-var bootstrap. You create the first admin through the UI, or via SQLite if you want to script it. Through the UI: Open http://localhost:1411 in a browser The setup wizard prompts for a first admin (username, email, name) After creating the user, register a passkey when prompted If you can't easily register a passkey (no compatible device, or running headless), insert the admin user via SQLite and then use the CLI to get a one-time login URL: docker compose exec pocket-id sh -c ' apk add --no-cache sqlite >/dev/null 2>&1 sqlite3 /data/pocket-id.db \" INSERT INTO users (id, created_at, updated_at, username, email, first_name, last_name, display_name, is_admin, email_verified) VALUES (\\\"admin-uuid-001\\\", datetime(\\\"now\\\"), datetime(\\\"now\\\"), \\\"pocketid-admin\\\", \\\"admin@example.com\\\", \\\"Pocket ID\\\", \\\"Admin\\\", \\\"Admin\\\", 1, 1); \" ' docker compose exec pocket-id /app/pocket-id one-time-access-token pocketid-admin The CLI prints a URL like http://localhost:1411/lc/. Open it in a browser to log in. The token is single-use and expires in 1 hour. Once signed in, register a passkey under Settings > Passkeys so you don't need to keep generating OTA tokens.","s":"First-time admin setup","u":"/docs/authentication/pocketid","h":"#first-time-admin-setup","p":259},{"i":269,"t":"Open Admin > OIDC Clients Click Add OIDC Client Set Name to Dashy Set Callback URLs to both: http://localhost:4000 http://localhost:4000/ Set Logout Callback URLs to http://localhost:4000 Turn Public Client on Turn PKCE on Click Save Pocket ID generates a client ID (a UUID, e.g. 268a3701-e9ec-41f6-bad5-87c78ce87c94). Copy it; you'll need it for Dashy's config.","s":"Create the OIDC client","u":"/docs/authentication/pocketid","h":"#create-the-oidc-client","p":259},{"i":271,"t":"Open Admin > User Groups Click Add Group Set Name to Dashy admins (or anything you like) Set Friendly name to admins (this is what Pocket ID emits in the OIDC groups claim, and what Dashy matches against) Click Save","s":"Create the admin group","u":"/docs/authentication/pocketid","h":"#create-the-admin-group","p":259},{"i":273,"t":"Open Admin > Users Click Add User Fill in Username, Email, First Name, Last Name Save, then optionally open the user and assign them to the admins group via the Groups tab Either share the OTA URL (via the CLI command above) with the user, or have them set up a passkey on first sign-in","s":"Create test users","u":"/docs/authentication/pocketid","h":"#create-test-users","p":259},{"i":275,"t":"Pocket ID should now have an OIDC client, an admin group, and at least one admin user in that group.","s":"Summary","u":"/docs/authentication/pocketid","h":"#summary","p":259},{"i":277,"t":"In /user-data/conf.yml: appConfig: ... disableConfigurationForNonAdmin: true auth: enableOidc: true oidc: clientId: 268a3701-e9ec-41f6-bad5-87c78ce87c94 endpoint: http://localhost:1411 adminGroup: admins scope: openid profile email groups Where: disableConfigurationForNonAdmin - Prevent read/write config access to non-admin users auth.enableOidc - Set the auth mode to OIDC clientId - The client ID from Pocket ID. It's a UUID, so no YAML quoting needed endpoint - Pocket ID's APP_URL. Must match exactly. Dashy appends /.well-known/openid-configuration itself adminGroup - The group's friendly name (not the display name) that grants admin in Dashy scope - groups is needed for the adminGroup check. Pocket ID emits the claim by default when the scope is requested Restart Dashy after editing. If Pocket ID runs behind a reverse proxy, make sure endpoint and APP_URL agree, and that Dashy can reach endpoint from its container. With both services in Docker, you can either put them on the same network and use the service name, or use the host's exposed port. Everything should now be fully configured and working 🎉 When you load Dashy, you'll be redirected to Pocket ID's sign-in page. Authenticate with your passkey (or paste a one-time code), and you'll land back on Dashy with full access. All of Dashy's client, server and asset endpoints will be locked behind authentication.","s":"3. Enabling Pocket ID in Dashy","u":"/docs/authentication/pocketid","h":"#3-enabling-pocket-id-in-dashy","p":259},{"i":279,"t":"Once the groups claim is in the id_token, you can use it to hide or show pages, sections and items. The property name is hideForKeycloakUsers / showForKeycloakUsers (the name is historical; it works for any OIDC provider, including Pocket ID). To make an Admin section visible only to members of admins: displayData: showForKeycloakUsers: groups: - admins Both showForKeycloakUsers and hideForKeycloakUsers accept lists of groups and roles. If a user matches an entry they're allowed or excluded as defined. sections: - name: Internal Tools displayData: showForKeycloakUsers: groups: ['admins'] hideForKeycloakUsers: groups: ['guests'] items: - title: Hidden from interns displayData: hideForKeycloakUsers: groups: ['interns']","s":"4. Groups and Visibility","u":"/docs/authentication/pocketid","h":"#4-groups-and-visibility","p":259},{"i":281,"t":"By default, when your token expires Dashy sends you back through Pocket ID's login to get a new one. Set enableSilentRenew: true to have Dashy refresh the session quietly in the background instead, using a refresh token: oidc: clientId: 268a3701-e9ec-41f6-bad5-87c78ce87c94 endpoint: http://localhost:1411 adminGroup: admins scope: openid profile email groups enableSilentRenew: true Dashy adds the offline_access scope to its request automatically, and Pocket ID issues a refresh token for it, so nothing needs changing on the Pocket ID side. It's off by default, and if a refresh ever fails Dashy falls back to the normal sign-in. See silent token renewal for the full notes and caveats.","s":"5. Silent token renewal (optional)","u":"/docs/authentication/pocketid","h":"#5-silent-token-renewal-optional","p":259},{"i":283,"t":"Pocket ID won't start, \"ENCRYPTION_KEY must be at least 16 bytes long\"​ Problem: Container restarts on boot, logs show the encryption key error. Solution: Set ENCRYPTION_KEY to a string of 16 characters or more. For production generate a real random one (openssl rand -hex 32 gives you 64 hex chars). \"WebAuthn is not supported in this browser\"​ Problem: Browser console shows the WebAuthn error and passkey registration or login fails. Solution: WebAuthn needs a secure context. Browsers grant this for HTTPS, or for the literal hostname localhost. pocketid.lvh.me, 127.0.0.1, or anything else over HTTP won't qualify. For local testing, set APP_URL: http://localhost:1411 and access Pocket ID via http://localhost:1411. For production, terminate HTTPS in front of Pocket ID. \"Cookie 'session' has been rejected because a non-HTTPS cookie can't be set as 'secure'\"​ Problem: Browser refuses to store Pocket ID's session cookie, so login appears to succeed but the next request comes back as not signed in. Solution: Same root cause as the WebAuthn one. Pocket ID always sets cookies with the Secure flag, which browsers will only accept on HTTPS or on localhost. Switch to http://localhost or use HTTPS. One-time access token returns \"Token is invalid or expired\"​ Problem: The CLI gave you a URL, but visiting it returns the invalid/expired error. Solution: OTA tokens are single-use and expire after 1 hour. If you've already used the URL or it's stale, generate a fresh one: docker compose exec pocket-id /app/pocket-id one-time-access-token . The earlier \"Cookie rejected as Secure\" issue can also cause this to look like a token problem because the session cookie from the OTA exchange never persisted. Logged in but config saves return 403​ Problem: User authenticates fine, but saving the dashboard returns 403. Solution: The groups claim isn't matching. Two things to check. First, that adminGroup in conf.yml equals the group's friendly name, not the display name. Second, that the user is actually in the group via Admin > Users > [user] > Groups. You can decode the id_token at jwt.io (take it from localStorage, key ID_TOKEN) to see what's in the claim. Redirect URI mismatch​ Problem: Pocket ID returns an invalid_request for the redirect URI on the authorize step. Solution: Pocket ID matches callback URLs exactly. Register both the bare URL and the trailing-slash variant (http://localhost:4000 and http://localhost:4000/) on the OIDC client, and make sure the scheme matches what Dashy is actually served on. Dashy server can't reach Pocket ID​ Problem: Auth'd API calls return 401 and Dashy logs show fetch errors for .well-known/openid-configuration. Solution: endpoint must be reachable from inside the Dashy container. If both run in Docker, share a network and use service names, or use network_mode: service:pocket-id (then localhost inside Dashy resolves to Pocket ID). Test with docker exec wget -qO- \"$ENDPOINT/.well-known/openid-configuration\". Issuer mismatch on token verification​ Problem: Dashy server logs show unexpected \"iss\" claim value. The token's iss claim doesn't match Dashy's endpoint. Solution: Pocket ID puts APP_URL straight into the issuer. Make sure APP_URL, endpoint in Dashy's conf.yml, and the URL the browser uses are all the same string (same scheme, host, and port). First admin can't sign in, no setup page appears​ Problem: Visiting Pocket ID just shows a login form, no setup wizard. Solution: A previous Pocket ID instance may have created users in the database. Either log in as one of those users, or wipe data/ and start fresh: docker compose down && rm -rf data && docker compose up -d. (The data dir is owned by the container user, so you may need sudo or use a throwaway container to clean up.) Token expired / clock skew​ Problem: 401s with \"exp\" claim timestamp check failed, even just after login. Solution: Dashy allows 30 seconds of drift. Sync clocks (NTP) on both hosts. Container clocks follow their host, so it's almost always the host that's drifted. Config change to auth.oidc not picked up​ Problem: Updated clientId, endpoint, adminGroup or scope in conf.yml, but Dashy still uses the old values. Solution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under auth.oidc.","s":"Troubleshooting common Pocket ID issues","u":"/docs/authentication/pocketid","h":"#troubleshooting-common-pocket-id-issues","p":259},{"i":285,"t":"If you're a developer or contributor looking to understand or make changes to Dashy's OIDC implementation, the following outlines how it's wired together. The same OIDC pipeline backs Pocket ID, Authelia, Authentik, Keycloak, Zitadel, and any other generic OIDC provider. The only Pocket ID specific quirk is that it's passkey-first; everything on the Dashy side is shared.","s":"How it Works","u":"/docs/authentication/pocketid","h":"#how-it-works","p":259},{"i":287,"t":"Boot starts in src/main.js. After the initial /conf.yml fetch parses the auth block, isOidcEnabled() decides whether to lazily import oidc-client-ts and call initOidcAuth(). src/utils/auth/OidcAuth.js wraps oidc-client-ts. On load it inspects the URL: if it sees a ?code= callback it runs userManager.signinCallback() to exchange the code (and PKCE verifier) for tokens, persists the user info, and hard-redirects to /. Otherwise it calls userManager.getUser(); if there's no usable session it falls through to userManager.signinRedirect() to send the browser to Pocket ID. A short-lived sessionStorage guard prevents the redirect loop that would otherwise occur if the IdP returns without a usable user. persistUserInfo() writes the raw id_token, the user's groups and roles, a derived isAdmin flag, and a username (falling back through preferred_username, email, and sub) to localStorage. The keys (ID_TOKEN, KEYCLOAK_INFO, USERNAME, ISADMIN) live in src/utils/config/defaults.js; the KEYCLOAK_INFO name is historical and reused for all OIDC providers, including Pocket ID. src/utils/auth/getApiAuthHeader.js builds the Authorization header for every internal API call. It does a client-side exp check and returns null for missing or expired tokens, so the next request triggers a fresh login rather than a 401. src/utils/IsVisibleToUser.js reads KEYCLOAK_INFO when evaluating showForKeycloakUsers and hideForKeycloakUsers rules.","s":"Client side","u":"/docs/authentication/pocketid","h":"#client-side","p":259},{"i":289,"t":"services/auth-oidc.js contains the entire server-side auth surface, in five small pieces: loadOidcSettings() reads auth.oidc (or auth.keycloak) at boot and returns a normalised { issuer, clientId, adminGroup, adminRole }. For generic OIDC providers the issuer is whatever you set as endpoint in conf.yml, verbatim createOidcMiddleware() returns a Connect middleware. Permissive on no-token requests so the SPA can bootstrap; otherwise it verifies the Bearer token against the issuer's JWKS using jose. Checks cover signature, issuer (against the canonical value from the discovery doc), audience (must equal clientId), and expiry, with a 30-second clock-skew tolerance. Sets req.auth = { user, isAdmin, claims } on success, 401 on failure getIssuerContext() lazily fetches .well-known/openid-configuration on first use and wraps jwks_uri in createRemoteJWKSet, which handles JWKS caching and on-demand key rotation. The result is memoised per-issuer for the life of the process deriveIsAdmin() checks the token's groups claim against adminGroup, and the top-level roles claim against adminRole (for Keycloak it also folds in the nested realm_access.roles / resource_access..roles arrays). Pocket ID emits groups natively, so the group path is what's used in practice maybeBootstrapConfig() is the stripped-response helper. When auth is configured, guest access is off, and an unauthenticated request hits the root /conf.yml, it returns a minimal copy with only appConfig.auth, appConfig.enableServiceWorker, and a pageInfo.title of Login | . Sections, items, hostnames and any other secrets never leave the server services/app.js wires it all together. The middleware mounts as protectConfig in front of every YAML route and config-mutating route. The /*.yml handler sets Cache-Control: private, no-store and Vary: Authorization whenever auth is configured (so intermediate caches can never mix auth states), then calls maybeBootstrapConfig; a stripped result is sent as-is, otherwise res.sendFile serves the full file. POST /config-manager/save is additionally guarded by requireAdmin, which returns 401 if req.auth is unset and 403 if req.auth.isAdmin is false.","s":"Server side","u":"/docs/authentication/pocketid","h":"#server-side","p":259},{"i":291,"t":"End-to-end authentication flow Server-side request handling","s":"Visual Overview","u":"/docs/authentication/pocketid","h":"#visual-overview","p":259},{"i":293,"t":"Tailscale is a popular way to put Dashy on a private network you can reach from your phone, laptop, or any other device you've added to your tailnet, without exposing it to the public internet. With Tailscale Serve in front, requests reaching Dashy already carry the user's identity in HTTP headers, which plugs straight into Dashy's header auth for auto-login. Headscale is a self-hosted control plane that speaks the same protocol. Everything in this guide works with both. There's a short section near the end on what changes when you swap Tailscale's coordination server for your own Headscale instance.","s":"Tailscale","u":"/docs/authentication/tailscale","h":"","p":292},{"i":295,"t":"How it fits together Prepare Tailscale Run Tailscale + Dashy together Configure Dashy Funnel for public access Using Headscale instead Best practices Troubleshooting How it Works","s":"Contents","u":"/docs/authentication/tailscale","h":"#contents","p":292},{"i":297,"t":"The pieces: tailscaled runs alongside Dashy, joins your tailnet, and obtains a free HTTPS cert for your tailnet hostname Tailscale Serve terminates HTTPS and reverse-proxies to Dashy, adding Tailscale-User-Login, Tailscale-User-Name, and Tailscale-User-Profile-Pic headers describing the authenticated tailnet user Dashy's header auth reads Tailscale-User-Login and matches it to a configured user Traffic between user devices and your host runs over WireGuard, peer-to-peer where possible. Tailscale's control plane only mediates key exchange and routing, not the data itself You need: A Tailscale account (free plan covers 100 devices and 3 users) An auth key, generated in the admin console Docker MagicDNS and HTTPS certificates both enabled in your tailnet settings (both on by default)","s":"How it fits together","u":"/docs/authentication/tailscale","h":"#how-it-fits-together","p":292},{"i":299,"t":"Sign in to the Tailscale admin console Open DNS and confirm MagicDNS is on, and HTTPS Certificates is on. Both are required for Tailscale Serve Open Settings > Keys > Generate auth key Pick Reusable: no, Ephemeral: no, Pre-approved: yes, optionally tag with tag:container Copy the key (tskey-auth-...). You'll paste it into a .env next to the compose file Note your tailnet's domain (something like tail123abc.ts.net). Your Dashy host will get a hostname under it once the container starts, e.g. dashy.tail123abc.ts.net.","s":"Prepare Tailscale","u":"/docs/authentication/tailscale","h":"#prepare-tailscale","p":292},{"i":301,"t":"The Tailscale container shares its network namespace with Dashy, so the daemon listens on the tailnet and proxies to 127.0.0.1:8080 (Dashy in the same namespace). Example docker-compose.yml name: dashy-tailscale services: tailscale: image: tailscale/tailscale:latest hostname: dashy restart: unless-stopped environment: TS_AUTHKEY: ${TS_AUTHKEY}?ephemeral=false TS_STATE_DIR: /var/lib/tailscale TS_SERVE_CONFIG: /config/serve.json TS_USERSPACE: \"false\" TS_EXTRA_ARGS: --advertise-tags=tag:container volumes: - ./ts-state:/var/lib/tailscale - ./ts-config:/config - /dev/net/tun:/dev/net/tun cap_add: - net_admin - sys_module dashy: image: lissy93/dashy:4.1.5 restart: unless-stopped network_mode: service:tailscale environment: NODE_ENV: production HOST: 0.0.0.0 PORT: 8080 volumes: - ./user-data:/app/user-data depends_on: tailscale: condition: service_started healthcheck: test: [\"CMD-SHELL\", \"wget -qO- http://127.0.0.1:8080/healthz >/dev/null 2>&1\"] interval: 10s timeout: 5s retries: 10 start_period: 30s .env next to the compose: TS_AUTHKEY=tskey-auth-xxx... Then ts-config/serve.json: { \"TCP\": { \"443\": { \"HTTPS\": true } }, \"Web\": { \"${TS_CERT_DOMAIN}:443\": { \"Handlers\": { \"/\": { \"Proxy\": \"http://127.0.0.1:8080\" } } } } } ${TS_CERT_DOMAIN} is substituted by the Tailscale container at runtime with the hostname it gets in your tailnet (e.g. dashy.tail123abc.ts.net). Bring it up: docker compose up -d The first run takes ~30 seconds while Tailscale joins the tailnet and provisions an HTTPS cert. After that, your tailnet name shows up in the admin console under Machines, and the URL becomes reachable from any device on the tailnet. Notice what's not in the compose: Dashy has no ports: mapping. It's only reachable through Tailscale, never directly.","s":"Run Tailscale + Dashy together","u":"/docs/authentication/tailscale","h":"#run-tailscale--dashy-together","p":292},{"i":303,"t":"In /user-data/conf.yml: appConfig: ... disableConfigurationForNonAdmin: true auth: enableHeaderAuth: true users: - user: alice@example.com hash: \"0000000000000000000000000000000000000000000000000000000000000000\" type: admin - user: bob@example.com hash: \"0000000000000000000000000000000000000000000000000000000000000000\" type: normal headerAuth: userHeader: Tailscale-User-Login proxyWhitelist: - 127.0.0.1 Where: enableHeaderAuth - Turns on header auth mode users - The email Tailscale sends in Tailscale-User-Login must match user here. type controls admin status in Dashy hash - Required by Dashy's user schema even though the password is never checked under header auth. Quote it so YAML doesn't parse an all-zero placeholder as the number 0 userHeader - The header tailscale serve sets on every proxied request, containing the tailnet user's login (typically email) proxyWhitelist - Just 127.0.0.1 because tailscaled and Dashy share a network namespace, so requests appear to come from localhost Restart Dashy after editing. Open https://dashy..ts.net from any device on your tailnet. You'll land on the dashboard with the right admin level for your email. No login prompt.","s":"Configure Dashy","u":"/docs/authentication/tailscale","h":"#configure-dashy","p":292},{"i":305,"t":"Tailscale Funnel exposes a tailnet service to the public internet through Tailscale's edge. It uses the same daemon and config, you just flip a switch in serve.json: { \"TCP\": { \"443\": { \"HTTPS\": true } }, \"Web\": { \"${TS_CERT_DOMAIN}:443\": { \"Handlers\": { \"/\": { \"Proxy\": \"http://127.0.0.1:8080\" } } } }, \"AllowFunnel\": { \"${TS_CERT_DOMAIN}:443\": true } } You also need to enable Funnel under Settings > Funnel in the admin console and add your node to the allow-list. Important caveat on auth: Funnel requests come from the public internet, where the caller isn't a tailnet member. Tailscale doesn't inject identity headers on Funnel traffic, only on Serve traffic from authenticated tailnet peers. Header auth in Dashy would 401 every public visitor. For a publicly-exposed Dashy via Funnel, you have two options: Use Dashy's built-in HTTP auth (ENABLE_HTTP_AUTH=true with users in conf.yml) so the browser prompts for credentials Front Funnel with another auth layer, or just don't expose via Funnel and stick to Serve plus the Tailscale apps on the user's devices For a \"Funnel only allows my tailnet users in, but they show up as authenticated\" effect, use Serve, not Funnel. Funnel is for \"I want anyone on the internet to reach this\".","s":"Funnel for public access (optional)","u":"/docs/authentication/tailscale","h":"#funnel-for-public-access-optional","p":292},{"i":307,"t":"Headscale is a self-hosted reimplementation of Tailscale's coordination server, compatible with the official Tailscale clients. Run your own and you don't depend on Tailscale's cloud for control-plane traffic. For Dashy, everything in this guide applies unchanged. The only differences are: Point the tailscaled daemon at your Headscale server via the TS_EXTRA_ARGS env var: TS_EXTRA_ARGS: --login-server=https://headscale.example.com --advertise-tags=tag:container Generate auth keys on the Headscale server (headscale preauthkeys create --user ) instead of the Tailscale admin console ACLs are defined in Headscale's policy file rather than the Tailscale admin UI MagicDNS HTTPS certificates require Headscale ≥0.23. Older versions don't auto-provision certs, so you'd need to terminate TLS yourself with a reverse proxy. Funnel is also Headscale ≥0.23 The serve.json shape, the Dashy header-auth config, and the identity headers (Tailscale-User-Login etc.) are identical. Headscale uses the same protocol, so the client behaves the same way.","s":"Using Headscale instead","u":"/docs/authentication/tailscale","h":"#using-headscale-instead","p":292},{"i":309,"t":"Don't bind Dashy's port to the host. Only tailscaled should reach it, which is the case if you leave ports: off the dashy service and use network_mode: service:tailscale Treat the auth key as a credential. Tag it (tag:container) and rotate it periodically. Prefer non-ephemeral keys for the Dashy node so it survives restarts Use Tailscale ACLs to limit who in your tailnet can reach the Dashy node, especially for shared organisations. Configure under Access controls in the admin console Pin tailscale/tailscale to a version tag in production rather than :latest. The Docker image follows the daemon's release cadence Match Dashy's users[] to actual tailnet emails. Every tailnet member who should reach Dashy needs an entry. Promote to admin by setting type: admin For self-hosting purists, Headscale removes the dependency on Tailscale's coordination server. Otherwise prefer the hosted plan, the free tier is generous and the engineering team owns reliability for you","s":"Best practices","u":"/docs/authentication/tailscale","h":"#best-practices","p":292},{"i":311,"t":"Container starts but no machine appears in the Tailscale admin console​ Problem: docker compose logs tailscale shows the daemon running but it never registers. Solution: The auth key is wrong, expired, or the wrong type. Generate a fresh Reusable: No, Ephemeral: No, Pre-approved: Yes key and update .env. Auth keys are single-use unless marked reusable. \"no HTTPS server configured for ${TS_CERT_DOMAIN}\"​ Problem: Serve config references the env-substituted hostname but Tailscale didn't substitute it. Solution: Older tailscale/tailscale images didn't expand ${TS_CERT_DOMAIN} in serve.json. Update to a recent image (v1.66+ or latest), or hardcode your tailnet hostname in serve.json (dashy.tail123abc.ts.net:443). HTTPS cert never issued​ Problem: Browser shows a TLS error visiting the tailnet URL. Solution: HTTPS certificates aren't enabled for your tailnet. Open DNS in the admin console and turn on both MagicDNS and HTTPS Certificates. Cert issuance takes about a minute after serve.json is applied. \"401 Unauthorized - not from trusted proxy\" from Dashy​ Problem: Reaches Dashy but the header auth middleware rejects it. Solution: tailscaled and Dashy must share a network namespace via network_mode: service:tailscale for the source IP to be 127.0.0.1. If they're on different networks, the source IP will be the Tailscale container's IP instead. Update proxyWhitelist accordingly, or fix the compose to share the namespace. \"401 Unauthorized - missing user header\"​ Problem: Source IP check passes, but Tailscale-User-Login isn't present. Solution: The request didn't come through tailscale serve (maybe Funnel, maybe a direct hit on the tailnet IP without going via Serve). Confirm serve.json is loaded by running docker compose exec tailscale tailscale serve status and re-issuing the request through the HTTPS URL. Logged in but admin features stay locked​ Problem: Authenticates fine, but admin actions return 403. Solution: The email in Tailscale-User-Login doesn't exactly match a users[].user with type: admin. Run docker compose exec tailscale tailscale whois to see what login Tailscale will pass through. Funnel users see 401​ Problem: You enabled AllowFunnel, but visitors from outside the tailnet get 401. Solution: Expected. Funnel traffic doesn't carry tailnet identity, so header auth has nothing to match. Either drop header auth and use built-in HTTP auth for Funnel-exposed Dashy, or only use Funnel for fully-public read access (no auth, guest mode). Tailscale container exits with \"cannot allocate memory\" or DEVICE_RESET​ Problem: Crash loop on a low-memory host. Solution: Either bump memory, or set TS_USERSPACE: \"true\" to use the userspace WireGuard implementation (slower but no kernel module dependency). Remove the /dev/net/tun volume mount and the net_admin and sys_module capabilities when in userspace mode. MagicDNS hostname doesn't resolve from the device I'm using​ Problem: Other tailnet devices can't reach the Dashy URL even though the machine shows up in the admin console. Solution: Confirm MagicDNS is on globally (admin console DNS) and on the client (tailscale netcheck and the Tailscale app's settings). On Linux clients add --accept-dns=true to tailscale up. Older Linux setups sometimes need nameserver 100.100.100.100 in /etc/resolv.conf. Headscale setup, certs not issued automatically​ Problem: Using Headscale, the tailnet hostname works but HTTPS isn't auto-provisioned. Solution: Headscale's auto-cert support requires version 0.23 or newer. Either upgrade Headscale, or run your own reverse proxy with HTTPS (Caddy, Traefik) in front of the Tailscale daemon and route to it as you would any other internal service. Config change to auth.headerAuth not picked up​ Problem: Updated userHeader, proxyWhitelist, or users in conf.yml, but Dashy still uses the old values. Solution: The server reads the auth config only at boot. Restart the Dashy container after any change.","s":"Troubleshooting common Tailscale issues","u":"/docs/authentication/tailscale","h":"#troubleshooting-common-tailscale-issues","p":292},{"i":314,"t":"tailscaled runs WireGuard for the data plane and a small HTTPS server in the same container for Tailscale Serve. When a tailnet peer hits https://dashy..ts.net, the request is delivered over WireGuard directly to your host. tailscaled terminates TLS using the cert it auto-provisioned, then makes an internal HTTP request to http://127.0.0.1:8080 (Dashy in the same namespace), adding three headers: Tailscale-User-Login - the user's tailnet login, typically their email Tailscale-User-Name - the display name Tailscale-User-Profile-Pic - a URL to their avatar Dashy's header auth middleware checks req.socket.remoteAddress against proxyWhitelist (which is 127.0.0.1 because of the shared namespace), reads Tailscale-User-Login, and sets req.auth = { user: }. The SPA's HeaderAuth.js then fetches /get-user, matches the email to users[], and sets the session cookie. Same code path as the Cloudflare Tunnel guide, just a different proxy and a different header name. Identity flows from the user's tailnet membership all the way to Dashy's role-based UI without the user ever entering a password. End-to-end authentication flow","s":"Server side","u":"/docs/authentication/tailscale","h":"#server-side","p":292},{"i":316,"t":"Dashy supports using Zitadel as its OIDC provider. Zitadel is an open source identity and access platform written in Go and backed by Postgres or CockroachDB. It speaks OIDC, OAuth 2.0, and SAML, supports MFA, social login, and per-project role management. The free self-hosted edition is fully featured; there's also a managed cloud option. important This guide targets Zitadel v3.4.x. Zitadel v4 removed the embedded-JS Actions API used to translate Zitadel's role claim format into a groups claim Dashy understands. The same flow on v4 needs a sidecar webhook target; see the v4 note in the Action step.","s":"Zitadel OIDC","u":"/docs/authentication/zitadel","h":"","p":315},{"i":318,"t":"1. Deploy Zitadel 2. Configure Zitadel Create the project Create the OIDC application Create the admin role Grant the role to a user Map roles to a groups claim Create test users 3. Enabling Zitadel in Dashy 4. Groups and Visibility 5. Silent token renewal (optional) Troubleshooting How it Works Client side Server side Visual Overview","s":"Contents","u":"/docs/authentication/zitadel","h":"#contents","p":315},{"i":320,"t":"Zitadel needs a Postgres backend. The compose below brings up both, with the admin user pre-created via env, and a machine user PAT written to disk for programmatic setup. Example docker-compose.yml name: dashy-zitadel services: postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: zitadel healthcheck: test: [\"CMD-SHELL\", \"pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}\"] interval: 5s timeout: 5s retries: 10 start_period: 10s volumes: - ./data/postgres:/var/lib/postgresql/data zitadel: image: ghcr.io/zitadel/zitadel:v3.4.10 restart: unless-stopped command: 'start-from-init --masterkey \"MasterkeyNeedsToHave32Characters\" --tlsMode disabled' ports: - \"8080:8080\" environment: ZITADEL_EXTERNALDOMAIN: zitadel.lvh.me ZITADEL_EXTERNALPORT: 8080 ZITADEL_EXTERNALSECURE: \"false\" ZITADEL_PORT: 8080 ZITADEL_TLS_ENABLED: \"false\" ZITADEL_DATABASE_POSTGRES_HOST: postgres ZITADEL_DATABASE_POSTGRES_PORT: 5432 ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel ZITADEL_DATABASE_POSTGRES_USER_USERNAME: postgres ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: postgres ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: postgres ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable ZITADEL_FIRSTINSTANCE_ORG_NAME: Dashy ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME: zitadel-admin ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: AdminPass2026! ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: \"false\" ZITADEL_FIRSTINSTANCE_ORG_HUMAN_EMAIL_ADDRESS: admin@example.com ZITADEL_FIRSTINSTANCE_ORG_HUMAN_EMAIL_VERIFIED: \"true\" ZITADEL_FIRSTINSTANCE_ORG_HUMAN_FIRSTNAME: Zitadel ZITADEL_FIRSTINSTANCE_ORG_HUMAN_LASTNAME: Admin depends_on: postgres: condition: service_healthy The Zitadel external domain (zitadel.lvh.me above) must resolve to the host Zitadel listens on. lvh.me and its subdomains resolve to 127.0.0.1 everywhere, so no /etc/hosts edits needed locally. For production: Drop --tlsMode disabled and ZITADEL_EXTERNALSECURE: \"false\" Put Zitadel behind a reverse proxy on a real domain with HTTPS Use a strong masterkey (32 characters, generated fresh: openssl rand -hex 16) Use a real Postgres password and a dedicated database user, not the postgres superuser Bring it up: docker compose up -d First boot runs the database migrations and creates the org, admin user, and a machine user. Once Zitadel is healthy (curl -sf http://localhost:8080/debug/ready), open http://zitadel.lvh.me:8080/ui/console and sign in as zitadel-admin with the bootstrap password.","s":"1. Deploy Zitadel","u":"/docs/authentication/zitadel","h":"#1-deploy-zitadel","p":315},{"i":322,"t":"Steps below are the manual UI walkthrough. Zitadel also has a Management API if you'd rather script the setup.","s":"2. Configure Zitadel","u":"/docs/authentication/zitadel","h":"#2-configure-zitadel","p":315},{"i":324,"t":"Open the console at http://zitadel.lvh.me:8080/ui/console Sign in as zitadel-admin Go to Projects Click Create New Project Set Name to Dashy, click Continue","s":"Create the project","u":"/docs/authentication/zitadel","h":"#create-the-project","p":315},{"i":326,"t":"Open the Dashy project Under Applications, click New Set Name to Dashy, Type to User Agent, click Continue Set Authentication Method to PKCE, click Continue Set Redirect URIs to both: http://localhost:4000 http://localhost:4000/ Set Post Logout URIs to http://localhost:4000, click Continue Review and click Create After creation, open the application and: Open Configuration (or Settings) Turn Development Mode on (required for http redirect URIs during local testing; turn it off in production) Turn ID Token Role Assertion on so project roles appear in the id_token Turn ID Token Userinfo Assertion on (optional but useful for debugging) Click Save Copy the Client ID shown on the application page. It's a long numeric string; you'll need it for Dashy's config.","s":"Create the OIDC application","u":"/docs/authentication/zitadel","h":"#create-the-oidc-application","p":315},{"i":328,"t":"In the Dashy project, open Roles Click New Set Key to admins Set Display Name to Dashy admins Set Group to admins (this name is also what Dashy's adminGroup will match) Click Save","s":"Create the admin role","u":"/docs/authentication/zitadel","h":"#create-the-admin-role","p":315},{"i":330,"t":"Open the zitadel-admin user (under Users) Open the Authorizations tab Click New Pick the Dashy project Select the admins role Click Save","s":"Grant the role to a user","u":"/docs/authentication/zitadel","h":"#grant-the-role-to-a-user","p":315},{"i":332,"t":"By default Zitadel emits roles in a nested urn:zitadel:iam:org:project:roles claim. Dashy looks for a flat groups array, so an Action is needed to translate. Open your org's settings (top-right menu, Organisation Settings), then Actions Click New Set Name to addGroupsClaim Paste this script: function addGroupsClaim(ctx, api) { if (ctx.v1 && ctx.v1.user && ctx.v1.user.grants && ctx.v1.user.grants.grants) { var groups = []; for (var i = 0; i < ctx.v1.user.grants.grants.length; i++) { var g = ctx.v1.user.grants.grants[i]; if (g.roles) { for (var j = 0; j < g.roles.length; j++) { if (groups.indexOf(g.roles[j]) === -1) groups.push(g.roles[j]); } } } api.v1.claims.setClaim('groups', groups); } } Set Allowed To Fail to true (so a bug here doesn't block login entirely) Click Save Now bind it to the token flow: Still in Actions, open Flows Open Complement Token For both Pre Userinfo Creation and Pre Access Token Creation, click Add and select addGroupsClaim Click Save note On Zitadel v4 the embedded-JS Actions feature is gone. The same translation needs an Actions v2 target (a small HTTP webhook your Zitadel instance can call). The shape of the script is roughly the same; only the deployment changes. See the Zitadel v4 Actions docs for the webhook flow.","s":"Map roles to a groups claim","u":"/docs/authentication/zitadel","h":"#map-roles-to-a-groups-claim","p":315},{"i":334,"t":"If you want users beyond zitadel-admin: Go to Users, click New Fill in Username (e.g. zitadel-user), First Name, Last Name, Email, click Continue Set an initial password and turn off Password Change Required Optionally grant the user the admins role via Authorizations if they should have admin access in Dashy","s":"Create test users","u":"/docs/authentication/zitadel","h":"#create-test-users","p":315},{"i":336,"t":"Zitadel should now have a project, an OIDC application, a role, the role granted to your admin user, and an Action that exposes roles as a groups claim.","s":"Summary","u":"/docs/authentication/zitadel","h":"#summary","p":315},{"i":338,"t":"In /user-data/conf.yml: appConfig: ... disableConfigurationForNonAdmin: true auth: enableOidc: true oidc: clientId: \"1234567890123456789\" endpoint: http://zitadel.lvh.me:8080 adminGroup: admins scope: openid profile email Where: disableConfigurationForNonAdmin - Prevent read/write config access to non-admin users auth.enableOidc - Set the auth mode to OIDC clientId - The Client ID from Zitadel. Wrap it in quotes: Zitadel client IDs are 19-digit numeric strings, which YAML parses as a JS number and loses precision on endpoint - The Zitadel base URL, which is also its issuer. Dashy appends /.well-known/openid-configuration itself adminGroup - Name of the role that grants admin in Dashy (matches the admins role above) scope - Standard OIDC scopes. Zitadel doesn't need a custom groups scope; the Action puts the claim in the token regardless of scope Restart Dashy for these changes to take effect. If Zitadel runs on a different host or behind a reverse proxy, make sure endpoint is reachable from inside the Dashy container, and that the issuer Zitadel advertises matches endpoint exactly. Everything should now be fully configured and working 🎉 When you load Dashy, you'll be redirected to Zitadel's login page. After signing in, you'll land back on Dashy's homepage with full access, and all of Dashy's client, server and asset endpoints will be locked behind authentication.","s":"3. Enabling Zitadel in Dashy","u":"/docs/authentication/zitadel","h":"#3-enabling-zitadel-in-dashy","p":315},{"i":340,"t":"Once the Action puts roles in the id_token's groups claim, you can use them to hide or show pages, sections and items. The property name is hideForKeycloakUsers / showForKeycloakUsers (the name is historical; it works for any OIDC provider, including Zitadel). To make an Admin section visible only to members of admins: displayData: showForKeycloakUsers: groups: - admins Both showForKeycloakUsers and hideForKeycloakUsers accept lists of groups and roles. If a user matches an entry they're allowed or excluded as defined. sections: - name: Internal Tools displayData: showForKeycloakUsers: groups: ['admins'] hideForKeycloakUsers: groups: ['guests'] items: - title: Hidden from interns displayData: hideForKeycloakUsers: groups: ['interns']","s":"4. Groups and Visibility","u":"/docs/authentication/zitadel","h":"#4-groups-and-visibility","p":315},{"i":342,"t":"By default, when your token expires Dashy sends you back through Zitadel's login to get a new one. Set enableSilentRenew: true to have Dashy refresh the session quietly in the background instead, using a refresh token: oidc: clientId: \"1234567890123456789\" endpoint: http://zitadel.lvh.me:8080 adminGroup: admins scope: openid profile email enableSilentRenew: true Dashy adds the offline_access scope to its request automatically. For Zitadel to issue a refresh token, open the application's Configuration and make sure the Refresh Token grant type is enabled. It's off by default, and if a refresh ever fails Dashy falls back to the normal sign-in. See silent token renewal for the full notes and caveats.","s":"5. Silent token renewal (optional)","u":"/docs/authentication/zitadel","h":"#5-silent-token-renewal-optional","p":315},{"i":344,"t":"Numeric Client ID truncated​ Problem: Login redirects to Zitadel with the error \"client not found\" or similar, even though the Client ID looks right. Solution: Zitadel Client IDs are long numeric strings. Without quotes around the value in conf.yml, YAML parses it as a JS number and loses precision past around 15 digits, so the actual value Dashy sends is wrong. Wrap it in quotes: clientId: \"373607668411072515\". Logged in but config saves return 403​ Problem: User authenticates fine, but saving the dashboard returns 403. Solution: The groups claim isn't in the id_token. Paste the token (from localStorage, key ID_TOKEN) into jwt.io. If groups is missing, the Action either isn't bound to the right triggers, or the user has no project role grants. Check Actions > Flows > Complement Token has addGroupsClaim on both Pre Userinfo Creation and Pre Access Token Creation, and that the user has been granted the admins role under their Authorizations tab. Development Mode disabled with http redirect URIs​ Problem: Zitadel returns invalid_redirect_uri for http://localhost:4000 on the authorize step. Solution: Zitadel only accepts http redirect URIs when the OIDC application has Development Mode turned on. Open the application's Configuration tab and enable it. For production, use HTTPS for both Zitadel and Dashy and leave Development Mode off. \"Invalid issuer\" on token verification​ Problem: Dashy server logs show unexpected \"iss\" claim value. The browser reaches Zitadel at one URL but the issuer in the id_token is different. Solution: Zitadel's issuer is built from ZITADEL_EXTERNALDOMAIN, ZITADEL_EXTERNALPORT, and ZITADEL_EXTERNALSECURE. Set these to match the public URL exactly. Behind a reverse proxy, make sure the proxy forwards X-Forwarded-Proto: https and X-Forwarded-Host. Token endpoint CORS blocked​ Problem: Browser shows Cross-Origin Request Blocked on a POST to /oauth/v2/token. Solution: This usually means Development Mode is off on the OIDC application and Zitadel isn't auto-allowing your local origin. Turn it on for local testing. In production, Zitadel allows origins listed in the registered redirect URIs automatically. Action script never fires​ Problem: The Action exists but no groups claim appears in the token. Solution: The Action needs to be bound to a flow. Open Actions > Flows > Complement Token and confirm addGroupsClaim is in both Pre Userinfo Creation and Pre Access Token Creation. Also check the Action is in State: Active (not deactivated). Action exists but tokens have no groups, on Zitadel v4​ Problem: v1 Actions were deprecated in Zitadel v4 and no longer fire. Solution: Either pin to Zitadel v3.4.x (last release line with v1 Actions), or migrate to Actions v2 which uses HTTP webhook targets. Actions v2 needs an extra service to host the script, so it's a heavier setup. Zitadel UI loads but login fails with \"instance not found\"​ Problem: Visiting Zitadel via an unconfigured hostname returns \"Instance not found\" or similar. Solution: Zitadel binds itself to one external domain at first init. Use http://zitadel.lvh.me:8080 (or whatever ZITADEL_EXTERNALDOMAIN is set to), not http://localhost:8080 or http://127.0.0.1:8080. Dashy server can't reach Zitadel​ Problem: Auth'd API calls return 401 and Dashy logs show fetch errors for .well-known/openid-configuration. Solution: endpoint must be reachable from inside the Dashy container, not just from the browser. If both run in Docker, put them on the same network. Test with docker exec wget -qO- \"$ENDPOINT/.well-known/openid-configuration\". Subject claim is a UUID, not a username​ Problem: Dashy logs show users with random-looking subject IDs. Solution: Zitadel's sub claim is an opaque per-user ID. Dashy's OIDC client falls through preferred_username, email, then sub, so as long as profile and email are in the scopes, the username will be human-readable. Make sure both are listed in scope: in your Dashy config. Token expired / clock skew​ Problem: 401s with \"exp\" claim timestamp check failed or \"iat\" claim timestamp check failed, even just after login. Solution: Dashy allows 30 seconds of drift. Sync clocks on both hosts with NTP. Container clocks follow their host, so it's almost always the host that's drifted. Config change to auth.oidc not picked up​ Problem: Updated clientId, endpoint, adminGroup or scope in conf.yml, but Dashy still uses the old values. Solution: The server reads the auth config only at boot. Restart the Dashy container after any change to fields under auth.oidc. Postgres fails to start​ Problem: Zitadel never reaches ready, Postgres container exits or healthcheck fails. Solution: Often a stale data/postgres directory left over from a previous version. Either run docker compose down -v to drop the volume, or rm -rf data/postgres and start over. Also confirm the host port 5432 isn't already taken by another Postgres.","s":"Troubleshooting common Zitadel Issues","u":"/docs/authentication/zitadel","h":"#troubleshooting-common-zitadel-issues","p":315},{"i":346,"t":"If you're a developer or contributor looking to understand or make changes to Dashy's OIDC implementation, the following outlines how it's wired together. The same OIDC pipeline backs Zitadel, Authelia, Authentik, Keycloak, and any other generic OIDC provider. The only Zitadel-specific piece is the Action that maps project roles to the groups claim; everything on the Dashy side is shared.","s":"How it Works","u":"/docs/authentication/zitadel","h":"#how-it-works","p":315},{"i":348,"t":"Boot starts in src/main.js. After the initial /conf.yml fetch parses the auth block, isOidcEnabled() decides whether to lazily import oidc-client-ts and call initOidcAuth(). src/utils/auth/OidcAuth.js wraps oidc-client-ts. On load it inspects the URL: if it sees a ?code= callback it runs userManager.signinCallback() to exchange the code (and PKCE verifier) for tokens, persists the user info, and hard-redirects to /. Otherwise it calls userManager.getUser(); if there's no usable session it falls through to userManager.signinRedirect() to send the browser to Zitadel. A short-lived sessionStorage guard prevents the redirect loop that would otherwise occur if the IdP returns without a usable user. persistUserInfo() writes the raw id_token, the user's groups and roles, a derived isAdmin flag, and a username (falling back through preferred_username, email, and sub) to localStorage. The keys (ID_TOKEN, KEYCLOAK_INFO, USERNAME, ISADMIN) live in src/utils/config/defaults.js; the KEYCLOAK_INFO name is historical and reused for all OIDC providers, including Zitadel. src/utils/auth/getApiAuthHeader.js builds the Authorization header for every internal API call. It does a client-side exp check and returns null for missing or expired tokens, so the next request triggers a fresh login rather than a 401. src/utils/IsVisibleToUser.js reads KEYCLOAK_INFO when evaluating showForKeycloakUsers and hideForKeycloakUsers rules.","s":"Client side","u":"/docs/authentication/zitadel","h":"#client-side","p":315},{"i":350,"t":"services/auth-oidc.js contains the entire server-side auth surface, in five small pieces: loadOidcSettings() reads auth.oidc (or auth.keycloak) at boot and returns a normalised { issuer, clientId, adminGroup, adminRole }. For generic OIDC providers the issuer is whatever you set as endpoint in conf.yml, verbatim createOidcMiddleware() returns a Connect middleware. Permissive on no-token requests so the SPA can bootstrap; otherwise it verifies the Bearer token against the issuer's JWKS using jose. Checks cover signature, issuer (against the canonical value from the discovery doc), audience (must equal clientId), and expiry, with a 30-second clock-skew tolerance. Sets req.auth = { user, isAdmin, claims } on success, 401 on failure getIssuerContext() lazily fetches .well-known/openid-configuration on first use and wraps jwks_uri in createRemoteJWKSet, which handles JWKS caching and on-demand key rotation. The result is memoised per-issuer for the life of the process deriveIsAdmin() checks the token's groups claim against adminGroup, and the top-level roles claim against adminRole (for Keycloak it also folds in the nested realm_access.roles / resource_access..roles arrays). Zitadel emits roles in a different format, which is why the Action maps them into a flat groups array for Dashy maybeBootstrapConfig() is the stripped-response helper. When auth is configured, guest access is off, and an unauthenticated request hits the root /conf.yml, it returns a minimal copy with only appConfig.auth, appConfig.enableServiceWorker, and a pageInfo.title of Login | . Sections, items, hostnames and any other secrets never leave the server services/app.js wires it all together. The middleware mounts as protectConfig in front of every YAML route and config-mutating route. The /*.yml handler sets Cache-Control: private, no-store and Vary: Authorization whenever auth is configured (so intermediate caches can never mix auth states), then calls maybeBootstrapConfig; a stripped result is sent as-is, otherwise res.sendFile serves the full file. POST /config-manager/save is additionally guarded by requireAdmin, which returns 401 if req.auth is unset and 403 if req.auth.isAdmin is false.","s":"Server side","u":"/docs/authentication/zitadel","h":"#server-side","p":315},{"i":352,"t":"End-to-end authentication flow Server-side request handling","s":"Visual Overview","u":"/docs/authentication/zitadel","h":"#visual-overview","p":315},{"i":354,"t":"Beyond the cloud backup/restore service, there are several other self-hosted options you can use to backup Dashy, and any other Docker container data. These are outlined in the Management docs, at: Docker Backup Options. Dashy has a built-in feature for securely backing up your config to a hosted cloud service, and then restoring it on another instance. This feature is totally optional, and if you do not enable it, then Dashy will not make any external network requests. This is useful not only for backing up your configuration off-site, but it also enables Dashy to be used without having write a YAML config file, and makes it possible to use a public hosted instance, without the need to self-host.","s":"Cloud Backup and Restore","u":"/docs/backup-restore","h":"","p":353},{"i":356,"t":"All data is encrypted before being sent to the backend. In Dashy, this is done in CloudBackup.js, using crypto.js's AES method, using the users chosen password as the key. The data is then sent to a Cloudflare worker (a platform for running serverless functions), and stored in a KV data store.","s":"How it Works","u":"/docs/backup-restore","h":"#how-it-works","p":353},{"i":358,"t":"Once you've got Dashy configured to your preference, open the Backup & Restore menu (click the Cloud icon in the top-right corner). Here you will be prompted to choose a password, which will be used to encrypt your data. If you forget this password, there will be no way to recover your config. After clicking 'Backup' your data will be encrypted, compressed and sent to the hosted cloud service. A backup ID will be returned (in the format of xxxx-xxxx-xxxx-xxxx), this is what you use, along with your password to restore the backup on another system, so take note of it. To update a backup, return to this menu, enter your password, and click 'Update Backup'.","s":"Creating a Backup","u":"/docs/backup-restore","h":"#creating-a-backup","p":353},{"i":360,"t":"To restore a backup, navigate to the Backup & Restore menu, and under restore, enter your backup ID, and the password you chose. Your config file will be downloaded, decrypted and applied to local storage.","s":"Restoring a Backup","u":"/docs/backup-restore","h":"#restoring-a-backup","p":353},{"i":362,"t":"Data is only ever sent to the cloud when the user actively triggers a backup. All transmitted data is first encrypted using AES. Your selected password never leaves your device, and is hashed before being compared. It is only possible to restore a configuration if you have both the backup ID and decryption password. Because the data is encrypted on the client-side (before being sent to the cloud), it is not possible for a man-in-the-middle, government entity, website owner, or even Cloudflare to be able read any of your data. The biggest risk to your data, would be a weak password, or a compromised system. Having said that, although the code uses robust security libraries and is open source- it was never intended to be a security product, and has not been audited, and therefore cannot be considered totally secure - please keep that in mind.","s":"Privacy & Security","u":"/docs/backup-restore","h":"#privacy--security","p":353},{"i":364,"t":"Maximum of 24mb of storage per user. Please do not repeatedly hit the endpoint, as if the quota is exceeded the service may become less available to other users. Abuse may result in your IP being temporarily banned by Cloudflare.","s":"Fair Use Policy","u":"/docs/backup-restore","h":"#fair-use-policy","p":353},{"i":367,"t":"Install Wrangler CLI Tool: npm i -g @cloudflare/wrangler Log into Cloudflare account: wrangler login Create a new project: wrangler generate my-project Install dependencies: cd my-project && npm i","s":"Quick Start","u":"/docs/backup-restore","h":"#quick-start","p":353},{"i":369,"t":"Add your account_id (found on the right sidebar of the Workers or Overview Dashboard) Add your zone_id (found in the Overview tab of your desired domain on Cloudflare) Add your route, which should be a domain or host, supporting a wildcard name = \"dashy-worker\" type = \"javascript\" workers_dev = true route = \"example.com/*\" zone_id = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" account_id = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" kv_namespaces = [ { binding = \"DASHY_CLOUD_BACKUP\", id = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" } ]","s":"Populate wrangler.toml","u":"/docs/backup-restore","h":"#populate-wranglertoml","p":353},{"i":371,"t":"Write code to handle your requests, and interact with any other data sources in this file Generally, this is done within an event listener for 'fetch', and returns a promise For Example: addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) }) async function handleRequest(request) { return new Response('Hello World!', { headers: { 'content-type': 'text/plain' }, }) } For the code used for Dashy's cloud service, see here","s":"Complete index.js","u":"/docs/backup-restore","h":"#complete-indexjs","p":353},{"i":373,"t":"wrangler dev - To start the wrangler development server wrangler publish - To publish to your cloudflare account (first run wrangler login)","s":"Commands","u":"/docs/backup-restore","h":"#commands","p":353},{"i":375,"t":"There are four endpoints, and to keep things simple, they all use the same base URL/ route. GET - Get config for a given user backupId - The ID of the desired encrypted object subHash - The latter half of the password hash, to verify ownership POST - Save a new config object, and returns backupId userData - The encrypted, compressed and stringified user config subHash - The latter half of the password hash, to verify ownership PUT - Update an existing config object backupId - The ID of the object to update subHash - Part of the hash, to verify ownership of said object userData - The new data to store DELETE - Delete a specified config object backupId - The ID of the object to be deleted subHash - Part of the password hash, to verify ownership of the object For more info, see the API Docs. If you are using Postman, you may find this pre-made collection helpful in getting things setup.","s":"API","u":"/docs/backup-restore","h":"#api","p":353},{"i":377,"t":"All app configuration is specified in /user-data/conf.yml which is in YAML Format format. If you're using Docker, this file can be passed in as a volume. Changes can either be made directly to this file, or done through the UI. From the UI you can also export, backup, reset, validate and download your configuration file.","s":"Configuring","u":"/docs/configuring","h":"","p":376},{"i":379,"t":"Directly in the YAML file (5/5 reliability, 3/5 usability) Write changes directly to the conf.yml file, optionally using one of the templates provided. This can be done in your favorite editor and uploading to your server, or directly editing the file via SSH, but the easiest method would be to use Code Server UI JSON Editor (4/5 reliability, 4/5 usability) From the UI, under the config menu there is a JSON editor, with built-in validation, documentation and advanced options UI Visual Editor (3/5 reliability, 5/5 usability) From the UI, enter the Interactive Edit Mode, then click any part of the page to edit. Changes are previewed live, and then saved to disk REST API (see API docs) Programmatically edit config either through the command line, using a script or a third-party application","s":"There are three ways to edit the config","u":"/docs/configuring","h":"#there-are-three-ways-to-edit-the-config","p":376},{"i":381,"t":"You may find it helpful to look at some sample config files to get you started, a collection of which can be found here You can check that your config file fits the schema, by running yarn validate-config After modifying your config, the app needs to be recompiled, by running yarn build - this happens automatically if you're using Docker It is recommended to keep a backup of your config file. You can download it under Config menu, or use the Cloud Backup feature. You can make use of YAML features, like anchors, comments, multi-line strings, etc to reuse attributes and keep your config file readable Once you have finished configuring your dashboard, you can choose to disable UI config if you wish All fields are optional, unless otherwise stated. The following file provides a reference of all supported configuration options.","s":"Tips","u":"/docs/configuring","h":"#tips","p":376},{"i":383,"t":"pageInfo - Header text, footer, title, navigation, etc navLinks - Links to display in the navigation bar pages - List of additional config files, for multi-page dashboards appConfig - Main application settings webSearch - Configure web search engine options hideComponents - Show/ hide page components auth - Built-in authentication setup users - List or users (for simple auth) keycloak - Auth config for Keycloak headerAuth - Auth config for HeaderAuth sections - List of sections displayData - Section display settings show/hideForKeycloakUsers - Set user controls icon - Icon for a section items - List of items icon - Icon for an item displayData - Item display settings show/hideForKeycloakUsers - Set user controls widgets - List of widgets Notes Editing Config through the UI About YAML Config Saving Methods Preventing Changes Example","s":"Contents","u":"/docs/configuring","h":"#contents","p":376},{"i":385,"t":"Field Type Required Description pageInfo object Required Basic meta data like title, description, nav bar links, footer text. See pageInfo appConfig object Optional Settings related to how the app functions, including API keys and global styles. See appConfig sections array Required An array of sections, each containing an array of items, which will be displayed as links. See section pages array Optional An array additional config files, used for multi-page dashboards. See pages ⬆️ Back to Top","s":"Top-Level Fields","u":"/docs/configuring","h":"#top-level-fields","p":376},{"i":387,"t":"Field Type Required Description title string Required Your dashboard title, displayed in the header and browser tab description string Optional Description of your dashboard, also displayed as a subtitle navLinks array Optional Optional list of a maximum of 6 links, which will be displayed in the navigation bar. See navLinks footer string Optional Text to display in the footer. When omitted, no footer is rendered. Supports inline HTML (sanitized before render) logo string Optional The path to an image to display in the header (to the right of the title). This can be either local, where / is the root of ./public, or any remote image, such as https://i.ibb.co/yhbt6CY/dashy.png. It's recommended to scale your image down, so that it doesn't impact load times favicon string Optional URL or path to a custom favicon shown in the browser tab. Can be absolute (https://...), root-relative (/icons/x.png), or a data: URI color string Optional Theme colour applied to the browser chrome (mobile address bar). Any valid CSS color (e.g. #ff00a7) is accepted ⬆️ Back to Top","s":"PageInfo","u":"/docs/configuring","h":"#pageinfo","p":376},{"i":389,"t":"Field Type Required Description title string Required The text to display on the link button path string Required The URL to navigate to when clicked. Can be relative (e.g. /about) or absolute (e.g. https://example.com or http://192.168.1.1) target string Optional The opening method (external links only). Can be either newtab, sametab, newwindow, top or parent. Defaults to newtab ⬆️ Back to Top","s":"pageInfo.navLinks (optional)","u":"/docs/configuring","h":"#pageinfonavlinks-optional","p":376},{"i":391,"t":"Field Type Required Description name string Required A unique name for that page path string Required The path (local or remote) to the config file to use. For files located within /user-data, you only need to specify filename, for externally hosted files you must include the full URL For more info, see theMulti-Page docs ⬆️ Back to Top","s":"pages[] (optional)","u":"/docs/configuring","h":"#pages-optional","p":376},{"i":393,"t":"Field Type Required Description language string Optional The 2 (or 4-digit) ISO 639-1 code for your language, e.g. en or en-GB. This must be a language that the app has already been translated into. If your language is unavailable, Dashy will fallback to English. By default Dashy will attempt to auto-detect your language, although this may not work on some privacy browsers. startingView enum Optional Which view to land on when visiting /. One of home, minimal or workspace. Defaults to home. Applied at runtime, so no rebuild is needed. You can always switch views from the UI. (Legacy value default is accepted as an alias for home.) defaultOpeningMethod enum Optional The default opening method for items, if no target is specified for a given item. Can be either newtab, sametab, modal, workspace, clipboard, top or parent. Defaults to newtab statusCheck boolean Optional When set to true, Dashy will ping each of your services and display their status as a dot next to each item. This can be overridden by setting statusCheck under each item. Defaults to false statusCheckInterval number Optional The number of seconds between checks. If set to 0 then service will only be checked on initial page load, which is usually the desired functionality. If value is less than 10 you may experience a hit in performance. Defaults to 0 statusCheckAccessibility boolean Optional When set to true, status indicators will use distinct shapes to indicate status for color-blind users. Defaults to false pingCheckEnabled boolean Optional When set to true, Dashy will make a real ICMP ping to each of your hosts and display their status as a dot next to each item. This can be overridden by setting pingCheckEnabled under each item. Defaults to false pingCheckInterval number Optional The number of seconds between checks. If set to 0 then service will only be checked on initial page load. The value is limited to 5 seconds min to prevent performance issues. Defaults to 0 pingCheckAccessibility boolean Optional When set to true, status indicators will use distinct shapes to indicate status for color-blind users. Defaults to false pingCheckCount number Optional The number of ping ICMP packets sent to the host per check. This can be overridden by setting pingCheckCount under each item. If set to 0 then 3 packets will be sent. The value is limited to 5 packets max to prevent performance issues. Defaults to 0 pingCheckTimeout number Optional The number of milliseconds to wait for a ping to complete. This can be overriden by setting pingCheckTimeout under each item. If set to 0 then it uses the same value as pingCheckInterval but converted in milliseconds. The value is limited to 1000 ms per pingCheckCount to prevent performance issues. Defaults to 0 webSearch object Optional Configuration options for the web search feature, set your default search engine, opening method or disable web search. See webSearch backgroundImg string Optional Path to an optional full-screen app background image. This can be either remote (http) or local (relative to /app/public/item-icons/ inside the container). Note that this will slow down initial load enableFontAwesome boolean Optional If set to true font-awesome will be loaded, if set to false they will not be. if left blank font-awesome will be enabled only if required by 1 or more icons enableMaterialDesignIcons boolean Optional If set to true mdi icons will be loaded, if set to false they will not be. Where true is enabled, if left blank material design icons will be enabled only if required by 1 or more icons fontAwesomeKey string Optional If you have a font-awesome key, then you can use it here and make use of premium icons. It is a 10-digit alpha-numeric string from you're FA kit URL (e.g. 13014ae648) faviconApi enum Optional Only applicable if you are using favicons for item icons. Specifies which service to use to resolve favicons. Set to local to do this locally, without using an API. Services running locally will use this option always. Available options are: local, allesedv, iconhorse, faviconkit, duckduckgo, yandex, google, besticon, webmasterapi and mcapi. Defaults to allesedv. See Icons for more info auth object Optional All settings relating to user authentication. See auth defaultIcon string Optional An icon to be applied to any items, which don't already have an icon set. See Icon Docs for more info layout enum Optional Layout for homepage, one of auto, horizontal, vertical or masonry. Defaults to auto. Specifies the layout and direction of how sections are positioned on the home screen. auto uses a responsive CSS grid where each section's footprint is controlled by displayData.cols and displayData.rows. masonry uses a responsive grid where heights follow content, so shorter sections flow into the gaps left by taller neighbours (rows is ignored, cols still controls width). This can also be modified and overridden from the UI. iconSize enum Optional The size of link items / icons. Can be either small, medium, or large. Defaults to medium. This can also be set directly from the UI. colCount number Optional The number of columns of sections displayed on the homepage, using the default view. Should be in integer between 1 and 8. Note that by default this is applied responsively, based on current screen size, and specifying a value here will override this behavior, which may not be desirable. contentMaxWidth string or number Optional Sets the max width of the main sections area on the homepage, overriding the responsive default. Can be a percentage, or any CSS unit theme string Optional The default theme for first load (you can change this later from the UI) dayTheme string Optional Theme to apply when the OS is set to light mode. Overrides theme on initial load nightTheme string Optional Theme to apply when the OS is set to dark mode. Overrides theme on initial load cssThemes string[] Optional An array of custom theme names which can be used in the theme switcher dropdown customColors object Optional Enables you to apply a custom color palette to any given theme. Use the theme name (lowercase) as the key, for an object including key-value-pairs, with the color variable name as keys, and 6-digit hex code as value. See Theming for more info externalStyleSheet string or string[] Optional Either a URL to an external stylesheet or an array or URLs, which can be applied as themes within the UI customCss string Optional Raw CSS that will be applied to the page. This can also be set from the UI. Please minify it first. hideComponents object Optional A list of key page components (header, nav, search, settings) that are present by default, but can be removed using this option. See appConfig.hideComponents enableMultiTasking boolean Optional If set to true, will keep apps open in the background when in the workspace view. Useful for quickly switching between multiple sites, and preserving their state, but comes at the cost of performance. workspaceLandingUrl string Optional The URL or an app, service or website to launch when the workspace view is opened, before another service has been launched preventWriteToDisk boolean Optional If set to true, users will be prevented from saving config changes to disk through the UI preventLocalSave boolean Optional If set to true, users will be prevented from applying config changes to local storage disableConfiguration boolean Optional If set to true, no users will be able to view or edit the config through the UI disableConfigurationForNonAdmin boolean Optional If set to true, only admin users will be able to view or edit the config through the UI. disableConfiguration must not be set to true. widgetsAlwaysUseProxy boolean Optional If set to true, requests made by widgets will always be proxied, same as setting useProxy: true on each widget. Note that this may break some widgets. showSplashScreen boolean Optional If set to true, a loading screen will be shown. Defaults to false. enableErrorReporting boolean Optional Enable reporting of unexpected errors and crashes. This is off by default, and no data will ever be captured unless you explicitly enable it. Turning on error reporting helps previously unknown bugs get discovered and fixed. Dashy uses Sentry for error reporting. Defaults to false. sentryDsn boolean Optional If you need to monitor errors in your instance, then you can use Sentry to collect and process bug reports. Sentry can be self-hosted, or used as SaaS, once your instance is setup, then all you need to do is pass in the DSN here, and enable error reporting. You can learn more on the Sentry DSN Docs. Note that this will only ever be used if enableErrorReporting is explicitly enabled. disableSmartSort boolean Optional For the most-used and last-used app sort functions to work, a basic open-count is stored in local storage. If you do not want this to happen, then disable smart sort here, but you wil no longer be able to use these sort options. Defaults to false. disableUpdateChecks boolean Optional If set to true, Dashy will not check for updates. Defaults to false. enableServiceWorker boolean Optional Service workers cache web applications to improve load times and offer basic offline functionality, and are disabled by default in Dashy. The service worker can sometimes cause older content to be cached, requiring the app to be hard-refreshed. If you do not want SW functionality, or are having issues with caching, set this property to false to disable all service workers. enableAuthProxyCompat boolean Optional Only relevant when enableServiceWorker is true and when Dashy sits behind an authentication proxy (such as Cloudflare Zero Trust or Authelia). Set this to true to detect that redirect on load, unregister the service worker, and reload so re-authentication can proceed. Defaults to false. disableContextMenu boolean Optional If set to true, the custom right-click context menu will be disabled. Defaults to false. ⬆️ Back to Top","s":"appConfig (optional)","u":"/docs/configuring","h":"#appconfig-optional","p":376},{"i":395,"t":"note Since the auth is initiated in the main app entry point (for security), a rebuild is required to apply changes to the auth configuration. Run yarn build in the root directory, then restart the server. warning Built-in auth should not be used for security-critical applications, or if your Dashy instance is publicly accessible. For these, it is recommended to use an alternate authentication method. Field Type Required Description users array Optional An array of objects containing usernames and hashed passwords. If this is not provided, then authentication will be off by default, and you will not need any credentials to access the app. See appConfig.auth.users. Note this method of authentication is handled on the client side, so for security critical situations, it is recommended to use an alternate authentication method. enableKeycloak boolean Optional If set to true, then authentication using Keycloak will be enabled. Note that you need to have an instance running, and have also configured auth.keycloak. Defaults to false keycloak object Optional Config options to point Dashy to your Keycloak server. Requires enableKeycloak: true. See auth.keycloak for more info enableHeaderAuth boolean Optional If set to true, then authentication using HeaderAuth will be enabled. Note that you need to have your web server/reverse proxy running, and have also configured auth.headerAuth. Defaults to false headerAuth object Optional Config options to point Dashy to your headers for authentication. Requires enableHeaderAuth: true. See auth.headerAuth for more info enableOidc boolean Optional If set to true, then authentication using OIDC will be enabled. Note that you need to have a configured OIDC server and configure it with auth.oidc. Defaults to false oidc object Optional Config options to point Dash to your OIDC configuration. Request enableOidc: true. See auth.oidc for more info enableGuestAccess boolean Optional When set to true, an unauthenticated user will be able to access the dashboard, with read-only access, without having to login. Requires auth.users to be configured. Defaults to false. For more info, see the Authentication Docs ⬆️ Back to Top","s":"appConfig.auth (optional)","u":"/docs/configuring","h":"#appconfigauth-optional","p":376},{"i":397,"t":"Field Type Required Description user string Required Username to log in with hash string Required A SHA-256 hashed password type string Optional The user type, either admin or normal ⬆️ Back to Top","s":"appConfig.auth.users (optional)","u":"/docs/configuring","h":"#appconfigauthusers-optional","p":376},{"i":399,"t":"Field Type Required Description serverUrl string Required The URL (or URL/ IP + Port) where your keycloak server is running realm string Required The name of the realm (must already be created) that you want to use clientId string Required The Client ID of the client you created for use with Dashy idpHint string Optional The alias of an Identity Provider configured in your realm. If set, Keycloak will skip its login page and redirect straight to that external IdP legacySupport boolean Optional If using Keycloak 17 or older, then set this to true ⬆️ Back to Top","s":"appConfig.auth.keycloak (optional)","u":"/docs/configuring","h":"#appconfigauthkeycloak-optional","p":376},{"i":401,"t":"Field Type Required Description userHeader string Optional The HTTP header name containing the authenticated username (default: Remote-User). Case insensitive proxyWhitelist array Required An array of upstream proxy server IPs to accept authenticated requests from ⬆️ Back to Top","s":"appConfig.auth.headerAuth (optional)","u":"/docs/configuring","h":"#appconfigauthheaderauth-optional","p":376},{"i":403,"t":"Field Type Required Description clientId string Required The client id registered in the OIDC server endpoint string Required The URL of the OIDC server that should be used. adminRole string Optional The role that will be considered as admin. adminGroup string Optional The group that will be considered as admin. scope string Required The scope(s) to request from the OIDC provider enableSilentRenew boolean Optional If set to true, your session is silently renewed in the background before it expires (only works for providers which support the offline_access scope) allowedIssuers array Optional List of issuer URLs to accept tokens from. Needed for multi-tenant providers (e.g. Microsoft Entra) where the token issuer differs from the configured endpoint. If unset, the issuer from the discovery document is used ⬆️ Back to Top","s":"appConfig.auth.oidc (optional)","u":"/docs/configuring","h":"#appconfigauthoidc-optional","p":376},{"i":405,"t":"Field Type Required Description disableWebSearch boolean Optional Web search is enabled by default, but can be disabled by setting this property to true. Defaults to false searchEngine string Optional Set the key name for your search engine. Can also use a custom engine by setting this property to custom. See the searching docs for the full list of built-in keys. Defaults to duckduckgo customSearchEngine string Optional You can also use a custom search engine, or your own self-hosted instance. This requires searchEngine: custom to be set. Then add the URL of your service, with GET query string included here openingMethod string Optional Set your preferred opening method for search results: newtab, sametab, workspace. Defaults to newtab searchBangs object Optional A key-value-pair set of custom search bangs for redirecting query to a specific app or search engine. The key of each should be the bang you will type (typically starting with /, ! or :), and value is the destination, either as a search engine key (e.g. reddit) or a URL with search parameters (e.g. https://en.wikipedia.org/w/?search=) openUrlsDirectly boolean Optional If true, queries that look like URLs will be opened directly instead of searched. Defaults to false ⬆️ Back to Top","s":"appConfig.webSearch (optional)","u":"/docs/configuring","h":"#appconfigwebsearch-optional","p":376},{"i":407,"t":"Field Type Required Description hideHeading boolean Optional If set to true, the page title & sub-title will not be visible. Defaults to false hideNav boolean Optional If set to true, the navigation menu will not be visible. Defaults to false hideSearch boolean Optional If set to true, the search bar will not be visible. Defaults to false hideSettings boolean Optional If set to true, the settings menu will be initially collapsed. Defaults to false ⬆️ Back to Top","s":"appConfig.hideComponents (optional)","u":"/docs/configuring","h":"#appconfighidecomponents-optional","p":376},{"i":409,"t":"Field Type Required Description name string Required The title for the section icon string Optional An single icon to be displayed next to the title. See section.icon items array Optional An array of items to be displayed within the section. See item. Sections must include either 1 or more items, or 1 or more widgets. widgets array Optional An array of widgets to be displayed within the section. See widget displayData object Optional Meta-data to optionally override display settings for a given section. See displayData ⬆️ Back to Top","s":"section","u":"/docs/configuring","h":"#section","p":376},{"i":411,"t":"Field Type Required Description title string Required The text to display/ title of a given item. Max length 18 description string Optional Additional info about an item, which is shown in the tooltip on hover, or visible on large tiles url string Optional The URL / location of web address for when the item is clicked localUrl string Optional An alternative URL (e.g. a LAN address) that is preferred whenever it is reachable from your browser localUrlTimeout number Optional Milliseconds to wait for the localUrl reachability probe before giving up and using url. Between 300 and 5000. Defaults to 1500 localUrlCheckInterval number Optional Seconds between background re-checks of localUrl. 0 means only check on page load and when the browser tab regains focus. 300 max. Defaults to 0 icon string Optional The icon for a given item. Can be a font-awesome icon, favicon, remote URL or local URL. See item.icon target string Optional The opening method for when the item is clicked, either newtab, sametab, modal, workspace, clipboard, top or parent. Where newtab will open the link in a new tab, sametab will open it in the current tab, and modal will open a pop-up modal, workspace will open in the Workspace view and clipboard will copy the URL to system clipboard (but not launch app). Defaults to newtab hotkey number Optional Give frequently opened applications a numeric hotkey, between 0 - 9. You can then just press that key to launch that application. tags string[] Optional A list of tags, which can be used for improved search statusCheck boolean Optional When set to true, Dashy will ping the URL associated with the current service, and display its status as a dot next to the item. The value here will override appConfig.statusCheck so you can turn off or on checks for a given service. Defaults to appConfig.statusCheck, falls back to false statusCheckUrl string Optional If you've enabled statusCheck, and want to use a different URL to what is defined under the item, then specify it here statusCheckHeaders object Optional If you're endpoint requires any specific headers for the status checking, then define them here statusCheckAllowInsecure boolean Optional By default, any request to insecure content will be blocked. Setting this option to true will disable the rejectUnauthorized option, enabling you to ping non-HTTPS services for the current item. Defaults to false statusCheckAcceptCodes string Optional If your service's response code is anything other than 2xx, then you can opt to specify an alternative success code. E.g. if you expect your server to return 403, but still want the status indicator to be green, set this value to 403 statusCheckMaxRedirects number Optional If your service redirects to another page, and you would like status checks to follow redirects, then specify the maximum number of redirects here. Defaults to 0 / will not follow redirects pingCheckEnabled boolean Optional When set to true, Dashy will make a real ICMP ping to each of your hosts and display their status as a dot next to each item. The value here will override appConfig.pingCheckEnabled so you can turn off or on pings for a given host. Defaults to appConfig.pingCheckEnabled, falls back to false pingCheckHost string Optional FQDN hostname or IPV4 address of the host to ping. If empty or not defined then it uses the item's url host and if it is also invalid then no ping is performed. Defaults to undefined pingCheckInterval number Optional The number of seconds between checks. The value will override appConfig.pingCheckInterval so you can adjust the ping interval per host. Defaults to appConfig.pingCheckInterval, falls back to 0 pingCheckCount number Optional The number of ping ICMP packets sent to the host per check. The value will override appConfig.pingCheckCount so you can decide to send a different number of ping ICMP packets per host. Defaults to appConfig.pingCheckCount, falls back to 0 pingCheckTimeout number Optional The number of milliseconds to wait for a ping to complete. The value will override appConfig.pingCheckTimeout so you can adjust the timeout per host. Defaults to appConfig.pingCheckTimeout, falls back to 0 color string Optional An optional color for the text and font-awesome icon to be displayed in. Note that this will override the current theme and so may not display well rel string Optional The value of the rel attribute for the link. Useful for specifying the relationship between the target link/document and Dashy. Defaults to noopener noreferrer backgroundColor string Optional An optional background fill color for the that given item. Again, this will override the current theme and so might not display well against the background provider string Optional The name of the provider for a given service, useful for when including hosted apps. In some themes, this is visible under the item name displayData object Optional Meta-data to optionally override display settings for a given item. See displayData subItems array Optional An optional list of nested sub-items, rendered as smaller icons within the parent. Each sub-item supports title, url, icon, target, color and backgroundColor ⬆️ Back to Top","s":"section.item","u":"/docs/configuring","h":"#sectionitem","p":376},{"i":413,"t":"Field Type Required Description hideForUsers string[] Optional Current item will be visible to all users, except for those specified in this list showForUsers string[] Optional Current item will be hidden from all users, except for those specified in this list hideForGuests boolean Optional Current item will be visible for logged in users, but not for guests (see appConfig.enableGuestAccess). Defaults to false hideForKeycloakUsers object Optional Current item will be visible to all keycloak users, except for those configured via these groups and roles. See hideForKeycloakUsers showForKeycloakUsers object Optional Current item will be hidden from all keycloak users, except for those configured via these groups and roles. See showForKeycloakUsers hideFromWorkspace boolean Optional Current item will be visible in the default view but not in the Workspace view sidebar. Defaults to false hideFromHomepage boolean Optional If true, item is hidden from the home and minimal views until matched by a search. Still visible in workspace, edit mode and single-section view. Defaults to false ⬆️ Back to Top","s":"item.displayData (optional)","u":"/docs/configuring","h":"#itemdisplaydata-optional","p":376},{"i":415,"t":"Field Type Required Description type string Required The widget type. See Widget Docs for full list of supported widgets options object Optional Some widgets accept either optional or required additional options. Again, see the Widget Docs for full list of options updateInterval number Optional You can keep a widget constantly updated by specifying an update interval, in seconds. See Continuous Updates Docs for more info useProxy boolean Optional Some widgets make API requests to services that are not CORS-enabled. For these instances, you will need to route requests through a proxy, Dashy has a built in CORS-proxy, which you can use by setting this option to true. Defaults to false. See the Proxying Requests Docs for more info timeout number Optional Request timeout in milliseconds, defaults to ½ a second (500) ignoreErrors boolean Optional Prevent an error message being displayed, if a network request or something else fails. Useful for false-positives label string Optional Add custom label to a given widget. Useful for identification, if there are multiple of the same type of widget in a single section ⬆️ Back to Top","s":"section.widgets (optional)","u":"/docs/configuring","h":"#sectionwidgets-optional","p":376},{"i":417,"t":"Field Type Required Description sortBy string Optional The sort order for items within the current section. By default items are displayed in the order in which they are listed in within the config. The following sort options are supported: most-used (most opened apps first), last-used (the most recently used apps), alphabetical, reverse-alphabetical, random and default collapsed boolean Optional If true, the section will be collapsed initially, and will need to be clicked to open. Useful for less regularly used, or very long sections. Defaults to false cutToHeight boolean Optional By default, sections will fill available space. Set this option to true to match section height with content height rows number Optional Height of the section, specified as the number of rows it should span vertically, e.g. 2. Defaults to 1. Max is 5. Applies to the default auto layout; ignored by masonry (where heights follow content) and the flex-based horizontal/vertical layouts. cols number Optional Width of the section, specified as the number of columns the section should span horizontally, e.g. 2. Defaults to 1. Max is 5. Will be clamped to the page's active column count so that a section never exceeds the available grid width. itemSize string Optional Specify the size for items within this group, either small, medium or large. Note that this will override any settings specified through the UI color string Optional A custom accent color for the section, as a hex code or HTML color (e.g. #fff) customStyles string Optional Custom CSS properties that should be applied to that section, e.g. border: 2px dashed #ff0000; sectionLayout string Optional Specify which CSS layout will be used to responsively place items. Can be either auto (which uses flex layout), or grid. Defaults to auto. Setting itemCountX or itemCountY below will also switch the section to grid layout automatically itemCountX number Optional Number of items per row / horizontally. If not set, it will be calculated automatically based on available space. Setting this switches the section to grid layout. Must be a whole number between 1 and 12; values above 8 rely on the grid's responsive column sizing (no explicit minimum-width rule) itemCountY number Optional Number of explicit rows before items flow into implicit rows. Setting this switches the section to grid layout. Must be a whole number between 1 and 12. Row heights size to their content (section heights follow content in the masonry layout) hideForUsers string[] Optional Current section will be visible to all users, except for those specified in this list showForUsers string[] Optional Current section will be hidden from all users, except for those specified in this list hideForGuests boolean Optional Current section will be visible for logged in users, but not for guests (see appConfig.enableGuestAccess). Defaults to false hideForKeycloakUsers object Optional Current section will be visible to all keycloak users, except for those configured via these groups and roles. See hideForKeycloakUsers showForKeycloakUsers object Optional Current section will be hidden from all keycloak users, except for those configured via these groups and roles. See showForKeycloakUsers hideFromWorkspace boolean Optional Current section will be visible in the default view but not in the Workspace view sidebar. Defaults to false ⬆️ Back to Top","s":"section.displayData (optional)","u":"/docs/configuring","h":"#sectiondisplaydata-optional","p":376},{"i":419,"t":"Field Type Required Description icon string Optional The icon for a given item or section. See Icon Docs for all available supported icon types, including: auto-fetched favicons, generative icons, emoji icons, home-lab service logos, font-awesome, simple-icons, material icons, selfh.st icons, and icons specified by URL ⬆️ Back to Top","s":"section.icon and section.item.icon","u":"/docs/configuring","h":"#sectionicon-and-sectionitemicon","p":376},{"i":421,"t":"Field Type Required Description groups string[] Optional Current Section or Item will be hidden or shown based on the user having any of the groups in this list roles string[] Optional Current Section or Item will be hidden or shown based on the user having any of the roles in this list ⬆️ Back to Top","s":"section.displayData.hideForKeycloakUsers, section.displayData.showForKeycloakUsers, item.displayData.hideForKeycloakUsers and item.displayData.showForKeycloakUsers","u":"/docs/configuring","h":"#sectiondisplaydatahideforkeycloakusers-sectiondisplaydatashowforkeycloakusers-itemdisplaydatahideforkeycloakusers-and-itemdisplaydatashowforkeycloakusers","p":376},{"i":424,"t":"Config can be modified directly through the UI, and then written to disk, or applied locally. This can be done wither with the raw config editor (introduced in V 0.6.5 / #3), or the interactive editor (introduced in V 1.8.9 / #298). Interactive Editor JSON Editor","s":"Editing Config through the UI","u":"/docs/configuring","h":"#editing-config-through-the-ui","p":376},{"i":426,"t":"If you're new to YAML, it's pretty straight-forward. The format is exactly the same as that of JSON, but instead of using curly braces, structure is denoted using whitespace. This quick guide should get you up to speed in a few minutes, for more advanced topics take a look at this Wikipedia article.","s":"About YAML","u":"/docs/configuring","h":"#about-yaml","p":376},{"i":428,"t":"When updating the config through the JSON editor in the UI, you have two save options: Local or Write to Disk. Changes saved locally will only be applied to the current user through the browser, and will not apply to other instances - you either need to use the cloud sync feature, or manually update the conf.yml file. On the other-hand, if you choose to write changes to disk, then your main conf.yml file will be updated, and changes will be applied to all users, and visible across all devices. For this functionality to work, you must be running Dashy with using the Docker container, or the Node server. A backup of your current configuration will also be saved in the same directory.","s":"Config Saving Methods","u":"/docs/configuring","h":"#config-saving-methods","p":376},{"i":430,"t":"If you have authentication set up, then any user who is not an admin (with type: admin) will not be able to write changes to disk. You can also prevent changes from any user being written to disk, using preventWriteToDisk. Or prevent any changes from being saved locally in browser storage, using preventLocalSave. To disable all UI config features, set disableConfiguration. Alternatively you can disable UI config features for all non Admin users by setting disableConfigurationForNonAdmin to true.","s":"Preventing Changes","u":"/docs/configuring","h":"#preventing-changes","p":376},{"i":432,"t":"--- pageInfo: title: Home Lab sections: # An array of sections - name: Section 1 - Getting Started items: # An array of items - title: GitHub description: Source code and documentation on GitHub icon: fab fa-github url: https://github.com/Lissy93/dashy - title: Issues description: View currently open issues, or raise a new one icon: fas fa-bug url: https://github.com/Lissy93/dashy/issues - title: Demo description: A live demo icon: far fa-rocket url: https://dashy-demo-1.netlify.app - name: Section 2 - Local Services items: - title: Firewall icon: favicon url: http://192.168.1.1/ - title: Game Server icon: https://i.ibb.co/710B3Yc/space-invader-x256.png url: http://192.168.130.1/ For more example config files, see: this gist If you need any help, feel free to Raise an Issue or Start a Discussion Happy Configuring 🤓🔧 ⬆️ Back to Top","s":"Example","u":"/docs/configuring","h":"#example","p":376},{"i":434,"t":"First off, thank you for considering contributing towards Dashy! 🙌 There are several ways that you can help out, and any contributions, however small will always be very much appreciated. You will be appropriately credited in the readme - huge thank you to everyone who has helped so far 💞","s":"Contributing","u":"/docs/contributing","h":"","p":433},{"i":436,"t":"Help improve Dashy by taking a very short, 6-question survey. This will give me a better understanding of what is important to you, so that I can make Dashy better in the future :)","s":"Take a 2-minute survey","u":"/docs/contributing","h":"#take-a-2-minute-survey","p":433},{"i":438,"t":"Dashy now has a Showcase where you can show off a screenshot of your dashboard, and get inspiration from other users (and I really love seeing how people are using Dashy). To submit your dashboard, either open a PR or raise an issue.","s":"Share your dashboard","u":"/docs/contributing","h":"#share-your-dashboard","p":433},{"i":440,"t":"Donations help to cover server costs, development time and caffeine ;) Don't feel any pressure to donate anything, as Dashy and my other projects will always be 100% free, for everyone, for ever. Sponsoring will give you several perks - for $1 / £0.75 per month, you'll get a sponsor badge on your profile, be credited on the Dashy's readme, with a link to your website/ profile/ socials, get priority support, have your feature ideas implemented, plus lots more. For more info, see @Lissy93's Sponsor Page. You can also send a one-off small contribution using crypto [![Donate with BTC](https://en.cryptobadges.io/badge/big/3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC)](https://en.cryptobadges.io/donate/3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC)[![Donate with Ethereum](https://en.cryptobadges.io/badge/big/0x0fc98cBf8bea932B4470C46C0FbE1ed1f6765017)](https://en.cryptobadges.io/donate/0x0fc98cBf8bea932B4470C46C0FbE1ed1f6765017) BTC: 3853bSxupMjvxEYfwGDGAaLZhTKxB2vEVC ETH: 0x0fc98cBf8bea932B4470C46C0FbE1ed1f6765017 / aliciasykes.eth XMR: 471KZdxb6N63aABR4WYwMRjTVkc1p1x7wGsUTEF7AMYzL8L94A5pCuYWkosgJQ5Ze8Y2PscVCGZFJa3hDPg6MaDq47GUm8r LTC: MAuck6Ea1qaNihwKfXutkR1R6BorMth86H ZEC: t1bw1SefijsXRDQVxC9w64XsRK8hBhtQohQ","s":"Make a small donation","u":"/docs/contributing","h":"#make-a-small-donation","p":433},{"i":442,"t":"Bug reports helps me to discover bugs I was unaware of, and then fix them, in order to make Dashy more reliable long term. This is a simple, yet really helpful step you can take to help improve Dashy. Sentry is an open source error tracking and performance monitoring tool, which enables the identification any errors which occur in the production app (only if you enable it). To enable error reporting: appConfig: enableErrorReporting: true All reporting is disabled by default, and no data will ever be sent to any external endpoint without your explicit consent. All statistics are anonymized and stored securely. For more about privacy and security, see the Sentry Security Docs.","s":"Enable Anonymous Bug Reports","u":"/docs/contributing","h":"#enable-anonymous-bug-reports","p":433},{"i":444,"t":"If you speak another language, then adding translations will help make Dashy available to non-native English speakers. This is a very quick and easy task, as all application text is located in locales/en.json, so adding a new language is as simple as copying this file and translating the values. You don't have to translate it all, as any missing attributes will just fallback to English. For a full tutorial, see the Multi-Language Support Docs.","s":"Add Translations","u":"/docs/contributing","h":"#add-translations","p":433},{"i":446,"t":"Contributing to the code or docs is super helpful. You can fix a bug, add a new feature or improve an existing one. If you've built your own custom widget, theme or view, consider sharing it in a PR. I've written several guides to help you get started, and the steps for setting up the development environment are outlined in the Development Docs. Feel free to ask if you have any questions.","s":"Submit a PR","u":"/docs/contributing","h":"#submit-a-pr","p":433},{"i":448,"t":"Found a typo, or something that isn't as clear as it could be? Maybe I've missed something off altogether, or you hit a roadblock that took you a while to figure out. Submitting a pull request to add to or improve the documentation will help future users get Dashy up and running more easily. All content is located either in the ./README.md or /docs/ directory, and synced to the Wiki and website using a GH action.","s":"Improve the Docs","u":"/docs/contributing","h":"#improve-the-docs","p":433},{"i":450,"t":"If you've found a bug, then please do raise it as an issue. This will help me know if there's something that needs fixing. Try and include as much detail as possible, such as your environment, steps to reproduce, any console output and maybe an example screenshot or recording if necessary.","s":"Raise a bug","u":"/docs/contributing","h":"#raise-a-bug","p":433},{"i":452,"t":"I've enabled the discussion feature on GitHub, here you can share tips and tricks, useful information, or your dashboard. You can also ask questions, and offer basic support to other users.","s":"Join the discussion","u":"/docs/contributing","h":"#join-the-discussion","p":433},{"i":454,"t":"BountySource is a platform for sponsoring the development of certain features on open source projects. If there is a feature you'd like implemented into Dashy, but either isn't high enough priority or is deemed to be more work than it's worth, then you can instead contribute a bounty towards it's development. You won't pay a penny until your proposal is fully built, and you are satisfied with the result. This helps support the developers, and makes Dashy better for everyone.","s":"Request a feature via BountySource","u":"/docs/contributing","h":"#request-a-feature-via-bountysource","p":433},{"i":456,"t":"Dashy is still a relatively young project, and as such not many people know of it. It would be great to see more users, and so it would be awesome if you could consider sharing with your friends or on social platforms.","s":"Spread the word","u":"/docs/contributing","h":"#spread-the-word","p":433},{"i":458,"t":"Dashy is on the following platforms, and if you could spare a few seconds to give it an upvote or review, this will also help new users discover Dashy","s":"Star, Upvote or Leave a Review","u":"/docs/contributing","h":"#star-upvote-or-leave-a-review","p":433},{"i":460,"t":"If you've enjoyed Dashy, you can follow the me to get updates about other projects that I am working on. If you like, you could also consider subscribing to my mailing list for occasional blog post updates.","s":"Follow for More","u":"/docs/contributing","h":"#follow-for-more","p":433},{"i":462,"t":"For a full list of Dashy's contributors, see the Credits Page","s":"Contributors","u":"/docs/contributing","h":"#contributors","p":433},{"i":464,"t":"](https://seladb.github.io/StarTrack-js/#/preload?r=lissy93,dashy)","s":"Star-Gazers Over Time","u":"/docs/contributing","h":"#star-gazers-over-time","p":433},{"i":470,"t":"This app definitely wouldn't have been quite so possible without the making use of the following package and components. Full credit and big kudos to their respective authors, who've done an amazing job in building and maintaining them. Full credit and big kudos to their respective authors, who've done an amazing job in building and maintaining them","s":"Dependencies","u":"/docs/credits","h":"#dependencies","p":465},{"i":472,"t":"At it's core, the application uses Vue.js, as well as it's services with VueX for state management. Styling is done with SCSS, the app is written in TypeScript and JavaScript, and bundled with Vite. Linting is done with ESLint and Prettier, both following the AirBnB Styleguide. The config is defined in YAML, with a simple Node.js server to serve up the static app and the optional API endpoints, and container deployment is done with Docker.","s":"Core","u":"/docs/credits","h":"#core","p":465},{"i":474,"t":"crypto-js - Encryption implementations by @evanvosberg and community MIT ajv - JSON schema Validator by @epoberezkin and community MIT i18n - Internationalization plugin by @kazupon and community MIT frappe-charts - Lightweight charting library by @frappe MIT","s":"Utilities","u":"/docs/credits","h":"#utilities","p":465},{"i":476,"t":"vue-select - Dropdown component by @sagalbot MIT","s":"Frontend Components","u":"/docs/credits","h":"#frontend-components","p":465},{"i":478,"t":"Would you like to be listed here? Whatever your skill set, Dashy needs people like you to help support future development. Check out the Contributing Page for ways that you can get involved. Huge thank you to everyone who has already contributed! 💖","s":"You","u":"/docs/credits","h":"#you","p":465},{"i":480,"t":"Welcome to Dashy, so glad you're here :) Deployment is super easy, and there are several methods available depending on what type of system you're using. If you're self-hosting, then deploying with Docker (or similar container engine) is the recommended approach.","s":"Deployment","u":"/docs/deployment","h":"","p":479},{"i":482,"t":"If you want to skip the fuss, and get straight down to it, then you can spin up a new instance of Dashy by running: docker run -p 8080:8080 lissy93/dashy See Management Docs for info about securing, monitoring, updating, health checks, auto starting, web server configuration, etc Once you've got Dashy up and running, you'll want to configure it with your own content, for this you can reference the configuring docs.","s":"Quick Start","u":"/docs/deployment","h":"#quick-start","p":479},{"i":484,"t":"Docker Docker Run Docker Compose Podman Bare Metal Build from Source Pre-Built Release Self-Hosted Operating Systems TrueNAS SCALE Proxmox VE Unraid Synology NAS NixOS Kubernetes Self-Hosted Platforms Portainer Coolify 1Panel Runtipi Cosmos Cloud CasaOS Umbrel EasyPanel Saltbox Cloud Services Static Hosting Providers Netlify Vercel EdgeOne Pages Cloudflare Pages Firebase Hosting Azure Static Web Apps Container Runtimes Render Railway Fly.io Koyeb Northflank DigitalOcean App Platform Azure Container Apps Google Cloud Run Managed Hosting Hosting with any CDN Other VPS","s":"Deployment Methods","u":"/docs/deployment","h":"#deployment-methods","p":479},{"i":487,"t":"The pre-built Docker image runs on amd64, arm64 and armv7 (armv6 is not supported).","s":"Architecture","u":"/docs/deployment","h":"#architecture","p":479},{"i":489,"t":"CPU: any single core, x86-64 or ARM RAM: Node server idles around ~80-120 MB; 256 MB is comfortable, works in less Disk: ~250 MB for the image + whatever your config/icons need Runs fine on a Pi 3 and up","s":"System Resources","u":"/docs/deployment","h":"#system-resources","p":479},{"i":491,"t":"Requires Node.js (20+) and Yarn","s":"Bare Metal","u":"/docs/deployment","h":"#bare-metal","p":479},{"i":493,"t":"No specific requirements. The built app (without the Node server) is very lightweight and can be served by any static host or CDN. If you're using custom icons or other assets, additional disk space will be needed.","s":"CDN / Cloud Deploy","u":"/docs/deployment","h":"#cdn--cloud-deploy","p":479},{"i":495,"t":"JavaScript is required. Dashy targets browsers with >1% global usage and the last 2 versions of each (via browserslist). In practice, any modern browser works fine. Internet Explorer is not supported. Browser Minimum Version Status Chrome / Chromium 90+ Fully supported Firefox 90+ Fully supported Edge 90+ Fully supported Safari 14+ Mostly Supported Opera 76+ Supported Samsung Internet 15+ Supported Firefox ESR Latest Supported Internet Explorer - Not supported","s":"Browser Support","u":"/docs/deployment","h":"#browser-support","p":479},{"i":498,"t":"Dashy can be compiled and run directly on your machine. For this, you will need both git and Node.js (v20 or newer) installed. Get Code: git clone https://github.com/Lissy93/dashy.git and cd dashy Configuration: Fill in your settings in ./user-data/conf.yml Install dependencies: yarn Build: yarn build Run: yarn start For example, the following steps can be used for Ubuntu/Debian based distros: # Install prerequisites sudo apt update sudo apt install -y curl git # Install Node.js (v24 LTS) from NodeSource, and enable yarn curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - sudo apt install -y nodejs sudo corepack enable # Get the code git clone https://github.com/Lissy93/dashy.git cd dashy # Install dependencies and build yarn yarn build # Run it (serves on http://localhost:4000 by default, change with PORT env var) yarn start # Put all your assets (icons, styles, scripts, fonts, pages, etc) and configs in ./user-data # Then, get started with editing the main config nano user-data/conf.yml Notes: If you're making changes to Dashy's codebase, you can run yarn dev to start the dev server with hot reloading. See the Developing docs for more info. To keep Dashy running in the background, either use yarn pm2-start (instead of yarn start). Or, with systemd, see the systemd service example below","s":"Build from Source","u":"/docs/deployment/bare-metal","h":"#build-from-source","p":496},{"i":500,"t":"We publish GitHub releases for every major and minor (not patch) version of Dashy. Each release includes the pre-compiled app as dashy-[version].tar.gz (plus a SHA256 checksum and SLSA build-provenance attestation). This is useful if you want to run Dashy on bare metal, without the need to compile from source. You'll need Node.js (v20 or newer), curl, tar, and yarn enabled through Corepack. Pick a release from GitHub Releases Download dashy-[version].tar.gz Extract it somewhere permanent, such as /opt/dashy Run yarn install --production --frozen-lockfile Edit user-data/conf.yml Start it with node server For example (replace 4.3.0 with whichever version you want): curl -LO https://github.com/Lissy93/dashy/releases/download/4.3.0/dashy-4.3.0.tar.gz mkdir -p dashy tar -xzf dashy-4.3.0.tar.gz -C dashy cd dashy corepack enable yarn install --production --frozen-lockfile node server Dashy will run on http://localhost:4000 by default. Change this with the PORT env var. You can now start configuring by editing user-data/conf.yml and put any assets you want within user-data.","s":"Pre-Built Release","u":"/docs/deployment/bare-metal","h":"#pre-built-release","p":496},{"i":502,"t":"If you're running Dashy on a Linux server, you can use systemd to start it at boot and restart it if it crashes. This example assumes Dashy is installed in /opt/dashy, and runs it as a dedicated dashy user, so adjust it for your usecase. Run these commands as root: useradd --system --home /opt/dashy --shell /usr/sbin/nologin dashy chown -R dashy:dashy /opt/dashy Create /etc/systemd/system/dashy.service: [Unit] Description=Dashy After=network-online.target Wants=network-online.target [Service] Type=simple User=dashy WorkingDirectory=/opt/dashy Environment=NODE_ENV=production Environment=PORT=4000 ExecStart=/usr/bin/env node server Restart=always RestartSec=10 [Install] WantedBy=multi-user.target Then enable and start it: systemctl daemon-reload systemctl enable --now dashy systemctl status dashy If you installed Dashy somewhere else, update WorkingDirectory.","s":"Systemd Service","u":"/docs/deployment/bare-metal","h":"#systemd-service","p":496},{"i":504,"t":"Each pre-built release includes a SHA256 checksum. This is optional, but it is a quick way to check the file downloaded correctly. curl -LO https://github.com/Lissy93/dashy/releases/download/4.3.0/dashy-4.3.0.tar.gz curl -LO https://github.com/Lissy93/dashy/releases/download/4.3.0/dashy-4.3.0.tar.gz.sha256 sha256sum -c dashy-4.3.0.tar.gz.sha256 You should see dashy-4.3.0.tar.gz: OK.","s":"Checksum Verification","u":"/docs/deployment/bare-metal","h":"#checksum-verification","p":496},{"i":506,"t":"GitHub releases also include a SLSA build-provenance attestation. This lets you verify that the release asset was built by Dashy's GitHub Actions release workflow. Install a recent GitHub CLI, then run: gh attestation verify dashy-4.3.0.tar.gz --repo Lissy93/dashy","s":"Provenance Attestation","u":"/docs/deployment/bare-metal","h":"#provenance-attestation","p":496},{"i":508,"t":"The following is an example script to install or update the latest pre-built release on a Linux server with systemd. It creates a dashy user, installs into /opt/dashy, verifies the SHA256 checksum, installs production dependencies, and starts Dashy as a service. install-dashy.sh #!/usr/bin/env bash set -euo pipefail REPO=\"Lissy93/dashy\" INSTALL_DIR=\"${INSTALL_DIR:-/opt/dashy}\" RUN_USER=\"${RUN_USER:-dashy}\" PORT=\"${PORT:-4000}\" VERSION=\"${DASHY_VERSION:-latest}\" if [ \"$(id -u)\" -ne 0 ]; then echo \"Please run this script as root\" exit 1 fi need_cmd() { command -v \"$1\" >/dev/null 2>&1 || { echo \"Missing required command: $1\" exit 1 } } install_packages() { if command -v apt-get >/dev/null 2>&1; then apt-get update apt-get install -y curl ca-certificates tar gzip coreutils elif command -v dnf >/dev/null 2>&1; then dnf install -y curl ca-certificates tar gzip coreutils nodejs elif command -v yum >/dev/null 2>&1; then yum install -y curl ca-certificates tar gzip coreutils nodejs else echo \"Please install curl, tar, sha256sum and Node.js v20 or newer, then re-run this script.\" exit 1 fi } install_packages need_cmd curl need_cmd tar need_cmd sha256sum need_cmd runuser install_node_apt() { curl -fsSL https://deb.nodesource.com/setup_24.x | bash - apt-get install -y nodejs } if ! command -v node >/dev/null 2>&1; then if command -v apt-get >/dev/null 2>&1; then install_node_apt else echo \"Please install Node.js v20 or newer, then re-run this script.\" exit 1 fi fi NODE_MAJOR=\"$(node -p \"process.versions.node.split('.')[0]\")\" if [ \"$NODE_MAJOR\" -lt 20 ]; then if command -v apt-get >/dev/null 2>&1; then install_node_apt NODE_MAJOR=\"$(node -p \"process.versions.node.split('.')[0]\")\" fi if [ \"$NODE_MAJOR\" -lt 20 ]; then echo \"Node.js v20 or newer is required. Found: $(node -v)\" exit 1 fi fi corepack enable need_cmd yarn case \"$INSTALL_DIR\" in /*) ;; *) echo \"INSTALL_DIR must be an absolute path\"; exit 1 ;; esac if [ \"$INSTALL_DIR\" = \"/\" ]; then echo \"INSTALL_DIR cannot be /\" exit 1 fi if [ \"$VERSION\" = \"latest\" ]; then RELEASE_URL=\"$(curl -fsSLI -o /dev/null -w '%{url_effective}' \"https://github.com/${REPO}/releases/latest\")\" VERSION=\"${RELEASE_URL##*/}\" fi VERSION=\"${VERSION#v}\" ASSET=\"dashy-${VERSION}.tar.gz\" BASE_URL=\"https://github.com/${REPO}/releases/download/${VERSION}\" TMP_DIR=\"$(mktemp -d)\" trap 'rm -rf \"$TMP_DIR\"' EXIT echo \"Downloading Dashy ${VERSION}...\" curl -fL \"${BASE_URL}/${ASSET}\" -o \"${TMP_DIR}/${ASSET}\" curl -fL \"${BASE_URL}/${ASSET}.sha256\" -o \"${TMP_DIR}/${ASSET}.sha256\" ( cd \"$TMP_DIR\" sha256sum -c \"${ASSET}.sha256\" ) if ! id \"$RUN_USER\" >/dev/null 2>&1; then useradd --system --home \"$INSTALL_DIR\" --shell /usr/sbin/nologin \"$RUN_USER\" fi if [ -d \"$INSTALL_DIR/user-data\" ]; then BACKUP_DIR=\"${INSTALL_DIR}.user-data.$(date +%Y%m%d%H%M%S)\" cp -a \"$INSTALL_DIR/user-data\" \"$BACKUP_DIR\" echo \"Backed up user-data to ${BACKUP_DIR}\" fi NEW_DIR=\"${INSTALL_DIR}.new\" OLD_DIR=\"${INSTALL_DIR}.old\" rm -rf \"$NEW_DIR\" \"$OLD_DIR\" mkdir -p \"$NEW_DIR\" tar -xzf \"${TMP_DIR}/${ASSET}\" -C \"$NEW_DIR\" if [ -d \"$INSTALL_DIR/user-data\" ]; then rm -rf \"${NEW_DIR}/user-data\" cp -a \"$INSTALL_DIR/user-data\" \"${NEW_DIR}/user-data\" fi chown -R \"$RUN_USER:$RUN_USER\" \"$NEW_DIR\" runuser -u \"$RUN_USER\" -- bash -lc \"cd '$NEW_DIR' && yarn install --production --frozen-lockfile\" if [ -d \"$INSTALL_DIR\" ]; then mv \"$INSTALL_DIR\" \"$OLD_DIR\" fi mv \"$NEW_DIR\" \"$INSTALL_DIR\" rm -rf \"$OLD_DIR\" cat >/etc/systemd/system/dashy.service < in the browser. Explanation of the above options: -d Detached mode (not running in the foreground of your terminal) -p The port that should be exposed, and the port it should be mapped to in your host system [host-port]:[container-port], leave the container port as 8080 -v Mounts the host directory containing your conf.yml (and any other assets) into the container at /app/user-data --name Give your container a human-readable name --restart=always Spin up the container when the daemon starts, or after it has been stopped lissy93/dashy:latest The image to run. Replace :latest with a specific version from the tags if needed For all available options, and to learn more, see the Docker Run Docs Notes: Dashy is also available through GHCR: docker pull ghcr.io/lissy93/dashy:latest The image is multi-arch, so the same tag works on amd64, arm64, and arm/v7 (Raspberry Pi 2+). Docker selects the right variant for your host automatically. Published tags: latest, exact versions (e.g. 4.3.9), and rolling minor/major tags (4.3, 4.x). See available tags The container runs as the node user (uid 1000). If your mounted user-data is owned by a different uid, you may hit permission errors. Fix this with chown, or run with --user \"$(id -u):$(id -g)\"","s":"Docker Run","u":"/docs/deployment/docker","h":"#docker-run","p":548},{"i":553,"t":"Using Docker Compose can be useful for saving your specific config in files, without having to type out a long run command each time. Save compose config as a YAML file, and then run docker compose up -d (optionally use the -f flag to specify file location, if it isn't located at ./docker-compose.yml), -d is detached mode (not running in the foreground of your terminal). Compose is also useful if you are using clusters, as the format is very similar to stack files, used with Docker Swarm. The following is a complete example of a docker-compose.yml for Dashy. Run it as is, or uncomment the additional options you need. services: dashy: # The image to pull + version. Can use `ghcr.io/lissy93/dashy` instead image: lissy93/dashy:latest # Or, to build from source, replace `image:` with `build: .` # build: . # Optional container name container_name: dashy # Port to serve on (keep container port (second one) as 8080) ports: - 8080:8080 # Mount a directory containing your conf.yml and any other assets volumes: - ./user-data:/app/user-data # Add any env vars for server here, if needed environment: - NODE_ENV=production # If you get permission errors on mounted files, set your own ids (`id -u` / `id -g`) # user: \"1000:1000\" # Auto-start the container on boot restart: unless-stopped # Optional healthcheck override (the image ships a built-in one, running every 5 minutes) healthcheck: test: ['CMD', 'node', '/app/services/healthcheck.js'] interval: 1m30s timeout: 10s retries: 3 start_period: 30s To pull from GHCR instead of Docker Hub, set image: ghcr.io/lissy93/dashy:latest.","s":"Docker Compose","u":"/docs/deployment/docker","h":"#docker-compose","p":548},{"i":555,"t":"Podman is a drop-in replacement for Docker that runs containers without a daemon and doesn't require root. If you're on Fedora, RHEL, or just prefer daemonless containers, Podman works with the same images and mostly the same CLI. podman run -d \\ -p 8080:8080 \\ -v /path/to/your/user-data:/app/user-data:Z \\ --name dashy \\ --restart=always \\ docker.io/lissy93/dashy:latest The :Z suffix on the volume mount handles SELinux relabeling, which you'll need on Fedora/RHEL. If you're not using SELinux, you can leave it off. Podman also supports podman-compose or podman compose (with the compose plugin) using the same docker-compose.yml file shown above.","s":"Podman","u":"/docs/deployment/docker","h":"#podman","p":548},{"i":557,"t":"If you're having issues, take a look at the Troubleshooting Guide. For more detailed Docker setup and management guides, see the Management Docs: Updating Backing Up Healthchecks File Ownership and Permissions Passing in Environmental Variables Auto-Starting at System Boot Verifying Releases Container Security","s":"Read Next","u":"/docs/deployment/docker","h":"#read-next","p":548},{"i":559,"t":"The following lists third-party/community-maintained apps and scripts for deploying Dashy on various device types. Note that these are unofficial. Please ensure you're using a recent version of Dashy (4.x.x +) before opening issues here.","s":"Self-Hosted Operating Systems","u":"/docs/deployment/self-hosted-os","h":"","p":558},{"i":561,"t":"Dashy is in the TrueNAS community apps catalog. Go to Apps > Discover Apps, search \"Dashy\", click Install, then set the web port and a host-path storage volume for /app/user-data. (catalog entry) Older setups may instead find Dashy via TrueCharts, though TrueCharts is no longer integrated with TrueNAS and its chart may be outdated.","s":"TrueNAS SCALE","u":"/docs/deployment/self-hosted-os","h":"#truenas-scale","p":558},{"i":563,"t":"The community-maintained Proxmox VE Helper-Scripts project has a script that spins up Dashy in its own LXC container. Run this in the Proxmox host shell: bash -c \"$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/dashy.sh)\" See the script page for options. These scripts are community-run (not affiliated with Proxmox or Dashy), so give it a read before running.","s":"Proxmox VE","u":"/docs/deployment/self-hosted-os","h":"#proxmox-ve","p":558},{"i":565,"t":"Dashy is available through the Community Applications plugin. Search for \"Dashy\" in the Apps tab and install from there. The template pre-fills the Docker image, port mapping, and volume paths for you. If you'd prefer to set it up manually, go to Docker > Add Container and use lissy93/dashy:latest as the repository. Map port 8080, and add a path mapping for the host directory containing your conf.yml to /app/user-data.","s":"Unraid","u":"/docs/deployment/self-hosted-os","h":"#unraid","p":558},{"i":567,"t":"On DSM 7.2 and later, Docker is provided by the Container Manager package: Install Container Manager from the Package Center. In File Station, create a folder for Dashy's config (e.g. docker/dashy) and put your conf.yml (plus any icons/assets) inside it. In Container Manager, open Registry, search for lissy93/dashy, and download the latest tag. Under Container > Create, pick the image, enable auto-restart, map a host port (e.g. 4000) to container port 8080, and mount your docker/dashy folder to /app/user-data. Alternatively, use Container Manager's Project feature with the docker-compose.yml above, or run it over SSH: docker run -d \\ -p 4000:8080 \\ -v /volume1/docker/dashy:/app/user-data \\ --name dashy \\ --restart=always \\ lissy93/dashy:latest Dashy should be reachable on your chosen port within a minute or two.","s":"Synology NAS","u":"/docs/deployment/self-hosted-os","h":"#synology-nas","p":558},{"i":569,"t":"Dashy is packaged in nixpkgs as dashy-ui, and NixOS ships a services.dashy module. Enable it in your configuration: services.dashy.enable = true; See the module options for setting the port and your config. Note that the package builds the static app, so the server-only features (status checks, saving config through the UI) aren't available.","s":"NixOS","u":"/docs/deployment/self-hosted-os","h":"#nixos","p":558},{"i":571,"t":"caution This integration may need re-review, to ensure that it's using / compatible with Dashy 4+ This uses the Helm chart written by @vyrtualsynthese, which is available here # Add the self-hosted repo helm repo add self-hosted https://vyrtualsynthese.github.io/selfhosted-helmcharts/ helm repo update # Install the dashy chart helm install [RELEASE_NAME] self-hosted/dashy TrueCharts also publishes a Dashy Helm chart, aimed at Kubernetes and TrueNAS SCALE.","s":"Kubernetes","u":"/docs/deployment/self-hosted-os","h":"#kubernetes","p":558},{"i":573,"t":"Dashy has ready-made apps, templates, and one-click installs for a range of self-hosted control panels and home-server platforms. To deploy onto a specific operating system or NAS instead, see Self-Hosted Operating Systems. note Please ensure the app you install is pinned to a recent or latest version of Dashy (e.g. 4.x.x upwards). Some of these integrations are unofficial/third-party, so we can't offer support for them. Raise any issues in the appropriate repo.","s":"Self-Hosted Platforms","u":"/docs/deployment/self-hosted-platforms","h":"","p":572},{"i":575,"t":"If you manage your Docker host through Portainer, you can deploy Dashy from its UI: Go to Stacks > Add stack Paste the docker-compose.yml contents, or point to the URL Adjust the port and volume mappings as needed Deploy the stack Alternatively, add the lissy93/portainer-templates list (https://raw.githubusercontent.com/Lissy93/portainer-templates/main/templates.json) under Settings > App Templates, then install Dashy from the Templates tab. note Some older Portainer templates mount config at /app/public/conf.yml. For Dashy 4, use /app/user-data instead.","s":"Portainer","u":"/docs/deployment/self-hosted-platforms","h":"#portainer","p":572},{"i":577,"t":"Coolify is a self-hostable PaaS (a Heroku/Netlify alternative). Dashy is available as a service template: under + New Resource > Service, search \"Dashy\" and deploy. The current template may still mount config at /app/public/conf.yml; for Dashy 4, change this to /app/user-data. (template source)","s":"Coolify","u":"/docs/deployment/self-hosted-platforms","h":"#coolify","p":572},{"i":579,"t":"1Panel is a web-based Linux server management panel. Dashy is in its official App Store: open App Store, search \"Dashy\", click Install, and set the port. (app page) note The public 1Panel app page currently lists an older Dashy 3.x version. If the installer allows it, change the image to lissy93/dashy:latest or a recent 4.x tag.","s":"1Panel","u":"/docs/deployment/self-hosted-platforms","h":"#1panel","p":572},{"i":581,"t":"Runtipi is a homeserver management platform. The source for the Dashy app is here. Install runtipi with: curl -L https://setup.runtipi.io | bash Open tipi.local Go to App Store --> Search --> Dashy","s":"Runtipi","u":"/docs/deployment/self-hosted-platforms","h":"#runtipi","p":572},{"i":583,"t":"Cosmos Cloud is a self-hosted server manager with a built-in reverse proxy and app market. Dashy is in the official market, so there's no extra source to add. Open your Cosmos admin UI and go to the Market Search \"Dashy\" and click Install Accept the defaults, or set a path for your own conf.yml, then confirm Cosmos pulls lissy93/dashy:latest and sets up its reverse proxy for you. (market entry) note The current Cosmos template still mounts config at /app/public. For Dashy 4, edit the service to mount your config directory at /app/user-data.","s":"Cosmos Cloud","u":"/docs/deployment/self-hosted-platforms","h":"#cosmos-cloud","p":572},{"i":585,"t":"Dashy isn't in the built-in CasaOS store, but it is in the community BigBear app store. In the CasaOS App Store, open the menu and choose Add Source Paste https://github.com/bigbeartechworld/big-bear-casaos/archive/refs/heads/master.zip and save A BigBearCasaOS category appears. Search for Dashy v4 and click Install caution The store lists two Dashy apps. Pick \"Dashy v4\" (lissy93/dashy:4.0.8). The plain \"Dashy\" entry is pinned to the old 3.3.1, so avoid it. The same BigBear store should also work on ZimaOS (Zimaboard), with the same steps.","s":"CasaOS","u":"/docs/deployment/self-hosted-platforms","h":"#casaos","p":572},{"i":587,"t":"Dashy isn't in the official Umbrel App Store. A community app store has it, but it's no longer maintained. caution The only Umbrel option, dennys-umbrel-app-store, is discontinued by its maintainer and pins Dashy 3.x. It won't get updates, so treat it as a last resort. Running Dashy with Docker directly on your Umbrel host is the better option. To install it anyway: In the Umbrel App Store, open the three-dot menu and choose Community App Stores Add https://github.com/dennysubke/dennys-umbrel-app-store Open the store, find Dashy, and click Install (it serves on port 4444)","s":"Umbrel","u":"/docs/deployment/self-hosted-platforms","h":"#umbrel","p":572},{"i":589,"t":"Easypanel is a self-hosted server control panel with a Dashy template. It runs the full Docker image, so all features including the Node server work. Template: https://easypanel.io/docs/templates/dashy caution The Easypanel template pins an old Dashy version (3.3.1). After deploying, change the image tag to lissy93/dashy:latest (or a recent 4.x version) to get the current release.","s":"EasyPanel","u":"/docs/deployment/self-hosted-platforms","h":"#easypanel","p":572},{"i":591,"t":"Saltbox (an Ansible-based server automation project) includes Dashy as a sandbox app. Once Saltbox is set up, install it with: sb install sandbox-dashy See the Saltbox Dashy docs for details.","s":"Saltbox","u":"/docs/deployment/self-hosted-platforms","h":"#saltbox","p":572},{"i":593,"t":"This article outlines how to get Dashy running in a development environment, and outlines the basics of the architecture. If you're adding new features, you may want to check out the Development Guides docs, for tutorials covering basic tasks. Setting up the Development Environment Prerequisites Running the App Project Commands Environmental Variables Git Strategy Flow Branches Commit emojis PR Guidelines Resources for Beginners App Info Code Style Guide Application Structure Development Tools Misc / Notes","s":"Developing","u":"/docs/developing","h":"","p":592},{"i":596,"t":"You will need either the latest or LTS version of Node.js to build and serve the application and Git to easily fetch the code, and push any changes. If you plan on running or deploying the container, you'll also need Docker. To avoid any unexpected issues, ensure you've got at least NPM V 7.5 or Yarn 1.22 (you may find NVM helpful for switching/ managing versions).","s":"Prerequisites","u":"/docs/developing","h":"#prerequisites","p":592},{"i":598,"t":"Get Code: git clone https://github.com/Lissy93/dashy.git Navigate into the directory: cd dashy Install dependencies: yarn Start dev server: yarn dev Dashy should now be being served on http://localhost:8080/. Hot reload is enabled, so making changes to any of the files will trigger them to be rebuilt and the page refreshed.","s":"Running the Project","u":"/docs/developing","h":"#running-the-project","p":592},{"i":600,"t":"Basics​ yarn build - Builds the production bundle into ./dist. The Vite dev/runtime server serves user-data/conf.yml on each request, so a rebuild is only needed when source code or assets change, not when config changes yarn start - Starts the Node server, which serves the built site from ./dist and the live config from user-data/. Run yarn build first Development​ yarn dev - Starts the development server with hot reloading yarn lint - Lints code to ensure it follows a consistent, neat style yarn test - Runs tests, and outputs results Utils and Checks​ yarn validate-config - If you have quite a long configuration file, you may wish to check that it's all good to go, before deploying the app. This can be done with yarn validate-config or docker exec -it [container-id] yarn validate-config. Your config file needs to be in /user-data/conf.yml (or within your Docker container at /app/user-data/conf.yml). This will first check that your YAML is valid, and then validates it against Dashy's schema. yarn health-check - Checks that the application is up and running on it's specified port, and outputs current status and response times. Useful for integrating into your monitoring service, if you need to maintain high system availability Alternate Start Commands​ yarn build && yarn start - Builds the app, then starts the production Node server. Use this for a manual production-style run on bare metal. With Vite, conf.yml is served from user-data/ at runtime, so config changes only require a page refresh yarn pm2-start - Starts the Node server using PM2, a process manager for Node.js applications, that helps them stay alive. PM2 has some built-in basic monitoring features, and an optional management solution. If you are running the app on bare metal, it is recommended to use this start command Notes​ If you are using NPM, replace yarn with npm run If you are using Docker, precede each command with docker exec -it [container-id]. Container ID can be found by running docker ps","s":"Project Commands","u":"/docs/developing","h":"#project-commands","p":592},{"i":602,"t":"All environmental variables are optional. Currently there are not many environmental variables used, as most of the user preferences are stored under appConfig in the conf.yml file. You can set variables either in your environment, or using the .env file. NODE_ENV - Current environment, can be either development, production or test PORT - The port to expose the running application on HOST - The host that Dashy is running on, domain or IP BASE_URL - The default base path for serving up static assets VITE_APP_DOMAIN - Usually the same as BASE_URL, but accessible in frontend INTEGRITY - Should enable SRI for build script and link resources IS_DOCKER - Computed automatically on build. Indicates if running in container VITE_APP_VERSION - Again, set automatically using package.json during build time BACKUP_DIR - Directory for conf.yml backups DISABLE_CONFIG_BACKUPS - Set to 'true' to skip the pre-save backup step (useful on read-only filesystems or where permissions don't allow it)","s":"Environmental Variables","u":"/docs/developing","h":"#environmental-variables","p":592},{"i":604,"t":"You can set the environment using the NODE_ENV variable. By default, the correct environment should be selected based on the script you run to start the app. The following environments are supported: production, development and test. For more info, see Vite Env Variables and Modes.","s":"Environment Modes","u":"/docs/developing","h":"#environment-modes","p":592},{"i":607,"t":"Like most Git repos, we are following the Github Flow standard. Create a branch (or fork if you don'd have write access) Code some awesome stuff 🧑‍💻 Add, commit and push your changes to your branch/ fork Head over to GitHub and create a Pull Request Fill in the required sections in the template, and hit submit Follow up with any reviews on your code Merge 🎉","s":"Git Flow","u":"/docs/developing","h":"#git-flow","p":592},{"i":609,"t":"The format of your branch name should be something similar to: [TYPE]/[TICKET]_[TITLE] For example, FEATURE/420_Awesome-feature or FIX/690_login-server-error","s":"Git Branch Naming","u":"/docs/developing","h":"#git-branch-naming","p":592},{"i":611,"t":"Using a single emoji at the start of each commit message, to indicate the type task, makes the commit ledger easier to understand, plus it looks cool. 🎨 :art: - Improve structure / format of the code. ⚡️ :zap: - Improve performance. 🔥 :fire: - Remove code or files. 🐛 :bug: - Fix a bug. 🚑️ :ambulance: - Critical hotfix ✨ :sparkles: - Introduce new features. 📝 :memo: - Add or update documentation. 🚀 :rocket: - Deploy stuff. 💄 :lipstick: - Add or update the UI and style files. 🎉 :tada: - Begin a project. ✅ :white_check_mark: - Add, update, or pass tests. 🔒️ :lock: - Fix security issues. 🔖 :bookmark: - Make a Release or Version tag. 🚨 :rotating_light: - Fix compiler / linter warnings. 🚧 :construction: - Work in progress. ⬆️ :arrow_up: - Upgrade dependencies. 👷 :construction_worker: - Add or update CI build system. ♻️ :recycle: - Refactor code. 🩹 :adhesive_bandage: - Simple fix for a non-critical issue. 🔧 :wrench: - Add or update configuration files. 🍱 :bento: - Add or update assets. 🗃️ :card_file_box: - Perform database schema related changes. ✏️ :pencil2: - Fix typos. 🌐 :globe_with_meridians: - Internationalization and translations. For a full list of options, see gitmoji.dev","s":"Commit Emojis","u":"/docs/developing","h":"#commit-emojis","p":592},{"i":613,"t":"Once you've made your changes, and pushed them to your fork or branch, you're ready to open a pull request! For a pull request to be merged, it must: Must be backwards compatible The build, lint and tests (run by GH actions) must pass There must not be any merge conflicts When you submit your PR, include the required info, by filling out the PR template. Including: A brief description of your changes The issue, ticket or discussion number (if applicable) For UI relate updates include a screenshot If any dependencies were added, explain why it was needed, state the cost associated, and confirm it does not introduce any security issues Finally, check the checkboxes, to confirm that the standards are met, and hit submit!","s":"PR Guidelines","u":"/docs/developing","h":"#pr-guidelines","p":592},{"i":615,"t":"New to Web Development? Glad you're here! Dashy is a pretty simple app, so it should make a good candidate for your first PR. Presuming that you already have a basic knowledge of JavaScript, the following articles should point you in the right direction for getting up to speed with the technologies used in this project: Open Source for Beginners Introduction to Vue.js Vue.js Walkthrough ES6 Features Definitive guide to SCSS Complete beginners guide to Docker Docker Classroom - Interactive Tutorials Quick start TypeScript guide Complete TypeScript tutorial series Using TypeScript with Vue.js Git cheat sheet Basics of using NPM As well as Node, Git and Docker- you'll also need an IDE (e.g. VS Code or Vim) and a terminal (Windows users may find WSL more convenient).","s":"Resources for Beginners","u":"/docs/developing","h":"#resources-for-beginners","p":592},{"i":618,"t":"Linting is done using ESLint, and using the Vue.js Styleguide, which is very similar to the AirBnB Styleguide. You can run yarn lint to report and fix issues. While the dev server is running, issues will be reported to the console automatically, and any lint errors will trigger the build to fail. Note that all lint checks must pass before any PR can be merged. Linting is also run as a git pre-commit hook The most significant things to note are: Indentation should be done with two spaces Strings should use single quotes All statements must end in a semi-colon The final element in all objects must be preceded with a comma Maximum line length is 100 There must be exactly one blank line between sections, before function names, and at the end of the file With conditionals, put else on the same line as your if block's closing brace All multiline blocks must use braces Avoid console statements in the frontend Styleguides: Vue: Vue styleguide JavaScript: github.com/airbnb/javascript","s":"Style Guide","u":"/docs/developing","h":"#style-guide","p":592},{"i":620,"t":"Files in the Root: ./​ ╮ ├── package.json # Project meta-data, dependencies and paths to scripts ├── src/ # Project front-end source code ├── server.js # A Node.js server to serve up the /dist directory ├── services/ # All server-side endpoints and utilities ├── vite.config.mjs # Vite (build tool) configuration ├── Dockerfile # The blueprint for building the Docker container ├── docker-compose.yml # A Docker run command ├── .env # Location for any environmental variables ├── yarn.lock # Auto-generated list of current packages and version numbers ├── docs/ # Markdown documentation ├── README.md # Readme, basic info for getting started ├── LICENSE # License for use ╯ Frontend Source: ./src/​ ./src ├── App.vue # Vue.js starting file ├── assets # Static non-compiled assets │ ├── fonts # .ttf font files │ ├── locales # All app text, each language in a separate JSON file │ ╰── interface-icons # SVG icons used in the app ├── components # All front-end Vue web components │ ├── Charts # Charting components for dynamically displaying widget data │ │ ├── Gauge.vue # A speed-dial style chart for showing 0 - 100 values │ │ ╰── PercentageChart.vue # A horizontal bar for showing percentage breakdowns │ ├── Configuration # Components relating to the user config pop-up │ │ ├── AppInfoModal.vue # A modal showing core app info, like version, language, etc │ │ ├── AppVersion.vue # Shows current version from package.json, compares with GitHub │ │ ├── CloudBackupRestore.vue # Form where the user manages cloud sync options │ │ ├── ConfigContainer.vue # Main container, wrapping all other config components │ │ ├── CustomCss.vue # Form where the user can input custom CSS │ │ ╰── JsonEditor.vue # JSON editor, where the user can modify the main config file │ ├── FormElements # Basic form elements used throughout the app │ │ ├── Button.vue # Standard button component │ │ ├── Radio.vue # Standard radio button input │ │ ├── Select.vue # Standard dropdown input selector │ │ ├── Input.vue # Standard text field input component │ │ ╰── Toggle.vue # Standard on / off toggle switch │ ├── InteractiveEditor # Components for the interactive UI config editor │ │ ├── AddNewSectionLauncher # Button that launches the EditSection form, used for adding new section │ │ ├── EditAppConfig.vue # Form for editing appConfig │ │ ├── EditPageInfo.vue # Form for editing pageInfo │ │ ├── EditSection.vue # Form for adding / editing sections │ │ ├── EditItem.vue # Form for adding or editing items │ │ ├── EditModeSaveMenu.vue # The bar at the bottom of screen in edit mode, containing save buttons │ │ ├── EditModeTopBanner.vue # The bar at the top of screen in edit mode │ │ ├── ExportConfigMenu.vue # Modal for viewing / exporting edited config │ │ ├── MoveItemTo.vue # Form for moving / copying items to other sections │ │ ╰── SaveCancelButtons.vue # Buttons visible in all the edit menus, to save or cancel changes │ ├── LinkItems # Components for Sections and Link Items │ │ ├── Collapsable.vue # The collapsible functionality of sections │ │ ├── IframeModal.vue # Pop-up iframe modal, for viewing websites within the app │ │ ├── Item.vue # Main link item, which is displayed within an item group │ │ ├── ItemGroup.vue # Item group is a section containing icons │ │ ├── ItemIcon.vue # The icon used by both items and sections │ │ ├── ItemOpenMethodIcon.vue # A small icon, visible on hover, indicating opening method │ │ ├── ItemContextMenu.vue # The right-click menu, for showing Item opening methods and info │ │ ├── SectionContextMenu.vue # The right-click menu, for showing Section edit/ open options │ │ ╰── StatusIndicator.vue # Traffic light dot, showing if app is online or down │ ├── Minimal View # Components used for the startpage / minimal alternative view │ │ ├── MinimalHeading.vue # Title part of minimal view │ │ ├── MinimalSearch.vue # Search bar for minimal view │ │ ╰── MinimalSection.vue # Tabbed-Item section for minimal view │ ├── PageStrcture # Components relating the main structure of the page │ │ ├── Footer.vue # Footer, visible at the bottom of all pages │ │ ├── Header.vue # Header, visible at the top of pages, and includes title and nav │ │ ├── LoadingScreen.vue # Splash screen shown on first load │ │ ├── Nav.vue # Navigation bar, includes a list of links │ │ ╰── PageTitle.vue # Page title and sub-title, visible within the Header │ ├── Workspace # Components used for the multi-tasking/ Workspace view │ │ ├── MultiTaskingWeb.vue # When multi-tasking enabled, generates new iframe │ │ ├── SideBar.vue # The left sidebar for the workspace view │ │ ├── SideBarItem.vue # App item for the sidebar view │ │ ├── SideBarSection.vue # Collapsible collection of items within workspace sidebar │ │ ├── WebContent.vue # Workspace iframe view, displays content of current app │ │ ╰── WidgetView.vue # Workspace container for displaying widgets in main content │ ├── Widgets # Directory contains all custom widget components │ │ ╰── .... # Too many to list, see widget docs instead │ ╰── Settings # Components relating to the quick-settings, in the top-right │ ├── AuthButtons.vue # Logout button and other app info │ ├── ConfigLauncher.vue # Icon that when clicked will launch the Configuration component │ ├── CustomThemeMaker.vue # Color pickers for letting user build their own theme │ ├── ItemSizeSelector.vue # Set of buttons used to set and save item size │ ├── KeyboardShortcutInfo.vue# Small pop-up displaying the available keyboard shortcuts │ ├── LanguageSwitcher.vue # Dropdown in a modal for changing app language │ ├── LayoutSelector.vue # Set of buttons, letting the user select their desired layout │ ├── SearchBar.vue # The input field in the header, used for searching the app │ ├── SettingsContainer.vue # Container that wraps all the quick-settings components │ ╰── ThemeSelector.vue # Drop-down menu enabling the user to select and change themes ├── main.js # Main front-end entry point ├── registerServiceWorker.js # Registers and manages service workers, for PWA apps ├── router.js # Defines all available application routes ├── styles # Directory of all globally used common SCSS styles │ ├── color-palette.scss # All color variable names and default values │ ├── color-themes.scss # All variable values for built-in themes │ ├── dimensions.scss # Dimensions and sizes as variables │ ├── global-styles.scss # Basics and style resets used globally │ ├── media-queries.scss # Screen sizes and media queries │ ├── style-helpers.scss # SCSS functions used for modifying values │ ├── typography.scss # Font and text styles used globally │ ╰── user-defined-themes.scss # Empty, put any custom styles or themes here ├── mixins # Reusable component bases, extended by other views / components │ ├── ChartingMixin.js # Functions for rendering charts in widget components │ ├── GlancesMixin.js # Functions for fetching system info from Glances for widgets │ ├── HomeMixin.js # Functions for homepage, used by default, minimal and workspace views │ ╰── WidgetMixin.js # Functions for all widgets, like data fetching, updating and error handling ├── utils # Directory of re-used helper functions │ ├── ArrowKeyNavigation.js # Functionality for arrow-key navigation │ ├── Auth.js # Handles all authentication related actions │ ├── CheckSectionVisibility.js # Checks which parts of the page should be visible/ hidden based on config │ ├── ClickOutside.js # A directive for detecting click, used to hide dropdown, modal or context menu │ ├── ConfigHelpers.js # Helper functions for managing configuration │ ├── CloudBackup.js # Functionality for encrypting, processing and network calls │ ├── ConfigSchema.json # The schema, used to validate the users conf.yml file │ ├── ConfigAccumulator.js # Central place for managing and combining config │ ├── ConfigValidator.js # A helper script that validates the config file against schema │ ├── CoolConsole.js # Prints info, warning and error messages to browser console, with a cool style │ ├── defaults.js # Global constants and their default values │ ├── emojis.json # List of emojis with unicode and shortcode, used for emoji icon feature │ ├── EmojiUnicodeRegex.js # Regular expression to validate emoji unicode format, for emoji icons │ ├── ErrorHandler.js # Helper function called when an error is returned │ ├── InitServiceWorker.js # Initializes and manages service worker, if enabled │ ├── Search.js # Helper functions for searching/ filtering items in all views │ ├── JsonToYaml.js # Function that parses and converts raw JSON into valid YAML │ ├── KeycloakAuth.js # Singleton class to manage Keycloak authentication │ ├── OidcAuth.js # Manages OIDC authentication with external providers │ ├── HeaderAuth.js # Handles header-based authentication via reverse proxy │ ├── languages.js # Handles fetching, switching and validating languages │ ╰── ThemeHelper.js # Function that handles the fetching and setting of user themes ╰── views # Directory of available pages, corresponding to available routes ├── Home.vue # The home page container ├── About.vue # About page ├── Login.vue # Authentication page ├── Minimal.vue # The minimal view ├── Workspace.vue # The workspace view with apps in sidebar ├── DownloadConfig.vue # Config export page ╰── 404.vue # Not found page Visualisation of Source Directory​","s":"Application Structure","u":"/docs/developing","h":"#application-structure","p":592},{"i":623,"t":"The easiest method of checking performance is to use Chromium's build in auditing tool, Lighthouse. To run the test, open Developer Tools (usually F12) --> Lighthouse and click on the 'Generate Report' button at the bottom.","s":"Performance - Lighthouse","u":"/docs/developing","h":"#performance---lighthouse","p":592},{"i":625,"t":"BundlePhobia is a really useful app that lets you analyze the cost of adding any particular dependency to an application WARN asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). - For the PWA to support Windows 10, a splash screen asset is required, and is quite large. This throws a warning, however PWA assets are not loaded until needed, so shouldn't have any impact on application performance. A similar warning is thrown for the Raleway font, and that is looking to be addressed.","s":"Dependencies - BundlePhobia","u":"/docs/developing","h":"#dependencies---bundlephobia","p":592},{"i":627,"t":"A series of short tutorials, to guide you through the most common development tasks. Sections: Creating a new theme Writing Translations Adding a new option in the config file Updating Dependencies Writing Netlify Cloud Functions Hiding Page Furniture Adding / Using Environmental Variables Building a Widget Respecting Config Permissions","s":"Development Guides","u":"/docs/development-guides","h":"","p":626},{"i":629,"t":"Adding a new theme is really easy. There're two things you need to do: Pass the theme name to Dashy, so that it can be added to the theme selector dropdown menu, and then write some styles!","s":"Creating a new theme","u":"/docs/development-guides","h":"#creating-a-new-theme","p":626},{"i":631,"t":"Choose a snappy name for your theme, and add it to the builtInThemes array inside defaults.js.","s":"1. Add Theme Name","u":"/docs/development-guides","h":"#1-add-theme-name","p":626},{"i":633,"t":"Put your theme styles inside color-themes.scss. Create a new block, and make sure that data-theme matches the theme name you chose above. For example: html[data-theme='tiger'] { --primary: #f58233; --background: #0b1021; } Then you can go ahead and write your own custom CSS. Although all CSS is supported here, the best way to define your theme is by setting the CSS variables. You can find a list of all CSS variables, here. For a full guide on styling, see Theming Docs. Note that if your theme is just for yourself, and you're not submitting a PR, then you can instead just pass it under appConfig.cssThemes inside your config file. And then put your theme in your own stylesheet, and pass it into the Docker container - see how.","s":"2. Write some Styles","u":"/docs/development-guides","h":"#2-write-some-styles","p":626},{"i":635,"t":"For full docs about Dashy's multi-language support, see Multi-Language Support Dashy is using vue-i18n to manage multi-language support. Adding a new language is pretty straightforward, with just three steps:","s":"Writing Translations","u":"/docs/development-guides","h":"#writing-translations","p":626},{"i":637,"t":"Create a new JSON file in ./src/assets/locales name is a 2-digit ISO-639 code for your language, E.g. for German de.json, French fr.json or Spanish es.json - You can find a list of all ISO codes at iso.org.","s":"1. Create a new Language File","u":"/docs/development-guides","h":"#1-create-a-new-language-file","p":626},{"i":639,"t":"Using en.json as an example, translate the JSON values to your language, while leaving the keys as they are. It's fine to leave out certain items, as if they're missing they will fall-back to English. If you see any attribute which include curly braces ({xxx}), then leave the inner value of these braces as is, as this is for variables. { \"theme-maker\": { \"export-button\": \"Benutzerdefinierte Variablen exportieren\", \"reset-button\": \"Stile zurücksetzen für\", \"show-all-button\": \"Alle Variablen anzeigen\", \"save-button\": \"Speichern\", \"cancel-button\": \"Abbrechen\", \"saved-toast\": \"{theme} Erfolgreich aktualisiert\", \"reset-toast\": \"Benutzerdefinierte Farben für {theme} entfernt\" }, }","s":"2. Translate","u":"/docs/development-guides","h":"#2-translate","p":626},{"i":641,"t":"In ./src/utils/languages.js, you need to do 2 small things: First import your new translation file, do this at the top of the page. E.g. import de from '@/assets/locales/de.json'; Second, add it to the array of languages, e.g: export const languages = [ { name: 'English', code: 'en', locale: en, flag: '🇬🇧', }, { name: 'German', // The name of your language code: 'de', // The ISO code of your language locale: de, // The name of the file you imported (no quotes) flag: '🇩🇪', // An optional flag emoji }, ]; You can also add your new language to the readme file, under the Language Switching section, and optionally include your name/ username if you'd like to be credited for your work. Done! If you are not comfortable with making pull requests, or do not want to modify the code, then feel free to instead send the translated file to me, and I can add it into the application. I will be sure to credit you appropriately.","s":"3. Add your file to the app","u":"/docs/development-guides","h":"#3-add-your-file-to-the-app","p":626},{"i":643,"t":"This section is for, adding a new setting to the config file. All of the users config is specified in ./user-data/conf.yml - see Configuring Docs for info. It's important to first ensure that there isn't a similar option already available, the new option is definitely necessary, and most importantly that it is fully backwards compatible. Next choose the appropriate section to place it under Application settings should be located under appConfig Page info (such as text and metadata) should be under pageInfo Data relating to specific sections should be under section[n].displayData Settings applied to specific items or widgets, should be under item[n] or widget[n] For example, if your option is added under appConfig, you can access it within your component using the $store, this is typically placed in a computed property, e.g: computed: { appConfig() { return this.$store.getters.appConfig; }, ... }, Then, where you want to get the users value within your component, use something like: this.appConfig.myProperty. If the user hasn't specified the value, Don't forget to have a fallback or default for it. If you have a default fallback value, then this would typically be specified in the defaults.js file. You will now need to add the definition of your new attribute into the ConfigSchema. This will make it available in the UI config editor, and also ensure that the config validation check doesn't fail. For example: \"fontAwesomeKey\": { \"type\": \"string\", \"pattern\": \"^[a-z0-9]{10}$\", \"description\": \"API key for font-awesome\", \"example\": \"0821c65656\" } or \"iconSize\": { \"enum\": [ \"small\", \"medium\", \"large\" ], \"default\": \"medium\", \"description\": \"The size of each link item / icon\" } Finally, add your new property to the configuring.md API docs. Put it under the relevant section, and be sure to include field name, data type, a description and mention that it is optional. If your new feature needs more explanation, then you can also document it under the relevant section elsewhere in the documentation. Checklist: Ensure the new attribute is actually necessary, and nothing similar already exists Update the Schema with the parameters for your new option If required, set a default or fallback value (usually in defaults.js) Document the new value in configuring.md, and if required under the relevant section in the docs Ensure your changes are backwards compatible, and that nothing breaks if the attribute isn't specified","s":"Adding a new option in the config file","u":"/docs/development-guides","h":"#adding-a-new-option-in-the-config-file","p":626},{"i":645,"t":"Running yarn upgrade will updated all dependencies based on the ranges specified in the package.json. The yarn.lock file will be updated, as will the contents of ./node_modules, for more info, see the yarn upgrade documentation. npm-check-updates is a useful tool to help with this. It is important to thoroughly test after any big dependency updates.","s":"Updating Dependencies","u":"/docs/development-guides","h":"#updating-dependencies","p":626},{"i":647,"t":"When Dashy is deployed to Netlify, it is effectively running as a static app, and therefore the server-side code for the Node.js endpoints is not available. However Netlify now supports serverless cloud lambda functions, which can be used to replace most functionality.","s":"Developing Netlify Cloud Functions","u":"/docs/development-guides","h":"#developing-netlify-cloud-functions","p":626},{"i":649,"t":"First off all, install the Netlify CLI: npm install netlify-cli -g Then, from within the root of Dashy's directory, start the server, by running: netlify dev","s":"1. Run Netlify Dev Server","u":"/docs/development-guides","h":"#1-run-netlify-dev-server","p":626},{"i":651,"t":"This should be saved in the ./services/serverless-functions directory exports.handler = async () => ({ statusCode: 200, body: 'Return some data here...', });","s":"2. Create a lambda function","u":"/docs/development-guides","h":"#2-create-a-lambda-function","p":626},{"i":653,"t":"In the netlify.toml file, add a 301 redirect, with the path to the original Node.js endpoint, and the name of your cloud function [[redirects]] from = \"/status-check\" to = \"/.netlify/functions/cloud-status-check\" status = 301 force = true","s":"3. Redirect the Node endpoint to the function","u":"/docs/development-guides","h":"#3-redirect-the-node-endpoint-to-the-function","p":626},{"i":655,"t":"For some pages (such as the login page, the minimal start page, etc) the basic page furniture, (like header, footer, nav, etc) is not needed. This section explains how you can hide furniture on a new view (step 1), or add a component that should be hidden on certain views (step 2).","s":"Hiding Page Furniture on Certain Routes","u":"/docs/development-guides","h":"#hiding-page-furniture-on-certain-routes","p":626},{"i":657,"t":"In ./src/utils/config/defaults.js, there's an array called hideFurnitureOn. Append the name of the route (the same as it appears in router.js) here.","s":"1. Add the route name to the should hide array","u":"/docs/development-guides","h":"#1-add-the-route-name-to-the-should-hide-array","p":626},{"i":659,"t":"First, import the helper function: import { shouldBeVisible } from '@/utils/config/SectionHelpers'; Then you can create a computed value, that calls this function, passing in the route name: export default { ... computed: { ... isVisible() { return shouldBeVisible(this.$route.name); }, }, }; Finally, in the markup of your component, just add a v-if statement, referencing your computed value
    ...
    ","s":"2. Add the conditional to the structural component to hide","u":"/docs/development-guides","h":"#2-add-the-conditional-to-the-structural-component-to-hide","p":626},{"i":661,"t":"All environmental variables are optional. Currently there are not many environmental variables used, as most of the user preferences are stored under appConfig in the conf.yml file. You can set variables either in your environment, or using the .env file. Any environmental variables used by the frontend are preceded with VITE_APP_. Vite will merge the contents of your .env file into the app in a similar way to the 'dotenv' package, where any variables that you set on your system will always take preference over the contents of any .env file. If add any new variables, ensure that there is always a fallback (define it in defaults.js), so as to not cause breaking changes. Don't commit the contents of your .env file to git, but instead take a few moments to document what you've added under the appropriate section. Try and follow the concepts outlined in the 12 factor app.","s":"Adding / Using Environmental Variables","u":"/docs/development-guides","h":"#adding--using-environmental-variables","p":626},{"i":664,"t":"If this is your first time working on Dashy, then the Developing Docs instructions for project setup and running. In short, you just need to clone the project, cd into it, install dependencies (yarn) and then start the development server (yarn dev). To build a widget, you'll also need some basic knowledge of Vue.js. The official Vue docs provides a good starting point, as does this guide by Tania Rascia If you just want to jump straight in, then here is a complete implementation of a new example widget, or take a look at the XkcdComic.vue widget, which is pretty simple.","s":"Step 0 - Prerequisites","u":"/docs/development-guides","h":"#step-0---prerequisites","p":626},{"i":666,"t":"Firstly, create a new .vue file under ./src/components/Widgets. All widgets extend from the Widget mixin. This provides some basic functionality that is shared by all widgets. The mixin includes the following options, startLoading(), finishLoading(), error() and update(). Getting user options: options Any user-specific config can be accessed with this.options.something (where something is the data key you're accessing) Loading state: startLoading() and finishLoading() You can show the loader with this.startLoading(), then when your data request completes, hide it again with this.finishLoading() Error handling: error() If something goes wrong (such as API error, or missing user parameters), then call this.error() to show message to user Updating data: update() When the user clicks the update button, or if continuous updates are enabled, then the update() method within your widget will be called","s":"Step 1 - Create Widget","u":"/docs/development-guides","h":"#step-1---create-widget","p":626},{"i":668,"t":"Accessing User Options​ If your widget is going to accept any parameters from the user, then we can access these with this.options.[parmName]. It's best to put these as computed properties, which will enable us to check it exists, is valid, and if needed format it. For example, if we have an optional property called count (to determine number of results), we can do the following, and then reference it within our component with this.count computed: { count() { if (!this.options.count) { return 5; } return this.options.count; }, ... }, Adding an API Endpoint​ If your widget makes a data request, then add the URL for the API endpoint to the widgetApiEndpoints array in defaults.js widgetApiEndpoints: { ... exampleEndpoint: 'https://hub.dummyapis.com/ImagesList', }, Then in your widget file: import { widgetApiEndpoints } from '@/utils/config/defaults'; For GET requests, you may need to add some parameters onto the end of the URL. We can use another computed property for this, for example: endpoint() { return `${widgetApiEndpoints.exampleEndpoint}?count=${this.count}`; }, Making an API Request​ The widget mixin gives you a makeRequest method for fetching data, so you don't need to import an HTTP client. It handles the loading state, errors, timeouts and the optional CORS proxy for you. Under the methods block, we'll create a function called fetchData, where we call makeRequest with our endpoint (and optionally any headers). fetchData() { this.makeRequest(this.endpoint, this.headers).then(this.processData); }, There are three things happening here: If the response completes successfully, we'll pass the results to another function that will handle them If there's an error, then we call this.error(), which will show a message to the user Whatever the result, once the request has completed, we call this.finishLoading(), which will hide the loader Processing Response​ In the above example, we call the processData() method with the result from the API, so we need to create that under the methods section. How you handle this data will vary depending on what's returned by the API, and what you want to render to the user. But however you do it, you will likely need to create a data variable to store the response, so that it can be easily displayed in the HTML. data() { return { myResults: null, }; }, And then, inside your processData() method, you can set the value of this, with: `this.myResults = 'whatever'` Rendering Response​ Now that the results are in the correct format, and stored as data variables, we can use them within the