mirror of
				https://github.com/house-of-abbey/GarminHomeAssistant.git
				synced 2025-10-29 23:08:14 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			463 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			463 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <html lang="en">
 | |
|   <head>
 | |
|     <meta charset="UTF-8" />
 | |
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | |
|     <title>GarminHomeAssistant</title>
 | |
| 
 | |
|     <link
 | |
|       rel="stylesheet"
 | |
|       data-name="vs/editor/editor.main"
 | |
|       href="https://www.unpkg.com/monaco-editor@0.45.0/min/vs/editor/editor.main.css" />
 | |
|     <link
 | |
|       rel="stylesheet"
 | |
|       type="text/css"
 | |
|       href="https://www.unpkg.com/toastify-js@1.12.0/src/toastify.css" />
 | |
|     <style>
 | |
|       @import url('https://www.unpkg.com/@catppuccin/palette/css/catppuccin.css');
 | |
| 
 | |
|       body {
 | |
|         display: flex;
 | |
|         flex-direction: column;
 | |
|       }
 | |
| 
 | |
|       html,
 | |
|       body,
 | |
|       #container {
 | |
|         height: 100%;
 | |
|         width: 100%;
 | |
|         margin: 0;
 | |
|         padding: 0;
 | |
|         overflow: hidden;
 | |
|         background-color: var(--ctp-mocha-base);
 | |
|       }
 | |
| 
 | |
|       .tap {
 | |
|         background-image: url(../resources-icons-48/tap_type.svg);
 | |
|         background-size: contain;
 | |
|         margin-left: 0.5em;
 | |
|         filter: grayscale() invert();
 | |
|       }
 | |
|       .template,
 | |
|       .info {
 | |
|         background-image: url(../resources-icons-48/info_type.svg);
 | |
|         background-size: contain;
 | |
|         margin-left: 0.5em;
 | |
|         filter: grayscale() invert();
 | |
|       }
 | |
|       .toggle_on {
 | |
|         background-image: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/toggle_on/default/48px.svg);
 | |
|         background-size: contain;
 | |
|         margin-left: 0.5em;
 | |
|         filter: grayscale() invert();
 | |
|         rotate: -90deg;
 | |
|       }
 | |
|       .toggle_off {
 | |
|         background-image: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/toggle_off/default/48px.svg);
 | |
|         background-size: contain;
 | |
|         margin-left: 0.5em;
 | |
|         filter: grayscale() invert();
 | |
|         rotate: -90deg;
 | |
|       }
 | |
|       .group {
 | |
|         background-image: url(../resources-icons-48/group_type.svg);
 | |
|         background-size: contain;
 | |
|         margin-left: 0.5em;
 | |
|         filter: grayscale() invert();
 | |
|       }
 | |
|       .mdi {
 | |
|         margin-left: 0.5em;
 | |
|       }
 | |
| 
 | |
|       .disabled {
 | |
|         opacity: 0.5;
 | |
|       }
 | |
| 
 | |
|       :root {
 | |
|         font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 | |
|       }
 | |
| 
 | |
|       #settings {
 | |
|         display: flex;
 | |
|         flex-direction: row;
 | |
|         justify-content: flex-start;
 | |
|         align-items: center;
 | |
|         padding: 0.4em;
 | |
|         gap: 0.25em;
 | |
|         background-color: var(--ctp-mocha-mantle);
 | |
|         color: var(--ctp-mocha-text);
 | |
|       }
 | |
| 
 | |
