| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552 |
- {% extends "base.html" %} {% block title %}Settings - {{ app_name or 'Dune Weaver' }}{%
- endblock %}
- {% block additional_styles %}
- /* Dark mode styles for settings page */
- .dark .bg-white {
- background-color: #262626;
- }
- .dark .text-slate-900 {
- color: #ffffff;
- }
- .dark .text-slate-800 {
- color: #f8fafc;
- }
- .dark .text-slate-700 {
- color: #f1f5f9;
- }
- .dark .text-slate-600 {
- color: #e2e8f0;
- }
- .dark .text-slate-500 {
- color: #e2e8f0;
- }
- /* Label overrides for better visibility */
- .dark label {
- color: #f1f5f9;
- }
- .dark .border-slate-200 {
- border-color: #404040;
- }
- .dark .border-slate-300 {
- border-color: #404040;
- }
- .dark .divide-slate-100 {
- border-color: #333333;
- }
- .dark .bg-slate-50 {
- background-color: #262626;
- }
- .dark .hover\:bg-slate-50:hover {
- background-color: #404040;
- }
- .dark .bg-slate-100 {
- background-color: #404040;
- }
- .dark .form-input,
- .dark input[type="number"],
- .dark input[type="text"],
- .dark input[type="time"] {
- background-color: #1f1f1f;
- border-color: #404040;
- color: #e5e5e5;
- }
- .dark .form-input::placeholder {
- color: #9ca3af;
- }
- .dark .form-input:focus {
- border-color: #0c7ff2;
- ring-color: #0c7ff2;
- }
- .dark input[type="time"]::-webkit-calendar-picker-indicator {
- filter: invert(1);
- }
- .dark .form-select {
- background-color: #1f1f1f;
- border-color: #404040;
- color: #e5e5e5;
- }
- .dark .form-select:focus {
- border-color: #0c7ff2;
- ring-color: #0c7ff2;
- }
- .dark .focus\:ring-sky-500:focus {
- ring-color: #0c7ff2;
- }
- .dark .focus\:border-sky-500:focus {
- border-color: #0c7ff2;
- }
- .dark .hover\:text-gray-700:hover {
- color: #e5e5e5;
- }
- .dark .text-gray-400 {
- color: #9ca3af;
- }
- /* Autocomplete suggestions dark mode */
- .dark #clearFromInSuggestions,
- .dark #clearFromOutSuggestions {
- background-color: #262626;
- border-color: #404040;
- }
- .dark .suggestion-item {
- color: #e5e5e5;
- }
- .dark .suggestion-item:hover {
- background-color: #404040;
- }
- .dark .suggestion-item.selected {
- background-color: #0c7ff2;
- color: white;
- }
- /* Light mode autocomplete styles */
- .suggestion-item {
- padding: 8px 12px;
- cursor: pointer;
- color: #1f2937;
- transition: background-color 0.15s;
- }
- .suggestion-item:hover {
- background-color: #f3f4f6;
- }
- .suggestion-item.selected {
- background-color: #0c7ff2;
- color: white;
- }
- .suggestion-item mark {
- background-color: #fef3c7;
- font-weight: 600;
- }
- .dark .suggestion-item mark {
- background-color: #92400e;
- color: #fef3c7;
- }
- /* Toggle switch styles */
- .switch {
- position: relative;
- display: inline-block;
- width: 60px;
- height: 34px;
- }
- .switch input {
- opacity: 0;
- width: 0;
- height: 0;
- }
- .slider {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #ccc;
- transition: .4s;
- }
- .slider:before {
- position: absolute;
- content: "";
- height: 26px;
- width: 26px;
- left: 4px;
- bottom: 4px;
- background-color: white;
- transition: .4s;
- }
- input:checked + .slider {
- background-color: #0c7ff2;
- }
- input:focus + .slider {
- box-shadow: 0 0 1px #0c7ff2;
- }
- input:checked + .slider:before {
- transform: translateX(26px);
- }
- .slider.round {
- border-radius: 34px;
- }
- .slider.round:before {
- border-radius: 50%;
- }
- /* Dark mode for switches */
- .dark .slider {
- background-color: #404040;
- }
- .dark input:checked + .slider {
- background-color: #0c7ff2;
- }
- /* Spin animation for loading states */
- @keyframes spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
- }
- .animate-spin {
- animation: spin 1s linear infinite;
- }
- /* Collapsible section styles */
- .section-header {
- cursor: pointer;
- user-select: none;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .section-header:hover {
- background-color: #f8fafc;
- }
- .dark .section-header:hover {
- background-color: #333333;
- }
- .section-toggle-icon {
- transition: transform 0.2s ease-in-out;
- }
- .section-header.collapsed .section-toggle-icon {
- transform: rotate(-90deg);
- }
- .section-header.collapsed {
- border-bottom: none;
- }
- .section-content {
- overflow: hidden;
- transition: max-height 0.3s ease-in-out, opacity 0.2s ease-in-out, padding 0.2s ease-in-out;
- max-height: 2000px;
- opacity: 1;
- }
- .section-content.collapsed {
- max-height: 0;
- opacity: 0;
- padding-top: 0 !important;
- padding-bottom: 0 !important;
- }
- /* Time slot specific styles */
- .time-slot-item {
- background-color: #f8fafc;
- border: 1px solid #e2e8f0;
- border-radius: 8px;
- padding: 16px;
- transition: all 0.15s;
- }
- .dark .time-slot-item {
- background-color: #1e293b;
- border-color: #475569;
- }
- .time-slot-item:hover {
- border-color: #cbd5e1;
- }
- .dark .time-slot-item:hover {
- border-color: #64748b;
- }
- /* Info box dark mode - grey theme */
- .dark .bg-blue-50 {
- background-color: #1f1f1f;
- }
- .dark .border-blue-200 {
- border-color: #404040;
- }
- .dark .text-blue-600 {
- color: #e2e8f0;
- }
- .dark .text-blue-800 {
- color: #f1f5f9;
- }
- .dark .text-blue-700 {
- color: #e2e8f0;
- }
- /* Amber box dark mode - grey theme */
- .dark .bg-amber-50 {
- background-color: #1f1f1f;
- }
- .dark .border-amber-200 {
- border-color: #404040;
- }
- .dark .text-amber-600 {
- color: #f1f5f9;
- }
- /* Sky box dark mode - grey theme (Still Sands options) */
- .dark .bg-sky-50 {
- background-color: #1f1f1f;
- }
- .dark .border-sky-200 {
- border-color: #404040;
- }
- /* Select dropdown dark mode */
- .dark select {
- background-color: #1f1f1f;
- border-color: #404040;
- color: #e5e5e5;
- }
- .dark select:focus {
- border-color: #0c7ff2;
- }
- .dark select option {
- background-color: #1f1f1f;
- color: #e5e5e5;
- }
- .dark select optgroup {
- background-color: #262626;
- color: #9ca3af;
- }
- {% endblock %}
- {% block content %}
- <div class="layout-content-container flex flex-col w-full max-w-4xl gap-8 pt-2 pb-[75px]">
- <div
- class="flex flex-wrap justify-between items-center p-4 bg-white rounded-xl shadow-sm mt-2 sm:mt-8"
- >
- <h1
- class="text-slate-900 tracking-tight text-2xl sm:text-3xl font-bold leading-tight"
- >
- Settings
- </h1>
- </div>
- <section class="bg-white rounded-xl shadow-sm overflow-hidden">
- <h2
- class="section-header text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
- onclick="toggleSection(this)"
- >
- <span>Device Connection</span>
- <span class="material-icons section-toggle-icon text-slate-400">expand_more</span>
- </h2>
- <div class="section-content">
- <div
- class="flex items-center gap-4 px-6 py-5 hover:bg-slate-50 transition-colors"
- >
- <div
- class="text-slate-600 flex items-center justify-center rounded-lg bg-slate-100 shrink-0 size-12"
- >
- <span class="material-icons text-3xl">usb_off</span>
- </div>
- <div class="flex-1">
- <p class="text-slate-800 text-base font-medium leading-normal">
- Status
- </p>
- <p
- id="serialStatus"
- class="text-red-500 text-sm font-medium leading-normal"
- >
- Disconnected
- </p>
- </div>
- <button
- id="disconnectButton"
- class="text-xs font-medium bg-red-100 hover:bg-red-200 text-red-700 dark:bg-red-900 dark:hover:bg-red-800 dark:text-red-200 px-3 py-1.5 rounded-md transition-colors"
- hidden
- >
- Disconnect
- </button>
- </div>
- <div id="portSelectionDiv" class="px-6 py-5 space-y-4">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal"
- >Available Serial Ports</span
- >
- <div class="flex gap-3 items-center">
- <select
- id="portSelect"
- class="form-select flex-1 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-medium leading-normal transition-colors "
- >
- <option value="">Select a port...</option>
- </select>
- <button
- id="connectButton"
- class="flex items-center justify-center gap-2 min-w-[100px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors flex-shrink-0"
- >
- <span class="material-icons text-lg">cable</span>
- <span class="truncate">Connect</span>
- </button>
- </div>
- <p class="text-xs text-slate-500 mt-2">
- Select a port and click 'Connect' to establish a connection.
- </p>
- </label>
- </div>
- <!-- Preferred Port Configuration -->
- <div class="px-6 py-5">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal flex items-center gap-2">
- <span class="material-icons text-slate-600 text-base">star</span>
- Preferred Port for Auto-Connect
- </span>
- <div class="flex gap-3 items-center">
- <select
- id="preferredPortSelect"
- class="form-select flex-1 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-medium leading-normal transition-colors"
- >
- <option value="">No preference (auto-detect)</option>
- </select>
- <button
- id="savePreferredPort"
- class="flex items-center justify-center gap-2 min-w-[100px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors flex-shrink-0"
- >
- <span class="material-icons text-lg">save</span>
- <span class="truncate">Save</span>
- </button>
- </div>
- <p class="text-xs text-slate-500 mt-2">
- When multiple ports are available, this port will be used automatically on startup. If set to "No preference", the system will try the last connected port or the first available port.
- </p>
- <p id="currentPreferredPort" class="text-xs text-sky-600 mt-1 hidden">
- <span class="material-icons text-xs align-middle">check_circle</span>
- <span id="preferredPortDisplay"></span>
- </p>
- </label>
- </div>
- </div>
- </section>
- <!-- Machine Settings Section -->
- <section id="machineSection" class="bg-white rounded-xl shadow-sm overflow-hidden">
- <h2
- class="section-header collapsed text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
- onclick="toggleSection(this)"
- >
- <span>Machine Settings</span>
- <span class="material-icons section-toggle-icon text-slate-400">expand_more</span>
- </h2>
- <div class="section-content collapsed px-6 py-5 space-y-6">
- <!-- Table Type Override -->
- <div class="space-y-3">
- <label class="text-sm font-medium text-slate-700 flex items-center gap-2">
- <span class="material-icons text-slate-600 text-base">precision_manufacturing</span>
- Table Type
- </label>
- <!-- Current detected type display -->
- <div id="detectedTableTypeContainer" class="flex items-center gap-2 text-sm text-slate-600 bg-slate-50 rounded-lg p-3">
- <span class="material-icons text-slate-500 text-base">info</span>
- <span>Detected: <span id="detectedTableType" class="font-medium text-slate-800">Unknown</span></span>
- </div>
- <div class="flex gap-3 items-center">
- <select
- id="tableTypeSelect"
- class="form-select flex-1 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-medium leading-normal transition-colors"
- >
- <option value="">Auto-detect (use detected type)</option>
- </select>
- <button
- id="saveTableType"
- class="flex items-center justify-center gap-2 min-w-[100px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors flex-shrink-0"
- >
- <span class="material-icons text-lg">save</span>
- <span class="truncate">Save</span>
- </button>
- </div>
- <p class="text-xs text-slate-500">
- Override the automatically detected table type. This affects gear ratio calculations and homing behavior. Leave as "Auto-detect" unless you need to manually specify your table type.
- </p>
- </div>
- <!-- Info box -->
- <div class="text-xs text-slate-600 bg-blue-50 border border-blue-200 rounded-lg p-3">
- <div class="flex items-start gap-2">
- <span class="material-icons text-blue-600 text-base">info</span>
- <div>
- <p class="font-medium text-blue-800">Table Type Detection</p>
- <ul class="mt-1 space-y-1 text-blue-700">
- <li>• Table type is normally detected automatically from GRBL settings</li>
- <li>• Use override if auto-detection is incorrect for your hardware</li>
- <li>• Changes take effect on next connection/homing</li>
- </ul>
- </div>
- </div>
- </div>
- </div>
- </section>
- <!-- Homing Configuration Section -->
- <section id="homingSection" class="bg-white rounded-xl shadow-sm overflow-hidden">
- <h2
- class="section-header collapsed text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
- onclick="toggleSection(this)"
- >
- <span>Homing Configuration</span>
- <span class="material-icons section-toggle-icon text-slate-400">expand_more</span>
- </h2>
- <div class="section-content collapsed px-6 py-5 space-y-6">
- <!-- Homing Mode Selection -->
- <div class="space-y-3">
- <label class="text-sm font-medium text-slate-700 flex items-center gap-2">
- <span class="material-icons text-slate-600 text-base">home</span>
- Homing Mode
- </label>
- <div class="space-y-3">
- <!-- Crash Homing Option -->
- <label class="flex items-start gap-3 p-3 border border-slate-300 rounded-lg cursor-pointer hover:bg-slate-50 transition-colors">
- <input
- type="radio"
- name="homingMode"
- value="0"
- id="homingModeCrash"
- class="mt-0.5 w-4 h-4 text-sky-600 focus:ring-sky-500"
- />
- <div class="flex-1">
- <div class="text-sm font-medium text-slate-700">Crash Homing</div>
- <div class="text-xs text-slate-500 mt-1">
- Y axis moves until physical stop, then theta and rho set to 0 (no x0 y0 command)
- </div>
- </div>
- </label>
- <!-- Sensor Homing Option -->
- <label class="flex items-start gap-3 p-3 border border-slate-300 rounded-lg cursor-pointer hover:bg-slate-50 transition-colors">
- <input
- type="radio"
- name="homingMode"
- value="1"
- id="homingModeSensor"
- class="mt-0.5 w-4 h-4 text-sky-600 focus:ring-sky-500"
- />
- <div class="flex-1">
- <div class="text-sm font-medium text-slate-700">Sensor Homing</div>
- <div class="text-xs text-slate-500 mt-1">
- Homes both X and Y axes using sensors
- </div>
- </div>
- </label>
- </div>
- </div>
- <!-- Compass Reference Point (Sensor mode only) -->
- <div id="compassOffsetContainer" class="space-y-2">
- <label for="angularOffsetInput" class="text-sm font-medium text-slate-700 flex items-center gap-2">
- <span class="material-icons text-slate-600 text-base">explore</span>
- Sensor offset (degrees) <span class="text-xs text-slate-400">(Sensor mode only)</span>
- </label>
- <input
- type="number"
- id="angularOffsetInput"
- min="0"
- max="360"
- step="0.1"
- value="0"
- class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-sky-500 focus:border-sky-500 text-sm"
- placeholder="0.0"
- />
- <p class="text-xs text-slate-500">
- Set the angle (in degrees) where your radial arm to be offset by. You want to choose a value here so that the radial arm will point East.
- </p>
- </div>
- <!-- Homing Info Box -->
- <div id="homingInfoBox" class="text-xs text-slate-600 bg-blue-50 border border-blue-200 rounded-lg p-3">
- <div class="flex items-start gap-2">
- <span class="material-icons text-blue-600 text-base">info</span>
- <div id="homingInfoContent">
- <p class="font-medium text-blue-800">Crash Homing Mode:</p>
- <ul class="mt-1 space-y-1 text-blue-700">
- <li>• Y axis moves -22mm (or -30mm for mini) until physical stop</li>
- <li>• Theta set to 0, rho set to 0</li>
- <li>• No x0 y0 command sent</li>
- <li>• No hardware sensors required</li>
- </ul>
- </div>
- </div>
- </div>
- <!-- Auto-Home During Playlists -->
- <div class="bg-slate-50 rounded-lg p-4 space-y-4">
- <div class="flex items-center justify-between">
- <div class="flex-1">
- <h3 class="text-slate-800 text-base font-semibold flex items-center gap-2">
- <span class="material-icons text-slate-600 text-base">autorenew</span>
- Auto-Home During Playlists
- </h3>
- <p class="text-xs text-slate-500 mt-1">
- Automatically perform homing after a certain number of patterns during playlist playback to maintain accuracy.
- </p>
- </div>
- <label class="switch">
- <input type="checkbox" id="autoHomeEnabledToggle">
- <span class="slider round"></span>
- </label>
- </div>
- <div id="autoHomeSettings" style="display: none;">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Home after every X patterns</span>
- <input
- type="number"
- id="autoHomeAfterPatternsInput"
- min="1"
- max="100"
- step="1"
- value="5"
- class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-sky-500 focus:border-sky-500 text-sm"
- placeholder="5"
- />
- <p class="text-xs text-slate-500">
- Homing will occur right after the clear pattern completes, before the next actual pattern begins.
- </p>
- </label>
- </div>
- </div>
- <div class="flex justify-end">
- <button
- id="saveHomingConfig"
- class="flex items-center justify-center gap-2 min-w-[140px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors"
- >
- <span class="material-icons text-lg">save</span>
- <span class="truncate">Save Configuration</span>
- </button>
- </div>
- </div>
- </section>
- <section class="bg-white rounded-xl shadow-sm overflow-hidden">
- <h2
- class="section-header collapsed text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
- onclick="toggleSection(this)"
- >
- <span>Application Settings</span>
- <span class="material-icons section-toggle-icon text-slate-400">expand_more</span>
- </h2>
- <div class="section-content collapsed px-6 py-5 space-y-6">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal"
- >Application Name</span
- >
- <div class="flex gap-3 items-center">
- <div class="relative flex-1">
- <input
- id="appNameInput"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 pr-10 text-base font-normal leading-normal transition-colors"
- placeholder="e.g., Dune Weaver"
- value="Dune Weaver"
- />
- <button
- type="button"
- onclick="document.getElementById('appNameInput').value='Dune Weaver';"
- class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-700"
- aria-label="Reset to default"
- title="Reset to default"
- >
- <span class="material-icons">restart_alt</span>
- </button>
- </div>
- <button
- id="saveAppName"
- class="flex items-center justify-center gap-2 min-w-[140px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors flex-shrink-0"
- >
- <span class="material-icons text-lg">save</span>
- <span class="truncate">Save Name</span>
- </button>
- </div>
- <p class="text-xs text-slate-500 mt-2">
- This name will appear in the browser tab and at the top of every page.
- </p>
- </label>
- <!-- Custom Logo Section -->
- <div class="border-t border-slate-200 pt-6">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Custom Logo & Favicon</span>
- <p class="text-xs text-slate-500 mb-2">
- Upload a custom logo to replace the default. The favicon (browser tab icon) will be automatically generated from your logo. Recommended size: 180x180 pixels. Supported formats: PNG, JPG, GIF, WebP, SVG.
- </p>
- <div class="flex gap-4 items-start">
- <!-- Logo Preview -->
- <div class="flex-shrink-0">
- <div id="logoPreviewContainer" class="w-16 h-16 rounded-full shadow border border-slate-200 overflow-hidden bg-slate-100 flex items-center justify-center">
- <img id="logoPreview" src="{% if custom_logo %}/static/custom/{{ custom_logo }}{% else %}/static/apple-touch-icon.png{% endif %}" alt="Logo Preview" class="w-full h-full object-cover"/>
- </div>
- </div>
- <!-- Upload Controls -->
- <div class="flex-1 space-y-2">
- <div class="flex gap-2 flex-wrap">
- <label class="flex items-center justify-center gap-2 cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors">
- <span class="material-icons text-lg">upload</span>
- <span>Upload Logo</span>
- <input type="file" id="logoFileInput" accept=".png,.jpg,.jpeg,.gif,.webp,.svg" class="hidden" />
- </label>
- <button
- type="button"
- id="resetLogoBtn"
- class="flex items-center justify-center gap-2 cursor-pointer rounded-lg h-10 px-4 border border-slate-300 hover:bg-slate-50 text-slate-700 text-sm font-medium leading-normal transition-colors {% if not custom_logo %}hidden{% endif %}"
- >
- <span class="material-icons text-lg">restart_alt</span>
- <span>Reset to Default</span>
- </button>
- </div>
- <p id="logoUploadStatus" class="text-xs text-slate-500"></p>
- </div>
- </div>
- </label>
- </div>
- </div>
- </section>
- <section class="bg-white rounded-xl shadow-sm overflow-hidden">
- <h2
- class="section-header collapsed text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
- onclick="toggleSection(this)"
- >
- <span>Pattern Clearing</span>
- <span class="material-icons section-toggle-icon text-slate-400">expand_more</span>
- </h2>
- <div class="section-content collapsed px-6 py-5 space-y-6">
- <p class="text-sm text-slate-600">
- Customize the clearing behavior used when transitioning between patterns. Set custom patterns and speed to control how sand is distributed.
- </p>
- <!-- Clearing Speed Section -->
- <div class="bg-slate-50 rounded-lg p-4 space-y-4">
- <h3 class="text-slate-800 text-base font-semibold">Clearing Speed</h3>
- <p class="text-sm text-slate-600">
- Set a custom speed for clearing patterns. Leave empty to use the default pattern speed. This allows clearing patterns to run at a different speed than regular patterns.
- </p>
- <div class="flex flex-col gap-1.5">
- <label for="clearPatternSpeedInput" class="text-slate-700 text-sm font-medium leading-normal">
- Speed (steps per minute)
- </label>
- <div class="flex gap-3 items-center">
- <input
- id="clearPatternSpeedInput"
- type="number"
- min="50"
- max="2000"
- step="50"
- class="form-input flex-1 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-normal leading-normal transition-colors"
- placeholder="Default (use pattern speed)"
- value=""
- />
- <button
- id="saveClearSpeed"
- class="flex items-center justify-center gap-2 min-w-[120px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors"
- >
- <span class="material-icons text-lg">save</span>
- <span class="truncate">Save Speed</span>
- </button>
- </div>
- <div id="effectiveClearSpeed" class="text-xs text-slate-500 mt-1"></div>
- </div>
- </div>
- <!-- Custom Patterns Section -->
- <div class="bg-slate-50 rounded-lg p-4 space-y-4">
- <h3 class="text-slate-800 text-base font-semibold">Custom Clear Patterns</h3>
- <p class="text-sm text-slate-600">
- Choose specific patterns to use when clearing. Leave empty to use the default clearing behavior.
- </p>
-
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
- <div class="flex flex-col gap-1.5">
- <label for="customClearFromInInput" class="text-slate-700 text-sm font-medium leading-normal">Clear From Center Pattern</label>
- <div class="relative">
- <input
- id="customClearFromInInput"
- type="text"
- class="form-input w-full resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 pr-10 text-base font-normal leading-normal transition-colors"
- placeholder="Type to search patterns or leave empty for default"
- autocomplete="off"
- />
- <button
- type="button"
- id="clearFromInClear"
- class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hidden"
- aria-label="Clear selection"
- title="Clear selection"
- >
- <span class="material-icons text-xl">close</span>
- </button>
- <div id="clearFromInSuggestions" class="absolute z-10 w-full mt-1 bg-white border border-slate-300 rounded-lg shadow-lg max-h-60 overflow-y-auto hidden"></div>
- </div>
- <p class="text-xs text-slate-500 mt-1">
- Pattern to use when clearing from the center outward.
- </p>
- </div>
- <div class="flex flex-col gap-1.5">
- <label for="customClearFromOutInput" class="text-slate-700 text-sm font-medium leading-normal">Clear From Perimeter Pattern</label>
- <div class="relative">
- <input
- id="customClearFromOutInput"
- type="text"
- class="form-input w-full resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 pr-10 text-base font-normal leading-normal transition-colors"
- placeholder="Type to search patterns or leave empty for default"
- autocomplete="off"
- />
- <button
- type="button"
- id="clearFromOutClear"
- class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hidden"
- aria-label="Clear selection"
- title="Clear selection"
- >
- <span class="material-icons text-xl">close</span>
- </button>
- <div id="clearFromOutSuggestions" class="absolute z-10 w-full mt-1 bg-white border border-slate-300 rounded-lg shadow-lg max-h-60 overflow-y-auto hidden"></div>
- </div>
- <p class="text-xs text-slate-500 mt-1">
- Pattern to use when clearing from the perimeter inward.
- </p>
- </div>
- </div>
- <div class="flex justify-end">
- <button
- id="saveClearPatterns"
- class="flex items-center justify-center gap-2 min-w-[140px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors"
- >
- <span class="material-icons text-lg">save</span>
- <span class="truncate">Save Patterns</span>
- </button>
- </div>
- </div>
- </div>
- </section>
- <section class="bg-white rounded-xl shadow-sm overflow-hidden">
- <h2
- class="section-header collapsed text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
- onclick="toggleSection(this)"
- >
- <span>LED Controller Configuration</span>
- <span class="material-icons section-toggle-icon text-slate-400">expand_more</span>
- </h2>
- <div class="section-content collapsed px-6 py-5 space-y-6">
- <!-- LED Provider Selection -->
- <div class="flex flex-col gap-2">
- <span class="text-slate-700 text-sm font-medium leading-normal">LED Provider</span>
- <div class="flex gap-3">
- <label class="flex items-center gap-2 cursor-pointer">
- <input type="radio" name="ledProvider" value="none" id="ledProviderNone" class="w-4 h-4 text-sky-600 border-slate-300 focus:ring-sky-500">
- <span class="text-sm text-slate-700">None</span>
- </label>
- <label class="flex items-center gap-2 cursor-pointer">
- <input type="radio" name="ledProvider" value="wled" id="ledProviderWled" class="w-4 h-4 text-sky-600 border-slate-300 focus:ring-sky-500">
- <span class="text-sm text-slate-700">WLED</span>
- </label>
- <label class="flex items-center gap-2 cursor-pointer">
- <input type="radio" name="ledProvider" value="dw_leds" id="ledProviderDwLeds" class="w-4 h-4 text-sky-600 border-slate-300 focus:ring-sky-500">
- <span class="text-sm text-slate-700">DW LEDs (Local GPIO)</span>
- </label>
- </div>
- <p class="text-xs text-slate-500">
- Select your LED control system (settings are mutually exclusive)
- </p>
- </div>
- <!-- WLED Configuration (shown when WLED is selected) -->
- <div id="wledConfig" class="flex flex-col gap-4 hidden">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">WLED IP Address</span>
- <div class="relative flex-1">
- <input
- id="wledIpInput"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 pr-10 text-base font-normal leading-normal transition-colors"
- placeholder="e.g., 192.168.1.100"
- value=""
- />
- <button
- type="button"
- onclick="document.getElementById('wledIpInput').value='';"
- class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-700"
- aria-label="Clear WLED IP"
- >
- <span class="material-icons">close</span>
- </button>
- </div>
- <p class="text-xs text-slate-500">
- Enter the IP address of your WLED controller
- </p>
- </label>
- </div>
- <!-- DW LEDs Configuration (shown when DW LEDs is selected) -->
- <div id="dwLedsConfig" class="flex flex-col gap-4 hidden">
- <div class="bg-blue-50 border border-blue-200 rounded-lg p-3">
- <div class="flex items-start gap-2">
- <span class="material-icons text-blue-600 text-base">info</span>
- <div class="text-xs text-blue-700">
- <p class="font-medium text-blue-800">Supported LED Strips</p>
- <p class="mt-1"><strong>RGB (3-channel):</strong> WS2811, WS2812, WS2812B, WS2813, WS2815 and other WS281x strips.</p>
- <p class="mt-1"><strong>RGBW (4-channel):</strong> SK6812, SK6812W and other RGBW strips with dedicated white channel.</p>
- </div>
- </div>
- </div>
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Number of LEDs</span>
- <input
- id="dwLedNumLeds"
- type="number"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-normal leading-normal transition-colors"
- placeholder="60"
- value="60"
- min="1"
- max="1000"
- />
- <p class="text-xs text-slate-500">
- Total number of LEDs in your WS281x strip
- </p>
- </label>
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">GPIO Pin</span>
- <select
- id="dwLedGpioPin"
- class="form-select flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 px-4 text-base font-normal leading-normal transition-colors"
- >
- <option value="12">GPIO 12 (PWM0)</option>
- <option value="13">GPIO 13 (PWM1)</option>
- <option value="18">GPIO 18 (PWM0)</option>
- <option value="19">GPIO 19 (PWM1)</option>
- </select>
- <p class="text-xs text-slate-500">
- Select a PWM-capable GPIO pin for WS281x timing
- </p>
- </label>
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Pixel Color Order</span>
- <select
- id="dwLedPixelOrder"
- class="form-select flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 px-4 text-base font-normal leading-normal transition-colors"
- >
- <optgroup label="RGB Strips (3-channel)">
- <option value="GRB" selected>GRB - WS2812/WS2812B (most common)</option>
- <option value="RGB">RGB - WS2815/WS2811 and some variants</option>
- <option value="BGR">BGR - Some WS2811 variants</option>
- <option value="RBG">RBG - Rare variant</option>
- <option value="GBR">GBR - Rare variant</option>
- <option value="BRG">BRG - Rare variant</option>
- </optgroup>
- <optgroup label="RGBW Strips (4-channel)">
- <option value="GRBW">GRBW - SK6812 RGBW (most common)</option>
- <option value="RGBW">RGBW - SK6812 RGBW variant</option>
- </optgroup>
- </select>
- <p class="text-xs text-slate-500">
- Most WS2812B strips use GRB. SK6812 RGBW strips typically use GRBW. If colors appear wrong, try different orders.
- </p>
- </label>
- </div>
- <!-- Save Button -->
- <button
- id="saveLedConfig"
- class="flex items-center justify-center gap-2 w-full sm:w-auto cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors"
- >
- <span class="material-icons text-lg">save</span>
- <span class="truncate">Save LED Configuration</span>
- </button>
- </div>
- </section>
- <!-- MQTT Configuration Section -->
- <section class="bg-white rounded-xl shadow-sm overflow-hidden">
- <h2
- class="section-header collapsed text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
- onclick="toggleSection(this)"
- >
- <span>Home Assistant Integration</span>
- <span class="material-icons section-toggle-icon text-slate-400">expand_more</span>
- </h2>
- <div class="section-content collapsed px-6 py-5 space-y-6">
- <!-- MQTT Enable Toggle -->
- <div class="flex items-center justify-between">
- <div class="flex-1">
- <h3 class="text-slate-700 text-base font-medium leading-normal">Enable MQTT</h3>
- <p class="text-xs text-slate-500 mt-1">
- Connect to an MQTT broker for Home Assistant integration and remote control.
- </p>
- </div>
- <label class="switch">
- <input type="checkbox" id="mqttEnableToggle">
- <span class="slider round"></span>
- </label>
- </div>
- <!-- Connection Status -->
- <div id="mqttStatusBanner" class="hidden">
- <div id="mqttConnectedBanner" class="bg-green-50 border border-green-200 rounded-lg p-3 hidden">
- <div class="flex items-center gap-2">
- <span class="material-icons text-green-600 text-base">check_circle</span>
- <span class="text-sm text-green-700 font-medium">Connected to MQTT broker</span>
- </div>
- </div>
- <div id="mqttDisconnectedBanner" class="bg-amber-50 border border-amber-200 rounded-lg p-3 hidden">
- <div class="flex items-center gap-2">
- <span class="material-icons text-amber-600 text-base">warning</span>
- <span class="text-sm text-amber-700 font-medium">MQTT is enabled but not connected. Check your settings or restart the application.</span>
- </div>
- </div>
- </div>
- <!-- MQTT Settings (shown when enabled) -->
- <div id="mqttSettings" class="space-y-4" style="display: none;">
- <!-- Broker Settings -->
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Broker Address <span class="text-red-500">*</span></span>
- <input
- id="mqttBrokerInput"
- type="text"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-normal leading-normal transition-colors"
- placeholder="e.g., 192.168.1.100 or mqtt.local"
- />
- <p class="text-xs text-slate-500">IP address or hostname of your MQTT broker</p>
- </label>
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Port</span>
- <input
- id="mqttPortInput"
- type="number"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-normal leading-normal transition-colors"
- placeholder="1883"
- value="1883"
- />
- <p class="text-xs text-slate-500">Default: 1883 (or 8883 for TLS)</p>
- </label>
- </div>
- <!-- Authentication -->
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Username</span>
- <input
- id="mqttUsernameInput"
- type="text"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-normal leading-normal transition-colors"
- placeholder="Optional"
- />
- <p class="text-xs text-slate-500">Leave empty if no authentication required</p>
- </label>
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Password</span>
- <div class="relative">
- <input
- id="mqttPasswordInput"
- type="password"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 pr-10 text-base font-normal leading-normal transition-colors"
- placeholder="Optional"
- />
- <button
- type="button"
- onclick="togglePasswordVisibility('mqttPasswordInput', this)"
- class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-700"
- aria-label="Toggle password visibility"
- >
- <span class="material-icons text-xl">visibility_off</span>
- </button>
- </div>
- <p class="text-xs text-slate-500">Leave empty if no authentication required</p>
- </label>
- </div>
- <!-- Home Assistant Discovery Settings -->
- <div class="border-t border-slate-200 pt-4">
- <h4 class="text-slate-700 text-sm font-medium mb-3">Home Assistant Discovery</h4>
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Device Name</span>
- <input
- id="mqttDeviceNameInput"
- type="text"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-normal leading-normal transition-colors"
- placeholder="Dune Weaver"
- value="Dune Weaver"
- />
- <p class="text-xs text-slate-500">Display name in Home Assistant</p>
- </label>
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Device ID</span>
- <input
- id="mqttDeviceIdInput"
- type="text"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-normal leading-normal transition-colors"
- placeholder="dune_weaver"
- value="dune_weaver"
- />
- <p class="text-xs text-slate-500">Must be unique per table (no spaces). Used for MQTT topics.</p>
- </label>
- </div>
- </div>
- <!-- Client ID and Discovery Prefix -->
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Client ID</span>
- <input
- id="mqttClientIdInput"
- type="text"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-normal leading-normal transition-colors"
- placeholder="dune_weaver"
- value="dune_weaver"
- />
- <p class="text-xs text-slate-500">Must be unique if running multiple tables on the same broker</p>
- </label>
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Discovery Prefix</span>
- <input
- id="mqttDiscoveryPrefixInput"
- type="text"
- class="form-input flex w-full min-w-0 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-normal leading-normal transition-colors"
- placeholder="homeassistant"
- value="homeassistant"
- />
- <p class="text-xs text-slate-500">Home Assistant discovery topic prefix</p>
- </label>
- </div>
- <!-- Test Connection Button -->
- <div class="flex flex-wrap gap-3 pt-2">
- <button
- id="testMqttConnection"
- class="flex items-center justify-center gap-2 cursor-pointer rounded-lg h-10 px-4 bg-slate-100 hover:bg-slate-200 text-slate-700 text-sm font-medium leading-normal tracking-[0.015em] transition-colors"
- >
- <span class="material-icons text-lg">wifi_tethering</span>
- <span class="truncate">Test Connection</span>
- </button>
- <span id="mqttTestResult" class="flex items-center text-sm"></span>
- </div>
- </div>
- <!-- Save Button -->
- <button
- id="saveMqttConfig"
- class="flex items-center justify-center gap-2 w-full sm:w-auto cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors"
- >
- <span class="material-icons text-lg">save</span>
- <span class="truncate">Save MQTT Configuration</span>
- </button>
- <!-- Restart Notice -->
- <div id="mqttRestartNotice" class="bg-blue-50 border border-blue-200 rounded-lg p-3 hidden">
- <div class="flex items-start gap-2">
- <span class="material-icons text-blue-600 text-base">info</span>
- <div class="text-xs text-blue-700">
- <p class="font-medium text-blue-800">Restart Required</p>
- <p class="mt-1">MQTT configuration changes require a restart to take effect. Use the restart button in the header to apply changes.</p>
- </div>
- </div>
- </div>
- </div>
- </section>
- <section class="bg-white rounded-xl shadow-sm overflow-hidden">
- <h2
- class="section-header collapsed text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
- onclick="toggleSection(this)"
- >
- <span>Auto-play on Boot</span>
- <span class="material-icons section-toggle-icon text-slate-400">expand_more</span>
- </h2>
- <div class="section-content collapsed px-6 py-5 space-y-6">
- <div class="flex items-center justify-between">
- <div class="flex-1">
- <h3 class="text-slate-700 text-base font-medium leading-normal">Enable Auto-play on Boot</h3>
- <p class="text-xs text-slate-500 mt-1">
- Automatically start playing a selected playlist when the system boots up.
- </p>
- </div>
- <label class="switch">
- <input type="checkbox" id="auto_playModeToggle">
- <span class="slider round"></span>
- </label>
- </div>
-
- <div id="auto_playSettings" class="space-y-4" style="display: none;">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Startup Playlist</span>
- <select
- id="auto_playPlaylistSelect"
- class="form-select flex-1 resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-medium leading-normal transition-colors"
- >
- <option value="">Select a playlist...</option>
- </select>
- <p class="text-xs text-slate-500 mt-1">
- Choose which playlist to automatically play when the system starts.
- </p>
- </label>
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Run Mode</span>
- <select
- id="auto_playRunModeSelect"
- class="form-select resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-medium leading-normal transition-colors"
- >
- <option value="single">Single (play once)</option>
- <option value="loop">Loop (repeat forever)</option>
- </select>
- <p class="text-xs text-slate-500 mt-1">
- How to run the playlist when it finishes.
- </p>
- </label>
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Pause Between Patterns (seconds)</span>
- <input
- id="auto_playPauseTimeInput"
- type="number"
- min="0"
- step="0.5"
- class="form-input resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-normal leading-normal transition-colors"
- placeholder="5.0"
- />
- <p class="text-xs text-slate-500 mt-1">
- Time to wait between each pattern (0 or more seconds).
- </p>
- </label>
- </div>
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
- <label class="flex flex-col gap-1.5">
- <span class="text-slate-700 text-sm font-medium leading-normal">Clear Pattern</span>
- <select
- id="auto_playClearPatternSelect"
- class="form-select resize-none overflow-hidden rounded-lg text-slate-900 focus:outline-0 focus:ring-2 focus:ring-sky-500 border border-slate-300 bg-white focus:border-sky-500 h-10 placeholder:text-slate-400 px-4 text-base font-medium leading-normal transition-colors"
- >
- <option value="none">None</option>
- <option value="adaptive">Adaptive</option>
- <option value="clear_from_in">Clear From Center</option>
- <option value="clear_from_out">Clear From Perimeter</option>
- <option value="clear_sideway">Clear Sideway</option>
- <option value="random">Random</option>
- </select>
- <p class="text-xs text-slate-500 mt-1">
- Pattern to run before each main pattern.
- </p>
- </label>
- <div class="flex items-center justify-between">
- <div class="flex-1">
- <h4 class="text-slate-700 text-sm font-medium leading-normal">Shuffle Playlist</h4>
- <p class="text-xs text-slate-500 mt-1">
- Randomize the order of patterns in the playlist.
- </p>
- </div>
- <label class="switch">
- <input type="checkbox" id="auto_playShuffleToggle">
- <span class="slider round"></span>
- </label>
- </div>
- </div>
- <div class="flex justify-end">
- <button
- id="saveAutoPlaySettings"
- class="flex items-center justify-center gap-2 min-w-[140px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors"
- >
- <span class="material-icons text-lg">save</span>
- <span class="truncate">Save Auto-play</span>
- </button>
- </div>
- </div>
- </div>
- </section>
- <section class="bg-white rounded-xl shadow-sm overflow-hidden">
- <h2
- class="section-header collapsed text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
- onclick="toggleSection(this)"
- >
- <span>Still Sands</span>
- <span class="material-icons section-toggle-icon text-slate-400">expand_more</span>
- </h2>
- <div class="section-content collapsed px-6 py-5 space-y-6">
- <div class="flex items-center justify-between">
- <div class="flex-1">
- <h3 class="text-slate-700 text-base font-medium leading-normal">Enable Still Sands</h3>
- <p class="text-xs text-slate-500 mt-1">
- Automatically bring the sands to rest during specified time periods.
- </p>
- </div>
- <label class="switch">
- <input type="checkbox" id="scheduledPauseToggle">
- <span class="slider round"></span>
- </label>
- </div>
- <div id="scheduledPauseSettings" class="space-y-4" style="display: none;">
- <!-- Finish Current Pattern Option -->
- <div class="bg-sky-50 rounded-lg p-4 border border-sky-200">
- <div class="flex items-center justify-between">
- <div class="flex-1">
- <h4 class="text-slate-800 text-sm font-medium flex items-center gap-2">
- <span class="material-icons text-slate-800 dark:text-slate-200 text-base">hourglass_bottom</span>
- Finish Current Pattern
- </h4>
- <p class="text-xs text-slate-600 mt-1">
- Let the current pattern complete before entering still mode
- </p>
- </div>
- <label class="switch">
- <input type="checkbox" id="stillSandsFinishPattern">
- <span class="slider round"></span>
- </label>
- </div>
- </div>
- <!-- WLED Control Option -->
- <div class="bg-sky-50 rounded-lg p-4 border border-sky-200">
- <div class="flex items-center justify-between">
- <div class="flex-1">
- <h4 class="text-slate-800 text-sm font-medium flex items-center gap-2">
- <span class="material-icons text-slate-800 dark:text-slate-200 text-base">lightbulb</span>
- Control WLED Lights
- </h4>
- <p class="text-xs text-slate-600 mt-1">
- Turn off WLED lights during still periods for complete rest
- </p>
- </div>
- <label class="switch">
- <input type="checkbox" id="stillSandsWledControl">
- <span class="slider round"></span>
- </label>
- </div>
- </div>
- <!-- Timezone Selection -->
- <div class="bg-sky-50 rounded-lg p-4 border border-sky-200">
- <div class="flex items-center justify-between">
- <div class="flex-1">
- <h4 class="text-slate-800 text-sm font-medium flex items-center gap-2">
- <span class="material-icons text-slate-800 dark:text-slate-200 text-base">schedule</span>
- Timezone
- </h4>
- <p class="text-xs text-slate-600 mt-1">
- Select a timezone for still periods (defaults to system timezone)
- </p>
- </div>
- <select id="stillSandsTimezone" class="h-10 px-3 rounded-lg border border-slate-300 bg-white text-slate-800 text-sm min-w-[200px]">
- <option value="">System Default</option>
- <optgroup label="Americas">
- <option value="America/New_York">Eastern Time (New York)</option>
- <option value="America/Chicago">Central Time (Chicago)</option>
- <option value="America/Denver">Mountain Time (Denver)</option>
- <option value="America/Los_Angeles">Pacific Time (Los Angeles)</option>
- <option value="America/Anchorage">Alaska (Anchorage)</option>
- <option value="Pacific/Honolulu">Hawaii (Honolulu)</option>
- <option value="America/Toronto">Toronto</option>
- <option value="America/Vancouver">Vancouver</option>
- <option value="America/Mexico_City">Mexico City</option>
- <option value="America/Sao_Paulo">São Paulo</option>
- <option value="America/Buenos_Aires">Buenos Aires</option>
- </optgroup>
- <optgroup label="Europe">
- <option value="Europe/London">London</option>
- <option value="Europe/Paris">Paris</option>
- <option value="Europe/Berlin">Berlin</option>
- <option value="Europe/Amsterdam">Amsterdam</option>
- <option value="Europe/Rome">Rome</option>
- <option value="Europe/Madrid">Madrid</option>
- <option value="Europe/Zurich">Zurich</option>
- <option value="Europe/Stockholm">Stockholm</option>
- <option value="Europe/Moscow">Moscow</option>
- </optgroup>
- <optgroup label="Asia & Pacific">
- <option value="Asia/Tokyo">Tokyo</option>
- <option value="Asia/Shanghai">Shanghai</option>
- <option value="Asia/Hong_Kong">Hong Kong</option>
- <option value="Asia/Singapore">Singapore</option>
- <option value="Asia/Seoul">Seoul</option>
- <option value="Asia/Dubai">Dubai</option>
- <option value="Asia/Kolkata">India (Kolkata)</option>
- <option value="Asia/Bangkok">Bangkok</option>
- <option value="Australia/Sydney">Sydney</option>
- <option value="Australia/Melbourne">Melbourne</option>
- <option value="Australia/Perth">Perth</option>
- <option value="Pacific/Auckland">Auckland</option>
- </optgroup>
- <optgroup label="Africa">
- <option value="Africa/Cairo">Cairo</option>
- <option value="Africa/Johannesburg">Johannesburg</option>
- <option value="Africa/Lagos">Lagos</option>
- </optgroup>
- <optgroup label="GMT Offsets">
- <option value="Etc/GMT+12">GMT-12</option>
- <option value="Etc/GMT+11">GMT-11</option>
- <option value="Etc/GMT+10">GMT-10</option>
- <option value="Etc/GMT+9">GMT-9</option>
- <option value="Etc/GMT+8">GMT-8</option>
- <option value="Etc/GMT+7">GMT-7</option>
- <option value="Etc/GMT+6">GMT-6</option>
- <option value="Etc/GMT+5">GMT-5</option>
- <option value="Etc/GMT+4">GMT-4</option>
- <option value="Etc/GMT+3">GMT-3</option>
- <option value="Etc/GMT+2">GMT-2</option>
- <option value="Etc/GMT+1">GMT-1</option>
- <option value="Etc/GMT">GMT / UTC</option>
- <option value="Etc/GMT-1">GMT+1</option>
- <option value="Etc/GMT-2">GMT+2</option>
- <option value="Etc/GMT-3">GMT+3</option>
- <option value="Etc/GMT-4">GMT+4</option>
- <option value="Etc/GMT-5">GMT+5</option>
- <option value="Etc/GMT-6">GMT+6</option>
- <option value="Etc/GMT-7">GMT+7</option>
- <option value="Etc/GMT-8">GMT+8</option>
- <option value="Etc/GMT-9">GMT+9</option>
- <option value="Etc/GMT-10">GMT+10</option>
- <option value="Etc/GMT-11">GMT+11</option>
- <option value="Etc/GMT-12">GMT+12</option>
- <option value="Etc/GMT-13">GMT+13</option>
- <option value="Etc/GMT-14">GMT+14</option>
- </optgroup>
- </select>
- </div>
- </div>
- <div class="bg-slate-50 rounded-lg p-4 space-y-4">
- <div class="flex items-center justify-between">
- <h4 class="text-slate-800 text-base font-semibold">Still Periods</h4>
- <button
- id="addTimeSlotButton"
- class="flex items-center justify-center gap-2 cursor-pointer rounded-lg h-9 px-3 bg-sky-600 hover:bg-sky-700 text-white text-xs font-medium leading-normal tracking-[0.015em] transition-colors"
- >
- <span class="material-icons text-base">add</span>
- <span>Add Still Period</span>
- </button>
- </div>
- <p class="text-sm text-slate-600">
- Define time periods when the sands should rest in stillness. Patterns will resume automatically when still periods end.
- </p>
- <div id="timeSlotsContainer" class="space-y-3">
- <!-- Time slots will be dynamically added here -->
- </div>
- <div class="text-xs text-slate-500 bg-blue-50 border border-blue-200 rounded-lg p-3">
- <div class="flex items-start gap-2">
- <span class="material-icons text-blue-600 text-base">info</span>
- <div>
- <p class="font-medium text-blue-800">Important Notes:</p>
- <ul class="mt-1 space-y-1 text-blue-700">
- <li>• Times are based on the selected timezone (or system default if not set)</li>
- <li>• By default, patterns pause immediately when entering a still period</li>
- <li>• Enable "Finish Current Pattern" to let patterns complete first</li>
- <li>• Patterns will resume automatically when exiting a still period</li>
- <li>• Still periods that span midnight (e.g., 22:00 to 06:00) are supported</li>
- </ul>
- </div>
- </div>
- </div>
- </div>
- <div class="flex justify-end">
- <button
- id="savePauseSettings"
- class="flex items-center justify-center gap-2 min-w-[140px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors"
- >
- <span class="material-icons text-lg">save</span>
- <span class="truncate">Save Still Sands</span>
- </button>
- </div>
- </div>
- </div>
- </section>
- <section id="software-version-section" class="bg-white rounded-xl shadow-sm overflow-hidden">
- <h2
- class="section-header collapsed text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
- onclick="toggleSection(this)"
- >
- <span>Software Version</span>
- <span class="material-icons section-toggle-icon text-slate-400">expand_more</span>
- </h2>
- <div class="section-content collapsed">
- <div class="flex items-center gap-4 px-6 py-5">
- <div
- class="text-slate-600 flex items-center justify-center rounded-lg bg-slate-100 shrink-0 size-12"
- >
- <span class="material-icons text-3xl">terminal</span>
- </div>
- <div class="flex-1">
- <p class="text-slate-800 text-base font-medium leading-normal">
- Current Version
- </p>
- <p id="currentVersionText" class="text-slate-500 text-sm font-normal leading-normal">Loading...</p>
- </div>
- </div>
- <div class="flex items-center gap-4 px-6 py-5">
- <div
- class="text-slate-600 flex items-center justify-center rounded-lg bg-slate-100 shrink-0 size-12"
- >
- <span class="material-icons text-3xl">system_update</span>
- </div>
- <div class="flex-1">
- <p class="text-slate-800 text-base font-medium leading-normal">
- Latest Version
- </p>
- <p id="latestVersionText" class="text-slate-500 text-sm font-normal leading-normal">Checking...</p>
- </div>
- <button
- id="updateSoftware"
- class="flex items-center justify-center gap-1.5 min-w-[84px] cursor-pointer rounded-lg h-9 px-3 bg-gray-400 text-white text-xs font-medium leading-normal tracking-[0.015em] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- disabled
- >
- <span id="updateIcon" class="material-icons text-base">download</span>
- <span id="updateText" class="truncate">Update</span>
- </button>
- </div>
- <div class="flex items-center gap-4 px-6 py-5 border-t border-slate-200">
- <div
- class="text-slate-600 flex items-center justify-center rounded-lg bg-slate-100 shrink-0 size-12"
- >
- <span class="material-icons text-3xl">article</span>
- </div>
- <div class="flex-1">
- <p class="text-slate-800 text-base font-medium leading-normal">
- Application Logs
- </p>
- <p class="text-slate-500 text-sm font-normal leading-normal">View real-time application logs</p>
- </div>
- <button
- id="openLogsBtn"
- onclick="openLogsModal()"
- class="flex items-center justify-center gap-1.5 min-w-[84px] cursor-pointer rounded-lg h-9 px-3 bg-blue-600 hover:bg-blue-700 text-white text-xs font-medium leading-normal tracking-[0.015em] transition-colors"
- >
- <span class="material-icons text-base">terminal</span>
- <span class="truncate">View Logs</span>
- </button>
- </div>
- </div>
- </section>
- </div>
- {% endblock %} {% block scripts %}
- <script src="/static/js/settings.js"></script>
- {% endblock %}
|