|       dialog {
 | |
|         background-color: var(--ctp-mocha-base);
 | |
|         color: var(--ctp-mocha-text);
 | |
|         border: 1px solid var(--ctp-mocha-surface1);
 | |
|         border-radius: 0.25em;
 | |
|         padding: 0.5em;
 | |
|         max-width: min(900px, calc(100% - 10em));
 | |
|         max-height: 500px;
 | |
| 
 | |
|         & > div {
 | |
|           margin-bottom: 0.5em;
 | |
| 
 | |
|           & > p {
 | |
|             margin-inline: 0.5em;
 | |
|           }
 | |
|           & > code {
 | |
|             margin: 0.5em;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         &:focus-within,
 | |
|         &:focus-visible {
 | |
|           outline: none;
 | |
|           border: 1px solid var(--ctp-mocha-teal);
 | |
|         }
 | |
| 
 | |
|         &::backdrop {
 | |
|           background-color: black;
 | |
|           opacity: 0.5;
 | |
|         }
 | |
| 
 | |
|         & h2 {
 | |
|           margin: 0;
 | |
|           padding: 0;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       .row {
 | |
|         display: flex;
 | |
|         flex-direction: row;
 | |
|         justify-content: space-between;
 | |
|         padding: 0.4em;
 | |
|         gap: 0.25em;
 | |
|       }
 | |
| 
 | |
|       #settings:has(.invalid, :invalid) + #container {
 | |
|         opacity: 0.5;
 | |
|         pointer-events: none;
 | |
|       }
 | |
| 
 | |
|       #settings,
 | |
|       dialog {
 | |
|         & input {
 | |
|           background-color: var(--ctp-mocha-surface1);
 | |
|           color: var(--ctp-mocha-text);
 | |
|           border: 1px solid var(--ctp-mocha-surface1);
 | |
|           border-radius: 0.25em;
 | |
|           padding: 0.25em;
 | |
| 
 | |
|           &::placeholder {
 | |
|             color: var(--ctp-mocha-text);
 | |
|             opacity: 0.4;
 | |
|           }
 | |
| 
 | |
|           &:focus-visible {
 | |
|             outline: none;
 | |
|             border: 1px solid var(--ctp-mocha-teal);
 | |
|           }
 | |
| 
 | |
|           &.outofsync {
 | |
|             border: 1px solid var(--ctp-mocha-yellow);
 | |
|           }
 | |
| 
 | |
|           &.invalid,
 | |
|           &:invalid {
 | |
|             border: 1px solid var(--ctp-mocha-red);
 | |
|           }
 | |
| 
 | |
|           flex-grow: 1;
 | |
|           &#api_token {
 | |
|             flex-grow: 0;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         code {
 | |
|           display: block;
 | |
|           background-color: var(--ctp-mocha-surface1);
 | |
|           border-radius: 0.5em;
 | |
|           padding: 1em;
 | |
|           font-family: 'Courier New', Courier, monospace;
 | |
| 
 | |
|           & pre {
 | |
|             margin: 0;
 | |
|             padding: 0;
 | |
|             font-size: 1.2em;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         kbd {
 | |
|           background-color: var(--ctp-mocha-surface1);
 | |
|           border-radius: 0.25em;
 | |
|           padding: 0.25em;
 | |
|           font-family: 'Courier New', Courier, monospace;
 | |
|           font-size: 0.8em;
 | |
|         }
 | |
| 
 | |
|         a {
 | |
|           color: var(--ctp-mocha-teal);
 | |
|           text-decoration: none;
 | |
| 
 | |
|           &:hover {
 | |
|             text-decoration: underline;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         button {
 | |
|           background-color: var(--ctp-mocha-surface1);
 | |
|           color: var(--ctp-mocha-text);
 | |
|           border: 1px solid var(--ctp-mocha-surface1);
 | |
|           border-radius: 0.25em;
 | |
|           padding-inline: 0.5em;
 | |
|           padding-block: 0.25em;
 | |
|           user-select: none;
 | |
| 
 | |
|           &:focus-visible {
 | |
|             outline: none;
 | |
|             border: 1px solid var(--ctp-mocha-teal);
 | |
|           }
 | |
| 
 | |
|           &:hover {
 | |
|             cursor: pointer;
 | |
|             background-color: var(--ctp-mocha-surface0);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         .row {
 | |
|           display: flex;
 | |
|           flex-direction: row;
 | |
|           justify-content: space-between;
 | |
|         }
 | |
| 
 | |
|         button.icon {
 | |
|           border: none;
 | |
|           padding: 0.1em;
 | |
|           margin: 0;
 | |
|           width: 1.8em;
 | |
|           height: 1.8em;
 | |
|           background-color: var(--ctp-mocha-surface1);
 | |
| 
 | |
|           &:disabled {
 | |
|             opacity: 0.5;
 | |
|             cursor: not-allowed;
 | |
|           }
 | |
| 
 | |
|           &:hover {
 | |
|             background-color: var(--ctp-mocha-overlay1);
 | |
|           }
 | |
| 
 | |
|           &::before {
 | |
|             display: block;
 | |
|             content: '';
 | |
|             background-size: contain;
 | |
|             background-repeat: no-repeat;
 | |
|             background-position: center;
 | |
|             filter: grayscale() invert();
 | |
|             width: 100%;
 | |
|             height: 100%;
 | |
|             margin: 0;
 | |
|             padding: 0;
 | |
|           }
 | |
|           &[icon='close'] {
 | |
|             &:hover {
 | |
|               background-color: var(--ctp-mocha-red);
 | |
|             }
 | |
|             &::before {
 | |
|               background-image: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/close/default/48px.svg);
 | |
|             }
 | |
|           }
 | |
|           &[icon='download']::before {
 | |
|             background-image: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/download/default/48px.svg);
 | |
|           }
 | |
|           &[icon='copy']::before {
 | |
|             background-image: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/content_copy/default/48px.svg);
 | |
|           }
 | |
|           &[icon='info']::before {
 | |
|             background-image: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/info/default/48px.svg);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       ::-webkit-scrollbar {
 | |
|         width: 0.6em;
 | |
|       }
 | |
| 
 | |
|       ::-webkit-scrollbar-track-piece {
 | |
|         background-color: var(--ctp-mocha-mantle);
 | |
|       }
 | |
| 
 | |
|       ::-webkit-scrollbar-thumb {
 | |
|         background-color: var(--ctp-mocha-surface1);
 | |
|       }
 | |
| 
 | |
|       ::-webkit-scrollbar-thumb:hover {
 | |
|         background-color: var(--ctp-mocha-overlay1);
 | |
|       }
 | |
|     </style>
 | |
|     <link
 | |
|       rel="stylesheet"
 | |
|       href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css"
 | |
|       defer />
 | |
|   </head>
 | |
|   <body>
 | |
|     <div id="settings">
 | |
|       <input
 | |
|         required
 | |
|         placeholder="https://<home-assistant>/api"
 | |
|         title="Home Assistant API URL `https://<home-assistant>/api`"
 | |
|         type="url"
 | |
|         name="api_url"
 | |
|         id="api_url"
 | |
|         pattern="https://.*/api" />
 | |
|       <input
 | |
|         placeholder="https://<home-assistant>/local/garmin/menu.json"
 | |
|         title="Menu JSON URL `https://<home-assistant>/local/garmin/menu.json`"
 | |
|         type="url"
 | |
|         name="menu_url"
 | |
|         id="menu_url"
 | |
|         pattern="https://.*\.json" />
 | |
|       <button
 | |
|         title="Download content of menu url and put it in the editor"
 | |
|         class="icon"
 | |
|         icon="download"
 | |
|         id="download"
 | |
|         type="button"></button>
 | |
|       <button
 | |
|         title="Copy the content of the editor to the clipboard"
 | |
|         class="icon"
 | |
|         icon="copy"
 | |
|         id="copy"
 | |
|         type="button"></button>
 | |
|       <input
 | |
|         required
 | |
|         autocomplete="new-password"
 | |
|         placeholder="API Token"
 | |
|         title="Home Assistant Long-lived Access Token"
 | |
|         type="password"
 | |
|         name="api_token"
 | |
|         id="api_token" />
 | |
|       <button id="troubleshooting" type="button">Troubleshooting</button>
 | |
|       <button
 | |
|         title="How to use the editor"
 | |
|         class="icon"
 | |
|         icon="info"
 | |
|         id="info"
 | |
|         type="button"></button>
 | |
|     </div>
 | |
| 
 | |
|     <div id="container"></div>
 | |
| 
 | |
|     <dialog id="troubleshooting-dialog">
 | |
|       <div>
 | |
|         <div class="row">
 | |
|           <h2>GarminHomeAssistant Troubleshooting</h2>
 | |
|           <button
 | |
|             title="Close"
 | |
|             class="icon"
 | |
|             icon="close"
 | |
|             onclick="this.parentElement.parentElement.parentElement.close()"
 | |
|             type="button"></button>
 | |
|         </div>
 | |
| 
 | |
|         <p>
 | |
|           This is a troubleshooting tool for the GarminHomeAssistant watch app.
 | |
|           It allows you to test your Home Assistant API connection.
 | |
|         </p>
 | |
| 
 | |
|         <div class="row">
 | |
|           <div id="test-api-response">Check now!</div>
 | |
|           <button
 | |
|             title="Check the status of the API"
 | |
|             id="test-api"
 | |
|             type="button">
 | |
|             Test API
 | |
|           </button>
 | |
|         </div>
 | |
|         <div class="row">
 | |
|           <div id="test-menu-response">Check now!</div>
 | |
|           <button
 | |
|             title="Check the availability of the menu configuration"
 | |
|             id="test-menu"
 | |
|             type="button">
 | |
|             Test menu
 | |
|           </button>
 | |
|         </div>
 | |
|       </div>
 | |
|     </dialog>
 | |
| 
 | |
|     <dialog id="info-dialog">
 | |
|       <div>
 | |
|         <div class="row">
 | |
|           <h2>GarminHomeAssistant Web Editor</h2>
 | |
|           <button
 | |
|             title="Close"
 | |
|             class="icon"
 | |
|             icon="close"
 | |
|             onclick="this.parentElement.parentElement.parentElement.close()"
 | |
|             type="button"></button>
 | |
|         </div>
 | |
| 
 | |
|         <p>
 | |
|           This is the web editor for the GarminHomeAssistant watch app, it
 | |
|           offers enhanced schema checking and validation over the original JSON
 | |
|           schema by using the HomeAssistant API to create a schema based on your
 | |
|           HomeAssistant configuration.
 | |
|         </p>
 | |
| 
 | |
|         <p>
 | |
|           This editor makes use of the same credentials as the watch app (these
 | |
|           can be pasted in the top bar of this page). However in order for this
 | |
|           editor to work, you will need to amend the CORS settings of your
 | |
|           HomeAssistant instance. Add this to your configuration.yaml file:
 | |
|         </p>
 | |
| 
 | |
|         <code>
 | |
|           <pre id="cors-settings" data-lang="yaml">
 | |
| http:
 | |
|   cors_allowed_origins:
 | |
|     - https://house-of-abbey.github.io</pre
 | |
|           >
 | |
|         </code>
 | |
| 
 | |
|         <p>
 | |
|           Once you have added this to your configuration.yaml file, you will
 | |
|           need to restart HomeAssistant. After HomeAssistant is restarted,
 | |
|           reload this page.
 | |
|         </p>
 | |
| 
 | |
|         <p>
 | |
|           Now you should have validation and autocompletion, anywhere in the
 | |
|           editor, press <kbd>Ctrl</kbd> + <kbd>Space</kbd> to see the available
 | |
|           options. You will also see red lines under syntax errors and yellow
 | |
|           lines under validation errors. Hover over these lines to see the
 | |
|           message. You can also click on the `Run Action` and `Toggle` buttons
 | |
|           to test your actions.
 | |
|         </p>
 | |
| 
 | |
|         <p>
 | |
|           In the top bar there are 4 buttons, the first button will download the
 | |
|           online version of your menu.json file and put it in the editor. The
 | |
|           second button will copy the content of the editor to the clipboard.
 | |
|           The third button will open the troubleshooting dialog, this will allow
 | |
|           you to test your HomeAssistant API connection. The fourth button will
 | |
|           open this dialog.
 | |
|         </p>
 | |
| 
 | |
|         <p>
 | |
|           For directions on how to write your menu.json file, please see the
 | |
|           <a
 | |
|             href="https://github.com/house-of-abbey/GarminHomeAssistant#dashboard-definition"
 | |
|             >README</a
 | |
|           >
 | |
|           in the GitHub repo.
 | |
|         </p>
 | |
|       </div>
 | |
|     </dialog>
 | |
| 
 | |
|     <script src="https://www.unpkg.com/monaco-editor@0.52.2/min/vs/loader.js"></script>
 | |
|     <script src="https://www.unpkg.com/json-ast-comments@1.1.1/lib/json.js"></script>
 | |
|     <script src="https://www.unpkg.com/toastify-js@1.12.0/src/toastify.js"></script>
 | |
|     <script src="https://code.iconify.design/1/1.0.6/iconify.min.js"></script>
 | |
|     <script type="module" src="./main.js"></script>
 | |
|   </body>
 | |
| </html>
 |