###2026.10.27.1922 - New colours, fonts, tables, buttons, dialogs and notification panel for the Black and White themes. - Optional sidebar navigation layout. - Page enhancements: brand and server card in the sidebar, slim top bar, page headings, status pills, Docker summary and filters. - Own Stats page with a built-in collector, replacing Dynamix System Statistics. - Docker application groups, compact container list, restyled switches and menus. - Updates from the GitHub releases: "Check for updates" and "Update now" on the settings page, and automatic updates (on by default, weekly). - Redesigned Main, Users, VMs and login page; live port check in the Docker editor; dark / light switch. - Redesigned top bar, Apps tab, tool pages, help boxes and notices; the processor tile shows its cores as a compact grid. - Phone layout fixes across the webGUI: Dashboard tiles, wide tables, VM editor, Apps, header and pop-ups. - Plugins page: a plugin whose update check failed for a moment ("not available") is checked again; Aurora's own check needs a single request. - Choose when automatic updates run (hourly, daily or weekly at a set time); redesigned GPU Statistics tile. - GPU tile: an idle GPU's clock reads "idle"; NVIDIA and AMD rows tidied. - Docker page loads much faster with many containers, with a loading status; array usage widget; custom banner and header colours shown; Display Settings fixes. - FolderView support with an Aurora / FolderViewPlugin switch; groups on the Dashboard with app icons and CPU / RAM sums; VM usage cards, accent colour, phone fixes. - Containers keep Unraid's order and start in the order they are listed: drag them within and between groups, and move whole groups while the list is unlocked. VM groups on the VMs tab and the Dashboard, a buttons column on the VMs tab, group icons that open the app's menu and show stopped apps too. The sidebar can be hidden. FolderView3: the view switch on the VMs tab as well, drag to the top of the list, its autostart sequence respected, a tidier settings page. Fixes for long names on phones, the updates count and the server card. - Top layout: with the array usage indicator on, Stats and Tools are back in the menu; the indicator sits in the top bar on wide screens. - New "Fonts" setting: Aurora's Inter and Space Grotesk, or Unraid's own Clear Sans. - Dark / light switch: only the theme setting in dynamix.cfg is changed, and settings lost while the Unraid API rewrites the file are put back (two quick switches could empty it). - With "Favorites enabled" set to No in Display Settings, Unraid's Favorites tab is hidden too. - Docker start delay ("wait") field wide enough for its number in FolderView3's view. - An orange dot on a container's icon when an update is ready, running or not: on the Dashboard, in group and folder icons and in the Docker tab's compact list. A stopped container with an update stays on the Dashboard when stopped containers are hidden. - FolderView Plus support: with it installed, its folders arrange the Docker and VMs tabs and the Dashboard, and Aurora keeps to colours and layout (no view switch). - After an update, the next page shows "Aurora Theme updated" with the new version and its release notes; automatic updates also show an Unraid notification. Automatic updates are checked weekly by default. - Docker and VMs tabs with FolderView3 or FolderView Plus: a "beta" note in the top bar; their support still has compatibility issues and is being worked on. With both installed, a warning that this is not supported (also on the Dashboard and the Plugins page). - Phones: pages fit the screen width exactly; the "back to top" / "to the end" buttons no longer let the page be pushed sideways. - Faster on busy pages: style rules that the browser checked again on every change of any page (Community Applications cards, GPU tile) are limited to their page or replaced. - FolderView Plus: long container, VM and folder names end in "..." inside their chips and name boxes. Dashboard Array and pool tiles keep their columns in one row; on phones the status column has room for "standby". - Update channel: "Dev builds (testing)" also installs the dev builds published between releases; an "Aurora Dev" badge in the top bar shows when one is installed, and "Install stable" goes back to the stable release. - "Aurora Theme updated" dialog: after skipping versions, it lists the notes of every release since the previous version, newest first, each with its date. - Docker tab and Dashboard: a container updated in the background (CA Auto Update, "Update All" in another tab, the scheduled check) no longer shows "update ready" until the page is reloaded; the list is loaded again within 20 seconds. - Docker tab (Aurora view): images no container uses ("orphan image") are collected in an "Orphans" section at the end of the list, also in the basic view, with a "Remove all" button. - Docker tab on phones: each container's card also shows its addresses and its mounts (#27). - Phones: dialogs fit the part of the screen the browser leaves visible, so the Done button of a container update and the buttons of long dialogs no longer end up behind the browser's bars; long texts scroll between the title and the buttons. - Docker tab and Dashboard: "Check for Updates" and Unraid's scheduled check are no longer interrupted by the list's automatic refresh, which showed half the results, ended the "checking" state early and could put the old results back; the list now waits until the check is done. - File and folder pickers (Plugins > Install Plugin, the path fields of the Docker and VM editors, ...) in the theme's style, with folder and file icons; command output on a disk's page (btrfs status, ...) is monospaced again, so its columns line up. /dev/null | head -n 1) if [[ "$old" =~ ^[0-9]{4}\.[0-9]{2}\.[0-9]{2}[A-Za-z0-9._-]*$ && "$old" != "2026.10.27.1922" ]]; then from=$(sed -n 's/.*"from":"\([^"]*\)".*/\1/p' /boot/config/plugins/Aurora-Theme/updated.json 2>/dev/null) [[ "$from" =~ ^[0-9]{4}\.[0-9]{2}\.[0-9]{2}[A-Za-z0-9._-]*$ ]] || from=$old printf '{"from":"%s","to":"%s","time":%s}\n' "$from" "2026.10.27.1922" "$(date +%s)" > /boot/config/plugins/Aurora-Theme/updated.json fi cat > /usr/local/emhttp/plugins/Aurora-Theme/AuroraTheme.page <<'AURORA_THEME_EOF' Menu="Buttons" Link="nav-user" Title="Aurora Theme" --- on each page, after its own stylesheets, // so this is where the theme's files are attached. Link="nav-user" keeps it from adding a button. $aurora = parse_plugin_cfg('Aurora-Theme'); $auroraCustomDir = '/boot/config/plugins/Aurora-Theme'; $auroraClasses = []; if (($aurora['layout'] ?? 'top') == 'sidebar') $auroraClasses[] = 'tp-sidebar'; if (($aurora['enhance'] ?? 'yes') == 'yes') $auroraClasses[] = 'tp-enhance'; // Display Settings, "Favorites enabled" set to No also turns off the theme's starred cards. if (($display['favorites'] ?? 'yes') == 'no') $auroraClasses[] = 'tp-no-favourites'; // The page, from the first part of the address ("/Apps/..." gives tp-on-apps), so rules that // only matter on one page can be limited to it; the browser then skips them everywhere else. $auroraPage = strtolower(preg_replace('/[^A-Za-z0-9-]/', '', explode('/', trim((string) parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH), '/'))[0])); if ($auroraPage !== '') $auroraClasses[] = 'tp-on-' . $auroraPage; // Emergency switch: opening any page with ?aurora=off stops the plugin from loading anything // in this browser (remembered in a cookie) until a page is opened with ?aurora=on. $auroraSwitch = $_GET['aurora'] ?? ''; $auroraOff = $auroraSwitch == 'off' || ($auroraSwitch != 'on' && !empty($_COOKIE['aurora_off'])); // The theme is made for Unraid's top-navigation themes (Black, White). Azure and Gray have // their own sidebar layout, so there the plugin loads nothing and leaves them as they are. if (isset($themeHelper) && method_exists($themeHelper, 'isTopNavTheme') && !$themeHelper->isTopNavTheme()) $auroraOff = true; ?> '--tp-custom-header-text', 'headermetacolor' => '--tp-custom-header-muted', 'background' => '--tp-custom-header-bg'] as $auroraKey => $auroraVar) { $auroraHex = (string) ($display[$auroraKey] ?? ''); if (preg_match('/^([0-9a-fA-F]{3}){1,2}$/', $auroraHex)) $auroraHeaderVars[] = "$auroraVar: #$auroraHex;"; } ?> = (($auroraL + 0.05) / ($auroraLum('06101f') + 0.05)) ? '#ffffff' : '#06101f'; $auroraHex = "#$auroraAccent"; $auroraScale = []; foreach ([50 => 'white 92%', 100 => 'white 84%', 200 => 'white 68%', 300 => 'white 48%', 400 => 'white 24%', 500 => 'white 6%', 600 => 'black 8%', 700 => 'black 24%', 800 => 'black 40%', 900 => 'black 54%', 950 => 'black 70%'] as $auroraStep => $auroraMix) { $auroraScale[] = "--color-primary-$auroraStep: color-mix(in oklab, $auroraHex, $auroraMix); --ui-color-primary-$auroraStep: color-mix(in oklab, $auroraHex, $auroraMix);"; } ?> '', 'bold' => '-bold'] as $auroraWeight => $auroraWeightFile) { foreach (['normal' => '', 'italic' => '-italic'] as $auroraStyle => $auroraStyleFile) { $auroraFonts[] = "@font-face { font-family: $auroraFamily; font-weight: $auroraWeight; font-style: $auroraStyle; src: url('/webGui/styles/$auroraFamily$auroraWeightFile$auroraStyleFile.woff') format('woff'); }"; } } } ?> 60) $auroraTitle = ''; } catch (Throwable $auroraError) { $auroraTitle = ''; } ?> AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/AuroraThemeSettings.page <<'AURORA_THEME_EOF' Menu="Utilities" Title="Aurora Theme" Icon="paint-brush" Tag="paint-brush" ---
_(Colours and fonts)_: : > Applies the theme palette, fonts, buttons, tables and dialogs. It follows the Dynamix colour theme > chosen in Display Settings: **Black** gives the dark palette, **White** the light one. Other Dynamix > themes are left as they are. _(Accent colour)_: : > The colour of highlights: the active menu entry, buttons, links, switches and bars. Pick one or > type its code (such as `#e11d48`); **Default** goes back to the theme's blue. Lighter shades and > the text on buttons are worked out from it. _(Fonts)_: : > **Aurora** uses Inter for text and Space Grotesk for headings, bundled with the plugin. **Unraid > default** keeps Unraid's own fonts, Clear Sans for text and headings and Bitstream for code, with > everything else of the theme. The login page always uses the Aurora fonts. _(Navigation layout)_: : > Moves the main menu into a sidebar on screens at least 1024 pixels wide. Needs a top-navigation > Dynamix theme (Black or White). _(Page enhancements)_: : > Adds elements Unraid does not have, such as the summary strip above the Docker container list. _(Stats page)_: : > Adds a Stats tab with processor, memory, network and disk charts, and records one measurement > per minute for it. It replaces the Dynamix System Statistics plugin; remove that plugin first, > because both use the name "Stats" for their tab. _(Update channel)_: : > "Dev builds" also installs the test versions published between releases, so you get fixes and > new features earlier, together with their rough edges. While a dev build is installed, a "Dev" > badge in the top bar says so. To go back, choose "Stable releases", apply, and use "Install > stable" below (or wait for the next stable release, which replaces the dev build). _(Automatic updates)_: : _(Check and install)_: : > When the plugin looks for a newer release on GitHub and installs it: once a week (the default) > or a day at the chosen time (the server's time), or every hour. Each update shows an Unraid > notification, and the next page you open shows what changed; every check is noted in the > system log.   :
0; $auroraRepo = 'Tunefish92/Aurora-Theme'; ?>
_(Update)_
Version Dev build' : '' ?> Updates come from the plugin's releases on GitHub. Use "Check for updates" to look for a newer one now. Release notes on GitHub

_(Feedback and issues)_

Report a bug, something that is not implemented yet, or an idea. This opens a new issue on GitHub with your text filled in; you review and submit it there. Nothing is sent from this server.

See existing issues
_(Your own changes)_
Put your own rules in these files on the flash drive. They are loaded on every page, after the theme, and survive plugin updates: - `/boot/config/plugins/Aurora-Theme/custom.css` - `/boot/config/plugins/Aurora-Theme/custom.js`
_(Emergency switch)_
If the theme ever makes a page unusable, open any page with `?aurora=off` added to the address, for example `/Dashboard?aurora=off`. The plugin then loads nothing in that browser until you open a page with `?aurora=on`. AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/README.md <<'AURORA_THEME_EOF' #### Aurora Theme A modern look for the Unraid webGUI in a dark and a light palette: new fonts, cards and buttons, an optional sidebar layout, and redesigned Dashboard, Main, Docker, VMs, Users, Settings, Tools and login pages. Updates itself from GitHub. Settings: Settings > User Utilities > Aurora Theme. AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/Stats.page <<'AURORA_THEME_EOF' Menu="Tasks:90" Type="xmenu" Title="Stats" Tag="bar-chart" Markdown="false" Cond="(parse_plugin_cfg('Aurora-Theme')['stats'] ?? 'yes') == 'yes'" --- (string) ($auroraDisk['name'] ?? ''), 'size' => $auroraDiskSize, 'used' => (float) ($auroraDisk['fsUsed'] ?? 0), ]; } ?>
AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/default.cfg <<'AURORA_THEME_EOF' theme="yes" layout="sidebar" enhance="yes" stats="yes" channel="stable" autoupdate="yes" autoupdate_when="weekly" autoupdate_day="0" autoupdate_time="03:00" accent="" font="aurora" AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/fonts/inter-LICENSE.txt <<'AURORA_THEME_EOF' Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. AURORA_THEME_EOF base64 -d > /usr/local/emhttp/plugins/Aurora-Theme/fonts/inter-latin-ext-wght-normal.woff2 <<'AURORA_THEME_EOF' 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 FEMxUtm2W7Y38jMzFKvI2lDMVs/8625gry4w6f9Mk2+29Squ/5VuNun7nK641tOh2PyLHu8z6T3b olCclfeadJ+Xt8lXmlqUhC5HUl4mk1xEtOrN11qg5S5WdnYufEpQ2iv+TNAsdFblLi5S87R6U60o RcwMYl6XEwHkFfoYjjO+5OX1lPbo3IaIq3sqQ/fNZP+WrKyuu9CY82UwZwjO9l0ozs0or58yEj05 Wlo9VZXOy/aev/nsy22A3c69MjeczfnSKq6rXqzM/q10JnR3ZfhVRkN7NLVuU577ZWedIdtnnped UVk9daT0iUFp/XR5RnGu7wLw5gwDHdf9t2cPfgWwbkEG62wuj1SQP4LXOmg4L2OurWWUrTx+eKsA eWI+vS2g0q2IVFdQmNbFzs5vFnI/72q7DaT7lUoTpceYs1vbHXaH28ZVJkH5SZlQ9KYb+xTvn+s3 yLvqGteTgmPIG7o1xOkY7AZpY3BhkygxzRvgw+S6izuke2BO9Wt1TknjRQFBXtOtFRDYNdIaQhyL aF4DtS8RCRi6o5q/uDJrHrXjbdGEtfVhHDmd8iK4l9ALIG+9Rh+MioaeEfJtUUhuH8RVR53cSduX swOH4KaKX192dMchunJ9JzuLL4V/vhBJ3HHglENix3QJSMBsnPzmWWqS2sI4Y7+eDj48BYVEwx8e USpI/CEwdJkyQErrp1DIfQNpFESZL5nsK0JU+u8eNah+/u0R3/2m8QRuVTjYosgSBUJSjelOMDuu J9MuJ+DIAVQEst63ZML43793+NmleWfYVaTGurVyBTeiBwduRHNL3FtjUu0rvDPs0/zc/9mkT5TU +0YkHkg8EmCX482048KcjNMjCWWBgOyaLV1JY14bn7g1URRQRnUPmZkNp7r7lRWVQs+6HCX4mwx+ KVkdTMcTTZriy8aZ11bTgEl1M9Bxuw2wX15YnKYl7BFBNvLq+xDZsvMy6mePRz/r5Y1ha95pL40J lZ2upOvesEd20HFhDh1XlBBVDgIuKpSZSqcb5EVyCZ0AHLK9DbC3nVMGJyua5I0umdbNzWDnxMLj nVuxwp2nFu5FPXDCVmEAzNeXMe7vvB8DlvtgnJDIrPAw53ImGKwgJCorLBwukFCOFxTi7gafF97h 3NxhEMC4vbKw8fdq+7kucmlsaGfhKG/33eYjOcremqfIoTbcvRqyMyIwygdf9DdKIFak9oIUepto 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 AURORA_THEME_EOF base64 -d > /usr/local/emhttp/plugins/Aurora-Theme/fonts/inter-latin-wght-italic.woff2 <<'AURORA_THEME_EOF' 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 AURORA_THEME_EOF base64 -d > /usr/local/emhttp/plugins/Aurora-Theme/fonts/inter-latin-wght-normal.woff2 <<'AURORA_THEME_EOF' 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 AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/fonts/space-grotesk-LICENSE.txt <<'AURORA_THEME_EOF' Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk) This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. AURORA_THEME_EOF base64 -d > /usr/local/emhttp/plugins/Aurora-Theme/fonts/space-grotesk-latin-ext-wght-normal.woff2 <<'AURORA_THEME_EOF' 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 fMH4P+H/Hv8u9wAA AURORA_THEME_EOF base64 -d > /usr/local/emhttp/plugins/Aurora-Theme/fonts/space-grotesk-latin-wght-normal.woff2 <<'AURORA_THEME_EOF' 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 AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/StatsLib.php <<'AURORA_THEME_EOF' 0.0, 'MemAvailable' => 0.0]; foreach (@file('/proc/meminfo') ?: [] as $line) { if (preg_match('/^(MemTotal|MemAvailable):\s+(\d+)/', $line, $match)) { $mem[$match[1]] = (float) $match[2]; } } // Physical network cards only: bonds, bridges and container interfaces carry the same // traffic again and would double it. $rx = $tx = 0.0; foreach (@file('/proc/net/dev') ?: [] as $line) { if (!preg_match('/^\s*([^:\s]+):\s*(.+)$/', $line, $match) || !file_exists("/sys/class/net/{$match[1]}/device")) { continue; } $fields = preg_split('/\s+/', trim($match[2])); $rx += (float) ($fields[0] ?? 0); $tx += (float) ($fields[8] ?? 0); } // Whole disks only, not their partitions. Sector counts are always in 512-byte units. $read = $written = 0.0; foreach (@file('/proc/diskstats') ?: [] as $line) { $fields = preg_split('/\s+/', trim($line)); if (!preg_match('/^(sd[a-z]+|nvme\d+n\d+|vd[a-z]+|xvd[a-z]+|mmcblk\d+)$/', $fields[2] ?? '')) { continue; } $read += (float) ($fields[5] ?? 0) * 512; $written += (float) ($fields[9] ?? 0) * 512; } return [ 't' => microtime(true), 'busy' => $total - $idle, 'total' => $total, 'mem' => $mem['MemTotal'] > 0 ? (1 - $mem['MemAvailable'] / $mem['MemTotal']) * 100 : 0.0, 'rx' => $rx, 'tx' => $tx, 'rd' => $read, 'wr' => $written, ]; } /** One history point: [unix time, cpu %, memory %, rx B/s, tx B/s, read B/s, write B/s]. */ function tft_stats_rates(array $from, array $to): ?array { $seconds = $to['t'] - $from['t']; if ($seconds <= 0) { return null; } $jiffies = $to['total'] - $from['total']; $cpu = $jiffies > 0 ? max(0, min(100, ($to['busy'] - $from['busy']) / $jiffies * 100)) : 0; // Counters restart from zero after a reboot; a negative difference means "no data", not traffic. $rate = fn(string $key) => (int) round(max(0, ($to[$key] - $from[$key]) / $seconds)); return [(int) round($to['t']), round($cpu, 1), round($to['mem'], 1), $rate('rx'), $rate('tx'), $rate('rd'), $rate('wr')]; } /** Stored points between two times, averaged into at most $maxPoints buckets. */ function tft_stats_history(int $start, int $end, int $maxPoints = 240): array { $bucketSeconds = max(60, (int) ceil(($end - $start) / $maxPoints)); $buckets = []; for ($day = strtotime(gmdate('Y-m-d', $start) . ' UTC'); $day <= $end; $day += 86400) { $file = TFT_STATS_DIR . '/' . gmdate('Ymd', $day) . '.csv'; $handle = is_file($file) ? @fopen($file, 'r') : false; if (!$handle) { continue; } while (($row = fgetcsv($handle, 200, ',', '"', '')) !== false) { if (count($row) < 7 || $row[0] < $start || $row[0] > $end) { continue; } $index = intdiv((int) $row[0] - $start, $bucketSeconds); $buckets[$index] ??= ['n' => 0, 'time' => 0.0, 'sum' => array_fill(0, 6, 0.0)]; $buckets[$index]['n']++; $buckets[$index]['time'] += (float) $row[0]; for ($column = 0; $column < 6; $column++) { $buckets[$index]['sum'][$column] += (float) $row[$column + 1]; } } fclose($handle); } ksort($buckets); $points = []; foreach ($buckets as $bucket) { // The mean sample time, so a half-filled latest bucket is not drawn in the future. $point = [(int) round($bucket['time'] / $bucket['n'])]; foreach ($bucket['sum'] as $sum) { $point[] = round($sum / $bucket['n'], 1); } $points[] = $point; } return $points; } AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/UpdateLib.php <<'AURORA_THEME_EOF' cron time fields, 'text' => "every day at 03:00"]. * Settings: autoupdate_when (hourly, daily, weekly), autoupdate_day (0 Sunday ... 6 Saturday), * autoupdate_time (HH:MM, for daily and weekly). */ function aurora_update_schedule(): array { $when = aurora_setting('autoupdate_when'); if (!preg_match('/^([01]\d|2[0-3]):([0-5]\d)$/', aurora_setting('autoupdate_time'), $time)) $time = ['', '03', '00']; $day = preg_match('/^[0-6]$/', aurora_setting('autoupdate_day')) ? (int) aurora_setting('autoupdate_day') : 0; $days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; $minute = (int) $time[2]; $hour = (int) $time[1]; if ($when === 'daily') return ['cron' => "$minute $hour * * *", 'text' => "every day at {$time[1]}:{$time[2]}"]; if ($when === 'weekly') return ['cron' => "$minute $hour * * $day", 'text' => "every {$days[$day]} at {$time[1]}:{$time[2]}"]; return ['cron' => '23 * * * *', 'text' => 'every hour']; } /** Version in a .plg file, or '' when the file is not this plugin. */ function aurora_plg_version(string $file): string { $xml = is_file($file) ? @simplexml_load_file($file) : false; return $xml && (string) $xml['name'] === AURORA_PLUGIN ? (string) $xml['version'] : ''; } function aurora_installed_version(): string { return aurora_plg_version('/boot/config/plugins/' . AURORA_PLUGIN . '.plg'); } /** A download address of this plugin's .plg in one of its GitHub releases, or ''. */ function aurora_release_asset(string $url): string { $prefix = 'https://github.com/' . AURORA_REPO . '/releases/download/'; return strpos($url, $prefix) === 0 && preg_match('#^[A-Za-z0-9._-]+/' . AURORA_PLUGIN . '\.plg$#', substr($url, strlen($prefix))) ? $url : ''; } /** The GitHub API's answer for $url, decoded, or null. */ function aurora_github_api(string $url) { exec('/usr/bin/curl -fsSL --max-time 20 -H ' . escapeshellarg('Accept: application/vnd.github+json') . ' -A ' . escapeshellarg(AURORA_PLUGIN . '-update-check') . ' ' . escapeshellarg($url) . ' 2>/dev/null', $output, $status); return $status === 0 ? json_decode(implode("\n", $output), true) : null; } /** ['version', 'page', 'plg'] of one release from the GitHub API, or null when it has no valid .plg. */ function aurora_release_info($data): ?array { $tag = is_array($data) && empty($data['draft']) ? ltrim((string) ($data['tag_name'] ?? ''), 'vV') : ''; if (!preg_match('/^\d{4}\.\d{2}\.\d{2}[\w.-]*$/', $tag)) return null; $plg = ''; foreach ((array) ($data['assets'] ?? []) as $asset) { if (($asset['name'] ?? '') === AURORA_PLUGIN . '.plg') $plg = aurora_release_asset((string) ($asset['browser_download_url'] ?? '')); } return ['version' => $tag, 'page' => (string) ($data['html_url'] ?? AURORA_RELEASES_PAGE), 'plg' => $plg]; } /** * The newest release: ['version' => '2026.10.14.2219', 'page' => release URL, 'plg' => URL of * its .plg] or ['error' => text]. * Asks the GitHub API first (a few bytes); if that fails, for example because of its hourly * limit, the release's .plg itself is downloaded and read. * * 'plg' is the address of that exact release's file. The "latest" address keeps pointing to the * previous release for a minute or two after a new one is published, so installing from it * right after a check could bring an older version than the check reported, and the next check * would offer an update again. */ function aurora_latest_release(string $channel = ''): array { // Dev channel: the newest of the recent releases, dev builds (pre-releases) included. If the // list cannot be read, the newest stable release is the answer. if (($channel ?: aurora_channel()) === 'dev') { $newest = null; foreach ((array) aurora_github_api(AURORA_RELEASES_API) as $data) { $release = aurora_release_info($data); if ($release && $release['plg'] && (!$newest || strcmp($release['version'], $newest['version']) > 0)) $newest = $release; } if ($newest) return $newest; } $release = aurora_release_info(aurora_github_api(AURORA_RELEASE_API)); if ($release) { return ['plg' => $release['plg'] ?: AURORA_RELEASE_PLG] + $release; } $file = tempnam('/tmp', 'aurora-release-'); exec('/usr/bin/curl -fsSL --max-time 90 -o ' . escapeshellarg($file) . ' ' . escapeshellarg(AURORA_RELEASE_PLG) . ' 2>&1', $download, $status); $version = $status === 0 ? aurora_plg_version($file) : ''; @unlink($file); if ($version !== '') { return ['version' => $version, 'page' => AURORA_RELEASES_PAGE, 'plg' => AURORA_RELEASE_PLG]; } return ['error' => $status === 0 ? 'The newest release on GitHub has no valid ' . AURORA_PLUGIN . '.plg.' : 'GitHub could not be reached: ' . trim(implode(' ', $download))]; } AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/check-update.php <<'AURORA_THEME_EOF' 'POST only']); exit; } $installed = aurora_installed_version(); $latest = aurora_latest_release(); if (isset($latest['error'])) { http_response_code(502); echo json_encode(['installed' => $installed, 'error' => $latest['error']]); exit; } $newer = strcmp($latest['version'], $installed) > 0; // A dev build is installed but the channel is stable again: the newest stable release is older // than the dev build, so it is offered as a switch back (installed with "forced"). $switch = !$newer && aurora_is_dev($installed) && aurora_channel() === 'stable' && !aurora_is_dev($latest['version']); echo json_encode([ 'installed' => $installed, 'available' => $latest['version'], 'channel' => aurora_channel(), 'newer' => $newer, 'switch' => $switch, 'page' => $latest['page'], 'plg' => $latest['plg'], 'message' => $switch ? "You are on the dev build $installed. The stable channel's newest release is {$latest['version']}." : ($newer ? (aurora_is_dev($latest['version']) ? "Dev build {$latest['version']}" : "Version {$latest['version']}") . " is available on GitHub (installed: $installed)." : (strcmp($installed, $latest['version']) > 0 ? "Installed version $installed is newer than the newest release ({$latest['version']})." : "Installed version $installed is the newest " . (aurora_channel() === 'dev' ? 'build' : 'release') . '.')), ]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/cpu.php <<'AURORA_THEME_EOF' $file) { if (preg_match('/^Package id (\d+)$/', $label, $match)) { $value = @file_get_contents($file); $temps[(int) $match[1]] = $value === false ? null : round((float) $value / 1000, 1); } } continue; } $input = $found['Tdie'] ?? $found['Tctl'] ?? (is_file("$monitor/temp1_input") ? "$monitor/temp1_input" : null); $value = $input ? @file_get_contents($input) : false; $temps[] = $value === false ? null : round((float) $value / 1000, 1); } ksort($temps); echo json_encode([ 't' => microtime(true), 'total' => $total, 'cpus' => array_values($cpus), 'energy' => $energy, 'temps' => array_values($temps), ]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/docker-groups.php <<'AURORA_THEME_EOF' replaces the stored groups // "containers" holds the members' names for both kinds. // Unraid checks its csrf_token on every POST before this file runs, so writes can only come // from a logged-in webGUI page. The groups live on the flash drive and survive reboots. define('TFT_GROUPS_FILE', '/boot/config/plugins/Aurora-Theme/' . (($_GET['type'] ?? '') === 'vm' ? 'vm-groups.json' : 'docker-groups.json')); /** Keeps only well-formed groups; a container can be in one group, the first that names it. */ function tft_clean_groups($groups): array { $clean = []; $seenGroups = []; $seenContainers = []; foreach (is_array($groups) ? array_slice($groups, 0, 100) : [] as $group) { $name = is_array($group) && is_string($group['name'] ?? null) ? trim(mb_substr($group['name'], 0, 60)) : ''; if ($name === '' || isset($seenGroups[mb_strtolower($name)])) { continue; } $seenGroups[mb_strtolower($name)] = true; $containers = []; foreach (is_array($group['containers'] ?? null) ? array_slice($group['containers'], 0, 500) : [] as $container) { if (!is_string($container) || $container === '' || strlen($container) > 128 || isset($seenContainers[$container])) { continue; } $seenContainers[$container] = true; $containers[] = $container; } $clean[] = ['name' => $name, 'containers' => $containers]; } return $clean; } header('Content-Type: application/json'); header('Cache-Control: no-store'); if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') { $input = json_decode((string) ($_POST['groups'] ?? ''), true); if (!is_array($input)) { http_response_code(400); echo json_encode(['error' => 'groups must be a JSON list']); exit; } $groups = tft_clean_groups($input); if (!is_dir(dirname(TFT_GROUPS_FILE))) { mkdir(dirname(TFT_GROUPS_FILE), 0755, true); } if (file_put_contents(TFT_GROUPS_FILE, json_encode(['groups' => $groups], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n", LOCK_EX) === false) { http_response_code(500); echo json_encode(['error' => 'could not write ' . TFT_GROUPS_FILE]); exit; } echo json_encode(['groups' => $groups]); exit; } $stored = json_decode((string) @file_get_contents(TFT_GROUPS_FILE), true); echo json_encode(['groups' => tft_clean_groups($stored['groups'] ?? [])]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/docker-updates.php <<'AURORA_THEME_EOF' $stat ? $stat['mtime'] . '-' . $stat['size'] : '', 'busy' => $busy]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/docker-view.php <<'AURORA_THEME_EOF' 'POST view=aurora or view=folderview']); exit; } // The settings file holds key="value" lines; only the dockerview line is replaced or added. $lines = is_file(TFT_CFG_FILE) ? file(TFT_CFG_FILE, FILE_IGNORE_NEW_LINES) : []; $lines = array_values(array_filter($lines, function ($line) { return strpos($line, 'dockerview=') !== 0; })); $lines[] = 'dockerview="' . $view . '"'; if (!is_dir(dirname(TFT_CFG_FILE))) { mkdir(dirname(TFT_CFG_FILE), 0755, true); } if (file_put_contents(TFT_CFG_FILE, implode("\n", $lines) . "\n", LOCK_EX) === false) { http_response_code(500); echo json_encode(['error' => 'could not write ' . TFT_CFG_FILE]); exit; } echo json_encode(['view' => $view]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/favourites.php <<'AURORA_THEME_EOF' replaces the stored list // Unraid checks its csrf_token on every POST before this file runs, so writes can only come // from a logged-in webGUI page. The list lives on the flash drive and survives reboots. const TFT_FAVOURITES_FILE = '/boot/config/plugins/Aurora-Theme/favourites.json'; /** Keeps page paths such as "Settings/Identification", each once, in the order given. */ function tft_clean_favourites($list): array { $clean = []; foreach (is_array($list) ? array_slice($list, 0, 200) : [] as $item) { if (is_string($item) && strlen($item) <= 120 && preg_match('#^[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*$#', $item) && !in_array($item, $clean, true)) { $clean[] = $item; } } return $clean; } header('Content-Type: application/json'); header('Cache-Control: no-store'); if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') { $input = json_decode((string) ($_POST['favourites'] ?? ''), true); if (!is_array($input)) { http_response_code(400); echo json_encode(['error' => 'favourites must be a JSON list']); exit; } $favourites = tft_clean_favourites($input); if (!is_dir(dirname(TFT_FAVOURITES_FILE))) { mkdir(dirname(TFT_FAVOURITES_FILE), 0755, true); } if (file_put_contents(TFT_FAVOURITES_FILE, json_encode(['favourites' => $favourites], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n", LOCK_EX) === false) { http_response_code(500); echo json_encode(['error' => 'could not write ' . TFT_FAVOURITES_FILE]); exit; } echo json_encode(['favourites' => $favourites]); exit; } $stored = json_decode((string) @file_get_contents(TFT_FAVOURITES_FILE), true); echo json_encode(['favourites' => tft_clean_favourites($stored['favourites'] ?? [])]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/login-head.php <<'AURORA_THEME_EOF' . Files below /plugins are not served before login, so the stylesheet and two fonts // are written into the page. Nothing is written when the theme is off, when ?aurora=off is // used, or when a file is missing, so the stock login page always remains. (function () { $dir = '/usr/local/emhttp/plugins/Aurora-Theme'; $cfg = array_merge((array) @parse_ini_file("$dir/default.cfg"), (array) @parse_ini_file('/boot/config/plugins/Aurora-Theme/Aurora-Theme.cfg')); if (($cfg['theme'] ?? 'yes') !== 'yes') { return; } // The same emergency switch as on every other page. $switch = $_GET['aurora'] ?? ''; if ($switch === 'off') { echo "\n"; return; } if ($switch === 'on') { echo "\n"; } elseif (!empty($_COOKIE['aurora_off'])) { return; } $css = @file_get_contents("$dir/styles/login.css"); if ($css === false) { return; } $font = function (string $file) use ($dir): string { $data = @file_get_contents("$dir/fonts/$file"); return $data === false ? '' : 'data:font/woff2;base64,' . base64_encode($data); }; $css = str_replace( ['@@INTER@@', '@@GROTESK@@'], [$font('inter-latin-wght-normal.woff2'), $font('space-grotesk-latin-wght-normal.woff2')], $css ); echo '\n"; // "Successfully logged out" is shown with the error style; it gets the calm one instead. echo "\n"; })(); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/plugin-recheck.php <<'AURORA_THEME_EOF' .plg returns {"ok": true|false} // Unraid checks its csrf_token on every POST before this file runs. header('Content-Type: application/json'); header('Cache-Control: no-store'); if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { http_response_code(405); echo json_encode(['error' => 'POST only']); exit; } // Only an installed plugin (Unraid keeps a link per installed plugin in /var/log/plugins). The // OS itself has its own update check. $plugin = (string) ($_POST['plugin'] ?? ''); if (!preg_match('/^[A-Za-z0-9][A-Za-z0-9._ -]*\.plg$/', $plugin) || $plugin === 'unRAIDServer.plg' || !is_link('/var/log/plugins/' . $plugin)) { http_response_code(400); echo json_encode(['error' => 'not an installed plugin']); exit; } set_time_limit(150); $script = '/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/plugin'; $status = 1; for ($try = 1; $try <= 3 && $status !== 0; $try++) { if ($try > 1) sleep(2 * ($try - 1)); $output = []; exec(escapeshellarg($script) . ' check ' . escapeshellarg($plugin) . ' 2>&1', $output, $status); } echo json_encode(['ok' => $status === 0]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/ports.php <<'AURORA_THEME_EOF' /dev/null || ss -H -l -n -t -u -p 2>/dev/null', $lines); foreach ($lines as $line) { // "tcp LISTEN 0 4096 0.0.0.0:445 0.0.0.0:* users:(("smbd",pid=1234,fd=40))" $fields = preg_split('/\s+/', trim($line)); if (count($fields) < 5 || !preg_match('/:(\d+)$/', $fields[4], $match)) { continue; } $proto = $fields[0] === 'udp' ? 'udp' : 'tcp'; $port = (int) $match[1]; $process = preg_match('/users:\(\("([^"]+)"/', $line, $name) ? $name[1] : ''; if ($process === 'docker-proxy' || isset($seen["$proto/$port"])) { continue; } $seen["$proto/$port"] = true; $listening[] = ['port' => $port, 'proto' => $proto, 'process' => $process]; } usort($listening, function ($a, $b) { return $a['port'] <=> $b['port']; }); echo json_encode(['listening' => $listening]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/stats.php <<'AURORA_THEME_EOF' 'live', 'points' => $point ? [$point] : []]); exit; } $spans = ['hour' => 3600, 'day' => 86400, 'week' => 7 * 86400, 'month' => 31 * 86400]; if (!isset($spans[$range])) { $range = 'hour'; } $end = time(); $start = $end - $spans[$range]; echo json_encode(['range' => $range, 'start' => $start, 'end' => $end, 'points' => tft_stats_history($start, $end)]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/theme.php <<'AURORA_THEME_EOF' $message]); exit; } /** Reads dynamix.cfg as sections, retrying while another writer has it half written. */ function aurora_read_cfg(): ?array { for ($try = 0; $try < 15; $try++) { clearstatcache(true, AURORA_DYNAMIX_CFG); $text = @file_get_contents(AURORA_DYNAMIX_CFG); if (is_string($text) && trim($text) !== '') { $parsed = @parse_ini_string($text, true, INI_SCANNER_RAW); if (is_array($parsed) && $parsed) return $parsed; } usleep(200000); } return null; } // Values are written back as read (INI_SCANNER_RAW), quoted like Unraid's own write_ini_file. function aurora_ini_value($value): string { return '"' . $value . '"'; } /** Writes the sections in the layout Unraid uses, through a temporary file and a rename. */ function aurora_write_cfg(array $cfg): bool { $loose = ''; $sections = ''; foreach ($cfg as $name => $value) { if (!is_array($value)) { $loose .= $name . '=' . aurora_ini_value($value) . "\n"; continue; } $sections .= '[' . $name . "]\n"; foreach ($value as $key => $item) { foreach (is_array($item) ? $item : [$item] as $index => $one) { $sections .= $key . (is_array($item) ? '[' . (is_int($index) ? '' : $index) . ']' : '') . '=' . aurora_ini_value($one) . "\n"; } } } $temp = AURORA_DYNAMIX_CFG . '.aurora-tmp'; if (file_put_contents($temp, $loose . $sections) === false) return false; if (!rename($temp, AURORA_DYNAMIX_CFG)) { @unlink($temp); return false; } return true; } header('Content-Type: application/json'); header('Cache-Control: no-store'); if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') aurora_fail(405, 'POST only'); $theme = (string) ($_POST['theme'] ?? ''); $step = (string) ($_POST['step'] ?? 'save'); if (!in_array($theme, ['black', 'white'], true)) aurora_fail(400, 'theme must be black or white'); if (!in_array($step, ['save', 'verify'], true)) aurora_fail(400, 'step must be save or verify'); if (!is_dir(AURORA_THEME_DIR)) @mkdir(AURORA_THEME_DIR, 0700, true); $lock = fopen(AURORA_THEME_LOCK, 'c'); if (!$lock || !flock($lock, LOCK_EX)) aurora_fail(500, 'could not lock ' . AURORA_THEME_LOCK); $cfg = aurora_read_cfg(); if ($cfg === null) aurora_fail(503, 'dynamix.cfg could not be read, so nothing was changed'); if (isset($cfg['display']) && !is_array($cfg['display'])) aurora_fail(500, 'dynamix.cfg has no [display] section'); $restored = 0; if ($step === 'save') { file_put_contents(AURORA_THEME_SNAPSHOT, json_encode(['time' => time(), 'cfg' => $cfg])); } else { $snapshot = json_decode((string) @file_get_contents(AURORA_THEME_SNAPSHOT), true); if (is_array($snapshot) && is_array($snapshot['cfg'] ?? null) && time() - (int) ($snapshot['time'] ?? 0) <= AURORA_SNAPSHOT_MAX_AGE) { foreach ($snapshot['cfg'] as $name => $value) { if (!is_array($value)) { if (!array_key_exists($name, $cfg)) { $cfg[$name] = $value; $restored++; } continue; } if (!isset($cfg[$name]) || !is_array($cfg[$name])) $cfg[$name] = []; foreach ($value as $key => $item) { if (!array_key_exists($key, $cfg[$name])) { $cfg[$name][$key] = $item; $restored++; } } } } @unlink(AURORA_THEME_SNAPSHOT); } if ($restored === 0 && ($cfg['display']['theme'] ?? null) === $theme) { echo json_encode(['theme' => $theme, 'restored' => 0]); exit; } $cfg['display']['theme'] = $theme; if (!aurora_write_cfg($cfg)) aurora_fail(500, 'could not write dynamix.cfg'); // Read back: the file must hold everything just written. If another writer took keys away in // the meantime, they are put back (keeping what that writer changed) and the file is checked // again, a few times at most. for ($attempt = 0; ; $attempt++) { $check = aurora_read_cfg(); if ($check === null) aurora_fail(500, 'dynamix.cfg could not be read back'); $missing = 0; foreach ($cfg as $name => $value) { if (!is_array($value)) { if (!array_key_exists($name, $check)) { $check[$name] = $value; $missing++; } continue; } if (!isset($check[$name]) || !is_array($check[$name])) $check[$name] = []; foreach ($value as $key => $item) { if (!array_key_exists($key, $check[$name])) { $check[$name][$key] = $item; $missing++; } } } if (!$missing && ($check['display']['theme'] ?? null) === $theme) break; if ($attempt >= 3) aurora_fail(500, 'dynamix.cfg did not read back as written'); $restored += $missing; $check['display']['theme'] = $theme; if (!aurora_write_cfg($check)) aurora_fail(500, 'could not write dynamix.cfg'); $cfg = $check; } if ($restored) error_log("Aurora Theme: put back $restored dynamix.cfg settings lost while the theme changed"); echo json_encode(['theme' => $theme, 'restored' => $restored]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/include/updated.php <<'AURORA_THEME_EOF' true]); exit; } $record = json_decode((string) @file_get_contents(AURORA_UPDATED_FILE), true); $version = '/^\d{4}\.\d{2}\.\d{2}[\w.-]*$/'; if (!is_array($record) || !preg_match($version, (string) ($record['to'] ?? '')) || !preg_match($version, (string) ($record['from'] ?? ''))) { echo '{}'; exit; } /** * The releases for the record, newest first, or null when GitHub did not answer. Pages of 100 * are read until one reaches back to "from" (at most 5, so 500 releases). */ function aurora_releases_between(string $from, string $to): ?array { $back = strcmp($to, $from) < 0; $dev = aurora_is_dev($to); $found = []; for ($pageNumber = 1; $pageNumber <= 5; $pageNumber++) { $api = 'https://api.github.com/repos/' . AURORA_REPO . '/releases?per_page=100&page=' . $pageNumber; $output = []; exec('/usr/bin/curl -sSL --max-time 15 -w ' . escapeshellarg('\n%{http_code}') . ' -H ' . escapeshellarg('Accept: application/vnd.github+json') . ' -A ' . escapeshellarg(AURORA_PLUGIN . '-release-notes') . ' ' . escapeshellarg($api) . ' 2>/dev/null', $output); $code = (int) array_pop($output); $list = $code === 200 ? json_decode(implode("\n", $output), true) : null; if (!is_array($list)) return $pageNumber === 1 ? null : $found; $older = false; foreach ($list as $release) { $version = is_array($release) && empty($release['draft']) ? ltrim((string) ($release['tag_name'] ?? ''), 'vV') : ''; if (!preg_match('/^\d{4}\.\d{2}\.\d{2}[\w.-]*$/', $version)) continue; if (strcmp($version, $from) <= 0) $older = true; $wanted = $back ? $version === $to : strcmp($version, $from) > 0 && strcmp($version, $to) <= 0 && ($dev || !aurora_is_dev($version)); if (!$wanted) continue; $found[$version] = [ 'version' => $version, 'date' => (string) ($release['published_at'] ?? ''), 'notes' => mb_substr((string) ($release['body'] ?? ''), 0, 20000), 'page' => preg_match('#^https://github\.com/#', (string) ($release['html_url'] ?? '')) ? $release['html_url'] : AURORA_RELEASES_PAGE, ]; } if ($older || count($list) < 100) break; } krsort($found, SORT_STRING); return array_values($found); } if (!array_key_exists('releases', $record)) { $releases = aurora_releases_between($record['from'], $record['to']); // Kept only when GitHub answered; after a network problem the next page asks again. if ($releases !== null) { $record['releases'] = $releases; $record['page'] = count($releases) === 1 ? $releases[0]['page'] : AURORA_RELEASES_PAGE; @file_put_contents(AURORA_UPDATED_FILE, json_encode($record) . "\n"); } } echo json_encode([ 'from' => $record['from'], 'to' => $record['to'], 'time' => (int) ($record['time'] ?? 0), 'automatic' => !empty($record['automatic']), 'releases' => $record['releases'] ?? null, 'page' => $record['page'] ?? AURORA_RELEASES_PAGE, ]); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/scripts/autoupdate.php <<'AURORA_THEME_EOF' #!/usr/bin/php time(), 'result' => $result, 'message' => $message, 'version' => $version]) . "\n"); // Every run leaves one line in the system log (Tools > System Log, tag aurora-theme). exec('/usr/bin/logger -t aurora-theme ' . escapeshellarg("automatic update: $message")); echo "$message\n"; exit($result === 'failed' ? 1 : 0); } function notify(string $subject, string $description, string $level): void { $notify = '/usr/local/emhttp/webGui/scripts/notify'; if (is_executable($notify)) { exec($notify . ' -e ' . escapeshellarg('Aurora Theme') . ' -s ' . escapeshellarg($subject) . ' -d ' . escapeshellarg($description) . ' -i ' . escapeshellarg($level)); } } // One run at a time, also when a run is started by hand while cron starts another. $lock = fopen('/var/run/aurora-theme-autoupdate.lock', 'c'); if (!$lock || !flock($lock, LOCK_EX | LOCK_NB)) { exit(0); } if (aurora_setting('autoupdate') !== 'yes') { finish('off', 'Automatic updates are turned off.'); } $installed = aurora_installed_version(); $latest = aurora_latest_release(); if (isset($latest['error'])) { finish('skipped', $latest['error']); } $available = $latest['version']; if (strcmp($available, $installed) <= 0) { finish('current', "Up to date ($installed).", $installed); } // The exact release that was found, not the "latest" address (see aurora_latest_release()). exec('/usr/local/sbin/plugin install ' . escapeshellarg($latest['plg']) . ' 2>&1', $output, $status); if (aurora_installed_version() === $available) { $marker = '/boot/config/plugins/' . AURORA_PLUGIN . '/updated.json'; $record = json_decode((string) @file_get_contents($marker), true); if (is_array($record) && ($record['to'] ?? '') === $available) { $record['automatic'] = true; @file_put_contents($marker, json_encode($record) . "\n"); } notify('Aurora Theme updated', "Updated automatically from $installed to $available. Open the webGUI to see what changed.", 'normal'); finish('updated', "Updated from $installed to $available.", $available); } exec('/usr/bin/logger -t aurora-theme ' . escapeshellarg('automatic update: plugin install said: ' . trim(implode(' ', array_slice($output, -6))))); notify('Aurora Theme update failed', "Version $available could not be installed automatically.", 'warning'); finish('failed', "Installing $available failed: " . trim(implode(' ', array_slice($output, -3))), $installed); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/scripts/collect.php <<'AURORA_THEME_EOF' #!/usr/bin/php /usr/local/emhttp/plugins/Aurora-Theme/scripts/enhance.js <<'AURORA_THEME_EOF' // Page enhancements for the Aurora Theme: elements the stock webGUI does not have. // Each enhancer checks for the page it belongs to and does nothing elsewhere. (function () { 'use strict'; // ".../plugins//scripts/enhance.js" -> ".../plugins/", for the plugin's endpoints. const PLUGIN_BASE = ((document.currentScript && document.currentScript.src) || '').replace(/\/scripts\/[^/]*$/, ''); // Eyebrow and subtitle for the stock tabs; other tabs get a heading without them. const TABS = { Dashboard: ['Overview', 'Health, storage and services at a glance.'], Main: ['Storage', 'Array devices, pools and boot media.'], Shares: ['Storage', 'User shares and how they are exported and stored.'], Users: ['Access', 'Accounts that can reach shares and the web interface.'], Settings: ['Configuration', 'System, network service and preference settings.'], Plugins: ['Extensions', 'Installed plugins and the updates available for them.'], Docker: ['Applications', 'Manage containers, review resource use, and keep services healthy.'], VMs: ['Virtualisation', 'Virtual machines and the resources assigned to them.'], Apps: ['Community applications', 'Browse container templates and install them in one step.'], Stats: ['Monitoring', 'Processor, memory, network and disk activity over time.'], Tools: ['Maintenance', 'Diagnostics, system information and maintenance utilities.'], }; function element(tag, className, text) { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; } function iconButton(icon, label, onClick, extraClass) { const button = element('button', 'tp-icon-button' + (extraClass ? ' ' + extraClass : '')); button.type = 'button'; button.title = label; button.setAttribute('aria-label', label); button.append(element('i', 'fa fa-' + icon)); button.addEventListener('click', onClick); return button; } // FolderView (folder.view, .view2, .view3) arranges the Docker list and the Dashboard's Docker // tile, unless the switch above the Docker list is set to the theme's view (AuroraTheme.page // then keeps FolderView out of the list). function folderViewActive() { const view = window.auroraDockerView; if (folderViewPlus()) return true; if (view && view.mode === 'aurora') return false; return Boolean(document.querySelector('script[src*="/folder.view"]')); } // FolderView Plus (folderview.plus) owns its lists outright: the theme leaves its folders, rows // and chips alone (no view switch, no clicks taken over, nothing moved or rewritten) and keeps // to colours and layout. AuroraTheme.page tells when it is installed; its scripts tell too. function folderViewPlus() { const view = window.auroraDockerView; return Boolean((view && view.plus) || document.querySelector('script[src*="/plugins/folderview.plus/"]')); } // A group's apps (containers or VMs) as small icons: running and paused ones first with a // status dot (green, amber), then the stopped ones greyed out, each part in the list's order. // The first `max` of them are shown and "+n" for the rest; each icon's tooltip is the app's // name. An app with a target (the element whose click opens its menu) opens that menu when // its icon is clicked. The strip is only rebuilt when the apps or their states change. // apps: [{ name, src, state: 'running' | 'paused' | 'stopped', update, target }] const STRIP_ORDER = { running: 0, paused: 1, stopped: 2 }; function appStrip(strip, list, max) { // Apps with an update first, so the limit never hides their dot; then running, paused, stopped. const apps = list.slice().sort(function (a, b) { return (b.update ? 1 : 0) - (a.update ? 1 : 0) || STRIP_ORDER[a.state] - STRIP_ORDER[b.state]; }); // The targets are replaced on every call: Unraid rebuilds its rows and chips on refresh. strip.tpTargets = new Map(apps.map(function (app) { return [app.name, app.target]; })); if (!strip.tpBound) { strip.tpBound = true; strip.addEventListener('click', openFromStrip); strip.addEventListener('keydown', openFromStrip); } const signature = apps.map(function (app) { return app.name + ':' + app.state + (app.update ? ':u' : '') + (app.target ? '' : ':-'); }).join('|'); if (strip.dataset.apps === signature) return; strip.dataset.apps = signature; const shown = apps.slice(0, max).map(function (app) { const item = element('span', 'tp-app-icon tp-app-' + app.state + (app.update ? ' tp-app-update' : '')); item.dataset.name = app.name; item.title = app.name + (app.state === 'running' ? '' : ' (' + app.state + ')') + (app.update ? ', update available' : ''); if (app.target) { item.setAttribute('role', 'button'); item.tabIndex = 0; } const image = element('img'); image.src = app.src || '/plugins/dynamix.docker.manager/images/question.png'; image.alt = ''; image.loading = 'lazy'; item.append(image); return item; }); if (apps.length > max) shown.push(element('span', 'tp-app-more', '+' + (apps.length - max))); strip.replaceChildren(...shown); strip.hidden = !apps.length; } // A click (or Enter) on an icon of a strip: the click is passed on to the app's own element, // at the icon's position, so its menu opens there, and it goes no further (the strip sits // inside a group's header or chip, which would open or close the group). function openFromStrip(event) { if (event.type === 'keydown' && event.key !== 'Enter' && event.key !== ' ') return; const icon = event.target.closest('.tp-app-icon'); const target = icon && event.currentTarget.tpTargets && event.currentTarget.tpTargets.get(icon.dataset.name); if (!target || !target.isConnected) return; event.preventDefault(); event.stopPropagation(); const box = icon.getBoundingClientRect(); target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window, clientX: event.clientX || box.left, clientY: event.clientY || box.bottom, })); } // ---- Application groups: named sets of containers (type 'docker') or VMs (type 'vm'), // shown as collapsible sections on the Docker and VMs tabs and on the Dashboard. Stored on the // server (include/docker-groups.php) so every browser sees the same groups, in the order they // are arranged in; members are kept by name, in "containers" for both kinds. function validGroups(list) { return Array.isArray(list) ? list.filter(function (group) { return group && typeof group.name === 'string' && Array.isArray(group.containers); }) : []; } // Which sections are collapsed, remembered per browser (shared by the tab and the Dashboard). function collapsedState(storageKey) { const read = function () { try { return JSON.parse(localStorage.getItem(storageKey) || '[]'); } catch (error) { return []; } }; return { has: function (key) { return read().includes(key); }, toggle: function (key) { const keys = read(); const next = keys.includes(key) ? keys.filter(function (entry) { return entry !== key; }) : keys.concat(key); try { localStorage.setItem(storageKey, JSON.stringify(next)); } catch (error) { /* storage blocked */ } }, }; } // changed() redraws after the groups changed; saved(), if given, runs after a change was // drawn (the tabs save Unraid's order there, see saveUnraidOrder). function groupStore(type, changed, saved) { const url = PLUGIN_BASE + '/include/docker-groups.php' + (type === 'vm' ? '?type=vm' : ''); const noun = type === 'vm' ? 'VMs' : 'containers'; let menu = null; const closeMenu = function () { if (menu) menu.remove(); menu = null; }; document.addEventListener('click', closeMenu); document.addEventListener('keydown', function (event) { if (event.key === 'Escape') closeMenu(); }); const store = { groups: [], load: function () { // Normally the page already carries the groups (AuroraTheme.page); otherwise they are fetched. const preset = type === 'vm' ? window.auroraVmGroups : window.auroraDockerGroups; if (Array.isArray(preset)) { store.groups = validGroups(preset); changed(); return; } if (!PLUGIN_BASE) return; fetch(url, { credentials: 'same-origin' }) .then(function (response) { return response.json(); }) .then(function (data) { store.groups = validGroups(data.groups); changed(); }) .catch(function (error) { console.error('Aurora Theme groups:', error); }); }, save: function () { changed(); if (saved) saved(); if (!PLUGIN_BASE) return; const body = new URLSearchParams({ groups: JSON.stringify(store.groups), csrf_token: window.csrf_token || '' }); fetch(url, { method: 'POST', credentials: 'same-origin', body: body }) .then(function (response) { if (!response.ok) throw new Error('saving groups failed with status ' + response.status); }) .catch(function (error) { console.error('Aurora Theme groups:', error); modal({ title: 'Group change not saved', message: 'The server did not accept the change. It will be lost when the page reloads.', confirmLabel: 'OK', hideCancel: true }); }); }, of: function (name) { return store.groups.find(function (group) { return group.containers.includes(name); }) || null; }, // Asks for a name; resolves with the new group, an existing group of that name, or null. create: function () { return modal({ title: 'New group', message: 'Give the group a name. ' + (type === 'vm' ? 'VMs' : 'Containers') + ' are added to it from their folder button, or by dragging them into it while the list is unlocked.', input: { placeholder: 'Group name', maxLength: 60 }, confirmLabel: 'Create group', }).then(function (name) { if (!name) return null; const existing = store.groups.find(function (group) { return group.name.toLowerCase() === name.toLowerCase(); }); if (existing) return existing; const group = { name: name, containers: [] }; store.groups.push(group); return group; }); }, assign: function (name, group) { store.groups.forEach(function (other) { other.containers = other.containers.filter(function (entry) { return entry !== name; }); }); if (group) group.containers.push(name); store.save(); }, // One place up (-1) or down (1) in the order of the groups. move: function (group, step) { const from = store.groups.indexOf(group); const to = from + step; if (from < 0 || to < 0 || to >= store.groups.length) return; store.groups.splice(to, 0, store.groups.splice(from, 1)[0]); store.save(); }, rename: function (group) { modal({ title: 'Rename group', input: { value: group.name, placeholder: 'Group name', maxLength: 60 }, validate: function (name) { const taken = store.groups.some(function (other) { return other !== group && other.name.toLowerCase() === name.toLowerCase(); }); return taken ? 'A group with that name already exists.' : ''; }, confirmLabel: 'Rename', }).then(function (name) { if (!name || name === group.name) return; group.name = name; store.save(); }); }, remove: function (group) { modal({ title: 'Delete "' + group.name + '"?', message: 'Only the group is removed. Its ' + noun + ' are kept and become ungrouped.', confirmLabel: 'Delete group', danger: true, }).then(function (confirmed) { if (!confirmed) return; store.groups = store.groups.filter(function (other) { return other !== group; }); store.save(); }); }, // Small menu next to a folder button: pick a group, make a new one, or leave the group. openMenu: function (anchor, name) { closeMenu(); const current = store.of(name); const list = element('ul', 'dropdown-menu tp-group-menu'); const entry = function (icon, text, onClick) { const item = element('li'); const link = element('a'); link.href = '#'; link.append(element('i', 'fa fa-fw ' + icon), ' ' + text); link.addEventListener('click', function (event) { event.preventDefault(); closeMenu(); onClick(); }); item.append(link); list.append(item); }; store.groups.forEach(function (group) { entry(group === current ? 'fa-check' : 'fa-folder-o', group.name, function () { store.assign(name, group); }); }); if (store.groups.length) list.append(element('li', 'divider')); entry('fa-plus', 'New group…', function () { store.create().then(function (group) { if (group) store.assign(name, group); }); }); if (current) entry('fa-times', 'Remove from group', function () { store.assign(name, null); }); document.body.append(list); const box = anchor.getBoundingClientRect(); list.style.cssText = 'display:block;position:fixed;z-index:10001;margin:0'; list.style.top = Math.min(box.bottom + 6, window.innerHeight - list.offsetHeight - 8) + 'px'; list.style.left = Math.max(8, Math.min(box.right - list.offsetWidth, window.innerWidth - list.offsetWidth - 8)) + 'px'; menu = list; }, }; return store; } // A section's header row on the Docker or VMs tab: open / close with the name and count, the // members' icons, and buttons to start or stop the whole section, move the group up or down, // rename or delete it (the last four not for "Ungrouped"). The row's tpItems are the section's // members ({ name, running, paused }), set by the tab on every refresh. // options: { noun ('container' / 'VM'), onToggle(), start(stopped items), stop(running items), load, // removeAll(items): a "Remove all" button instead of start and stop (Docker's orphan images) } function sectionHeader(store, group, options) { const row = element('tr', 'tp-group'); const cell = element('td'); cell.colSpan = 20; const head = element('div', 'tp-group-head'); const toggle = element('button', 'tp-group-toggle'); toggle.type = 'button'; toggle.append(element('i', 'fa fa-chevron-down'), element('span', 'tp-group-name'), element('span', 'tp-group-count')); toggle.addEventListener('click', options.onToggle); if (group) { row.tpGroup = group; const grip = element('span', 'tp-group-grip'); grip.title = 'Drag to move the group'; grip.setAttribute('aria-hidden', 'true'); grip.append(element('i', 'fa fa-arrows-v')); dragGroup(row, grip, store); head.append(grip); } head.append(toggle, element('span', 'tp-group-apps')); if (options.load) { const load = element('span', 'tp-group-load'); load.append(element('span', 'tp-group-cpu'), element('span', 'tp-group-mem')); head.append(load); } const actions = element('div', 'tp-actions'); const members = function () { return row.tpItems || []; }; const plural = options.noun + 's'; if (options.removeAll) { const remove = element('button', 'tp-group-remove-all'); remove.type = 'button'; remove.title = 'Remove all ' + plural + ' in this section'; remove.append(element('i', 'fa fa-trash'), element('span', '', 'Remove all')); remove.addEventListener('click', function () { if (members().length) options.removeAll(members()); }); actions.append(remove); } else { actions.append(iconButton('play', 'Start all stopped ' + plural + ' in this section', function () { options.start(members().filter(function (item) { return !item.running && !item.paused; })); })); actions.append(iconButton('stop', 'Stop all running ' + plural + ' in this section', function () { const running = members().filter(function (item) { return item.running; }); if (!running.length) return; modal({ title: 'Stop ' + running.length + ' ' + (running.length === 1 ? options.noun : plural) + '?', message: 'All running ' + plural + ' in this section will be stopped: ' + running.map(function (item) { return item.name; }).join(', ') + '.', confirmLabel: 'Stop', danger: true, }).then(function (confirmed) { if (confirmed) options.stop(running); }); }, 'tp-danger')); } if (group) { row.tpUp = iconButton('arrow-up', 'Move group up', function () { store.move(group, -1); }, 'tp-group-move'); row.tpDown = iconButton('arrow-down', 'Move group down', function () { store.move(group, 1); }, 'tp-group-move'); actions.append(row.tpUp, row.tpDown); actions.append(iconButton('pencil', 'Rename group', function () { store.rename(group); })); actions.append(iconButton('trash-o', 'Delete group (its ' + plural + ' stay)', function () { store.remove(group); }, 'tp-danger')); } head.append(actions); cell.append(head); row.append(cell); return row; } // Unraid's lock button (in the menu) turns rearranging the Docker and VM lists on and off and // remembers "unlocked" in its lockbutton cookie. The groups' move controls (grip, up, down) // follow it: html.tp-unlocked is set while the lists can be rearranged (enhance.css shows the // controls only then). function listsUnlocked() { return /(?:^|;\s*)lockbutton=/.test(document.cookie); } function lockState() { const sync = function () { document.documentElement.classList.toggle('tp-unlocked', listsUnlocked()); }; sync(); // The Docker and VMs pages clear the cookie when they start (every visit begins locked), in // a jQuery ready handler that may run after this one. if (window.jQuery) window.jQuery(function () { setTimeout(sync); }); if (typeof window.LockButton === 'function' && !window.LockButton.tpWrapped) { const original = window.LockButton; window.LockButton = function () { const result = original.apply(this, arguments); sync(); return result; }; window.LockButton.tpWrapped = true; } } // Moves a group by dragging its header's grip (mouse, pen or touch) above or below another // group; its members move with it. A line shows where it will go. "Ungrouped" stays last. function dragGroup(row, grip, store) { grip.addEventListener('pointerdown', function (event) { if (event.button !== 0 || !listsUnlocked()) return; const list = row.parentElement; const others = list ? [...list.querySelectorAll(':scope > tr.tp-group')].filter(function (header) { return header.tpGroup && header !== row; }) : []; if (!others.length) return; event.preventDefault(); grip.setPointerCapture(event.pointerId); row.classList.add('tp-group-dragging'); const line = element('div', 'tp-group-drop-line'); document.body.append(line); let place = -1; // place: how many of the other groups come before the dragged one. const update = function (y) { place = others.filter(function (header) { const box = header.getBoundingClientRect(); return box.top + box.height / 2 < y; }).length; // The line goes above the group that will follow, or under the last group's section. const next = others[place]; let lineY; if (next) { lineY = next.getBoundingClientRect().top; } else { let end = others[others.length - 1]; for (let sibling = end.nextElementSibling; sibling && !(sibling.classList.contains('tp-group') && sibling !== row); sibling = sibling.nextElementSibling) { if (sibling.offsetParent && sibling !== row) end = sibling; } lineY = end.getBoundingClientRect().bottom; } const box = list.getBoundingClientRect(); line.style.cssText = 'top:' + (lineY - 1) + 'px;left:' + box.left + 'px;width:' + box.width + 'px'; }; const onMove = function (moveEvent) { if (moveEvent.clientY < 60) window.scrollBy(0, -12); else if (moveEvent.clientY > window.innerHeight - 60) window.scrollBy(0, 12); update(moveEvent.clientY); }; const finish = function (endEvent) { grip.removeEventListener('pointermove', onMove); grip.removeEventListener('pointerup', finish); grip.removeEventListener('pointercancel', finish); row.classList.remove('tp-group-dragging'); line.remove(); if (endEvent.type !== 'pointerup' || place < 0) return; const group = row.tpGroup; const order = others.map(function (header) { return header.tpGroup; }); order.splice(place, 0, group); const current = store.groups.filter(function (entry) { return order.includes(entry); }); if (order.every(function (entry, index) { return entry === current[index]; })) return; store.groups = order.concat(store.groups.filter(function (entry) { return !order.includes(entry); })); store.save(); }; grip.addEventListener('pointermove', onMove); grip.addEventListener('pointerup', finish); grip.addEventListener('pointercancel', finish); update(event.clientY); }); } // Fills in a section header: name, count, icons, open or closed, and which move buttons work. function updateSectionHeader(header, section, options) { const running = section.items.filter(function (item) { return item.running; }).length; const setText = function (node, text) { if (node.textContent !== text) node.textContent = text; }; setText(header.querySelector('.tp-group-name'), section.title); const toggle = header.querySelector('.tp-group-toggle'); if (toggle.title !== section.title) toggle.title = section.title; setText(header.querySelector('.tp-group-count'), section.items.length + ' ' + (section.items.length === 1 ? options.noun : options.noun + 's') + (options.noRunning ? '' : ' · ' + running + ' running')); appStrip(header.querySelector('.tp-group-apps'), section.items.map(options.app), 8); header.classList.toggle('tp-collapsed', section.collapsed); header.querySelector('.tp-group-toggle').setAttribute('aria-expanded', String(!section.collapsed)); if (header.tpUp) { header.tpUp.disabled = section.index === 0; header.tpDown.disabled = section.index === options.groupCount - 1; } } // Unraid's own order of the Docker or VM list: the one its lock button lets you drag, which is // also the order the containers start in and the Dashboard shows. Saved from the list as it now // stands (sections and all), the way Unraid's drag and drop saves it. function saveUnraidOrder(type, list) { const jq = window.jQuery; if (!jq || !list) return; const names = [...list.querySelectorAll(type === 'vm' ? ':scope > tr[parent-id] > td.vm-name .inner > a' : 'td.ct-name:not(.folder-name) .appname')] .map(function (node) { return node.textContent.trim(); }) .filter(Boolean); if (!names.length) return; jq.post('/plugins/dynamix.' + (type === 'vm' ? 'vm' : 'docker') + '.manager/include/UserPrefs.php', { names: names.join(';') + ';', index: names.map(function (name, index) { return index; }).join(';') + ';', }); } // Unraid's drag and drop keeps a dragged row within the list's rows. A row taller than the // first one (a FolderView folder) then never gets far enough up to go before it, so the // dragged row may also move over the table's header. Applied whenever Unraid's lock button // turns dragging on (and at once if it already is). function roomToDragToTop(list) { const jq = window.jQuery; const table = list.closest('table'); if (!jq || !table) return; const widen = function () { if (jq(list).data('ui-sortable')) jq(list).sortable('option', 'containment', table); }; jq(list).on('sortcreate', widen); widen(); } // Unraid's drag and drop (unlocked with the lock button in the menu) moves a row and saves the // order itself. Dropped in another group's section, the container or VM joins that group (or // leaves its group in "Ungrouped"); the section is read right at the drop, before the list is // laid out again. While a row is dragged, list.tpSorting is set and the tab leaves the list // alone (its placeholder row would be moved away); redraw() runs once the row is dropped. function dropIntoSections(list, store, nameOfRow, ungroupedKey, redraw) { const jq = window.jQuery; if (!jq) return; jq(list).on('sortstart', function () { list.tpSorting = true; }); jq(list).on('sortstop', function () { list.tpSorting = false; setTimeout(redraw); }); jq(list).on('sortupdate', function (event, ui) { const row = ui && ui.item && ui.item[0]; if (!row || !store.groups.length) return; let header = row.previousElementSibling; while (header && !header.classList.contains('tp-group')) header = header.previousElementSibling; header = header || list.querySelector(':scope > tr.tp-group'); if (!header || !header.tpKey) return; const name = nameOfRow(row); const target = header.tpKey === ungroupedKey ? null : store.groups.find(function (group) { return 'g:' + group.name === header.tpKey; }) || null; if (name && target !== store.of(name)) store.assign(name, target); }); } // Opens Unraid's menu for a container or VM (the click on its icon) from a button at the end of // its row. Unraid opens the menu at the click's position and does not keep it on screen, so the // click is placed left of the button: the menu then ends at the button instead of past the edge. function openMenuFrom(trigger, button) { const box = button.getBoundingClientRect(); trigger.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window, clientX: Math.max(8, box.right - 260), clientY: box.bottom, })); } // Opens a container's web interface the way Unraid's own context menu does (window.open). // The element is a link without href: Unraid shows its "external link" warning for real // links to other addresses, but not for its menu, so the WebUI buttons behave like the menu. function webuiOpener(node, url) { node.setAttribute('role', 'link'); node.tabIndex = 0; node.dataset.url = url; const open = function (event) { event.preventDefault(); event.stopPropagation(); window.open(url, '_blank'); }; node.addEventListener('click', open); node.addEventListener('keydown', function (event) { if (event.key === 'Enter' || event.key === ' ') open(event); }); return node; } function debounce(fn) { let timer; return function () { clearTimeout(timer); timer = setTimeout(fn, 150); }; } // Unraid passes each container's details to its context menu in the icon's onclick call: // addDockerContainerContext(name, image, template, started, paused, update, autostart, webui, ...). // The arguments are read as text; nothing from the attribute is executed. function contextArguments(trigger) { const call = (trigger && trigger.getAttribute('onclick')) || ''; const inner = call.slice(call.indexOf('(') + 1, call.lastIndexOf(')')); const pattern = /'((?:[^'\\]|\\.)*)'|([^,'\s][^,]*)/g; const values = []; let match; while ((match = pattern.exec(inner))) { values.push(match[1] !== undefined ? match[1].replace(/\\(.)/g, '$1') : match[2].trim()); } return values; } // Themed dialog, used instead of the browser's prompt / confirm / alert boxes. // modal({ title, message, input: { value, placeholder, maxLength }, validate, confirmLabel, cancelLabel, danger, hideCancel }) // message may be a list of paragraphs. // Resolves with the entered text (or true when there is no input), or null when cancelled. // validate(text) may return a message to show under the input and keep the dialog open. function modal(options) { return new Promise(function (resolve) { const previousFocus = document.activeElement; const backdrop = element('div', 'tp-modal-backdrop'); const box = element('div', 'tp-modal'); const titleId = 'tp-modal-title-' + Date.now(); box.setAttribute('role', 'dialog'); box.setAttribute('aria-modal', 'true'); box.setAttribute('aria-labelledby', titleId); const title = element('h2', 'tp-modal-title', options.title); title.id = titleId; box.append(title); (Array.isArray(options.message) ? options.message : [options.message]).forEach(function (text) { if (text) box.append(element('p', 'tp-modal-message', text)); }); let input = null; const problem = element('p', 'tp-modal-problem'); problem.setAttribute('role', 'alert'); if (options.input) { input = element('input', 'tp-modal-input'); input.type = 'text'; input.value = options.input.value || ''; input.placeholder = options.input.placeholder || ''; input.maxLength = options.input.maxLength || 200; input.setAttribute('aria-label', options.input.placeholder || options.title); box.append(input, problem); } const actions = element('div', 'tp-modal-actions'); const cancel = element('button', 'tp-modal-button', options.cancelLabel || 'Cancel'); cancel.type = 'button'; const confirm = element('button', 'tp-modal-button ' + (options.danger ? 'tp-modal-danger' : 'tp-modal-primary'), options.confirmLabel || 'OK'); confirm.type = 'button'; if (!options.hideCancel) actions.append(cancel); actions.append(confirm); box.append(actions); backdrop.append(box); function close(result) { document.removeEventListener('keydown', onKey, true); backdrop.remove(); if (previousFocus && previousFocus.focus) previousFocus.focus(); resolve(result); } function accept() { if (!input) return close(true); const text = input.value.trim(); if (!text) return input.focus(); const message = options.validate ? options.validate(text) : ''; if (message) { problem.textContent = message; return input.focus(); } close(text); } function onKey(event) { if (event.key === 'Escape') { event.stopPropagation(); close(null); } else if (event.key === 'Enter') { event.preventDefault(); accept(); } else if (event.key === 'Tab') { // Keep the focus inside the dialog. const stops = [input, options.hideCancel ? null : cancel, confirm].filter(Boolean); const index = stops.indexOf(document.activeElement); event.preventDefault(); stops[(index + (event.shiftKey ? stops.length - 1 : 1)) % stops.length].focus(); } } if (input) { const sync = function () { confirm.disabled = !input.value.trim(); problem.textContent = ''; }; input.addEventListener('input', sync); sync(); } cancel.addEventListener('click', function () { close(null); }); confirm.addEventListener('click', accept); // A click on the dimmed area cancels; clicks inside the box must not reach the page. backdrop.addEventListener('click', function (event) { event.stopPropagation(); if (event.target === backdrop) close(null); }); document.addEventListener('keydown', onKey, true); document.body.append(backdrop); (input || confirm).focus(); if (input) input.select(); }); } // "Tower/DisplaySettings" in the page title gives the server name and the page name. function pageInfo() { const titleParts = document.title.split('/'); const active = document.querySelector('#menu .nav-item.active a'); const tab = active ? active.textContent.trim() : ''; const path = location.pathname.split('/').filter(Boolean); const subPage = tab && path[0] === tab && path.length > 1 ? path[path.length - 1] : ''; // A page with a single section is named after that section's title ("PHP Settings", not // "PHPsettings"); otherwise the address is split into words. const titles = subPage ? document.querySelectorAll('#displaybox div.title > span.left') : []; const sectionName = titles.length === 1 ? titles[0].textContent.replace(/\s+/g, ' ').trim() : ''; // The page's own title from Unraid (AuroraTheme.page) comes first: the address split into // words read "CP Uset" for CPU Pinning and "UP Ssettings" for UPS Settings. const pageTitle = subPage && typeof window.auroraPageTitle === 'string' ? window.auroraPageTitle : ''; return { server: titleParts.length > 1 ? titleParts[0] : '', tab: tab || (titleParts[1] || ''), // "DisplaySettings" reads better as "Display Settings". // ... and "VMTemplates" as "VM Templates". subPage: pageTitle || (sectionName && sectionName.length <= 40 ? sectionName : subPage.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/[._]/g, ' ')), }; } // Slim top bar: breadcrumb on the left, with the array state mirrored from the footer. function topBar(info) { const header = document.getElementById('header'); if (!header || !info.tab || header.querySelector('.tp-crumb')) return; const crumb = element('div', 'tp-crumb'); const trail = element('span'); trail.append(info.server ? info.server + ' / ' : '', element('strong', '', info.subPage || info.tab)); crumb.append(trail); header.prepend(crumb); const status = document.getElementById('statusbar'); if (!status) return; const pill = element('span', 'tp-pill'); crumb.append(pill); function mirror() { const text = status.textContent.trim(); pill.hidden = !text; pill.textContent = text; pill.className = 'tp-pill' + (/started/i.test(text) ? ' tp-good' : /stopp|offline|invalid|error/i.test(text) ? ' tp-warn' : ''); } new MutationObserver(mirror).observe(status, { childList: true, subtree: true, characterData: true }); mirror(); } // Top right: a sun / moon switch between the Black and White Dynamix themes. The choice is a // server setting, saved in two places so it holds on every page and in every browser: // 1. dynamix.cfg, through include/theme.php, which changes only the theme key; // 2. the Unraid API's own copy (customization.setTheme), when the API is running, because // the API can otherwise write its older value back. // The API rewrites dynamix.cfg itself a moment later, so theme.php is asked once more to check // that nothing went missing in between and to put back anything that did (issue #22). // The page reloads once the server has answered. Other Dynamix themes do not show the switch. function themeSwitch() { const root = document.documentElement; const dark = root.classList.contains('Theme--black'); if (!dark && !root.classList.contains('Theme--white')) return; // One round button showing the active theme: a moon while dark, a sun while light. const button = element('button', 'tp-theme-switch' + (dark ? ' tp-dark' : '')); button.type = 'button'; button.title = dark ? 'Switch to the light theme (for all browsers)' : 'Switch to the dark theme (for all browsers)'; button.setAttribute('aria-label', button.title); button.append(element('i', 'fa ' + (dark ? 'fa-moon-o' : 'fa-sun-o'))); // step "save" changes the theme; step "verify" puts back keys lost since "save". function saveConfig(theme, step) { const body = new URLSearchParams({ theme: theme, step: step, csrf_token: window.csrf_token || '' }); return fetch(PLUGIN_BASE + '/include/theme.php', { method: 'POST', credentials: 'same-origin', body: body }) .then(function (response) { return response.json().catch(function () { return { error: 'unexpected answer from the server, status ' + response.status }; }); }) .then(function (data) { if (data.error) throw new Error(data.error); if (data.restored) console.warn('Aurora Theme: put back ' + data.restored + ' dynamix.cfg settings lost while the theme changed'); }); } // The Unraid API keeps a copy of the theme; it is told too when it is reachable. function saveApi(theme) { if (!window.apolloClient || !window.gql) return Promise.resolve(false); const mutation = window.gql('mutation setTheme($theme: ThemeName!) { customization { setTheme(theme: $theme) { name } } }'); const call = window.apolloClient.mutate({ mutation: mutation, variables: { theme: theme } }).then(function () { return true; }, function (error) { console.warn('Aurora Theme: the Unraid API did not take the theme', error); return true; }); return Promise.race([call, new Promise(function (resolve) { setTimeout(function () { resolve(true); }, 4000); })]); } // Gives the API time to write its copy of dynamix.cfg before it is checked. function settle(apiCalled) { return new Promise(function (resolve) { setTimeout(resolve, apiCalled ? 1500 : 0); }); } let busy = false; button.addEventListener('click', function (event) { event.preventDefault(); event.stopPropagation(); if (busy) return; busy = true; const next = dark ? 'white' : 'black'; button.classList.toggle('tp-dark', !dark); button.classList.add('tp-busy'); button.setAttribute('aria-busy', 'true'); saveConfig(next, 'save') .then(function () { return saveApi(next); }) .then(settle) .then(function () { // The theme is saved by now: a failed check gets its own message, then the page // reloads with the new theme all the same. return saveConfig(next, 'verify').catch(function (error) { console.error('Aurora Theme:', error); return modal({ title: 'Theme changed, settings not confirmed', message: ['The theme was changed, but Aurora could not confirm that your other settings in dynamix.cfg are intact (' + error.message + ').', 'Please check Settings > Display Settings and your notification and parity settings.'], confirmLabel: 'OK', hideCancel: true }); }); }) .then(function () { location.reload(); }) .catch(function (error) { console.error('Aurora Theme:', error); busy = false; button.classList.remove('tp-busy'); button.removeAttribute('aria-busy'); button.classList.toggle('tp-dark', dark); modal({ title: 'Theme not changed', message: 'The server did not accept the change (' + error.message + '). You can still pick the theme under Settings > Display Settings.', confirmLabel: 'OK', hideCancel: true }); }); }); // The top right row is drawn by Unraid's profile component, which appears a moment after // the page; the switch goes in front of it and is put back if the component redraws. It is // never moved while the pointer is on it: moving it mid-click would swallow the click. let pointerOn = false; button.addEventListener('pointerenter', function () { pointerOn = true; }); button.addEventListener('pointerleave', function () { pointerOn = false; place(); }); function place() { const profile = document.getElementById('UserProfile'); if (!profile || pointerOn) return; if (profile.firstElementChild === button) return; if (profile.contains(button) && button.parentElement === profile) return; profile.prepend(button); } place(); new MutationObserver(place).observe(document.getElementById('header') || document.body, { childList: true, subtree: true }); } // Sidebar: which server this browser is connected to. Only its name; the address is in the // browser's own address bar (and the card's tooltip). function serverCard(info) { const menu = document.getElementById('menu'); if (!menu || !info.server || menu.querySelector('.tp-server')) return; const block = element('div', 'tp-server'); const card = element('div', 'tp-server-card'); card.title = location.host; card.append(element('b', '', info.server)); block.append(element('span', 'tp-server-label', 'Connected server'), card); menu.prepend(block); } function pageHeading(info) { const box = document.getElementById('displaybox'); if (!box || !info.tab || box.querySelector('.tp-heading')) return; const known = TABS[info.tab]; const heading = element('div', 'tp-heading'); // The Apps tab's search bar sits above the heading on phones (theme.css adds the space). if (document.querySelector('.searchAreaHolder')) heading.classList.add('tp-heading-below-search'); const text = element('div', 'tp-heading-text'); const eyebrow = info.subPage ? info.tab : known && known[0]; if (eyebrow) text.append(element('p', 'tp-eyebrow', eyebrow)); text.append(element('h1', '', info.subPage || info.tab)); if (known && !info.subPage) text.append(element('p', 'tp-subtitle', known[1])); heading.append(text); box.prepend(heading); } // Docker and VMs tabs with a folder plugin (FolderView3, FolderView Plus): a "beta" chip in the // top bar (next to the page heading where the top bar has no breadcrumb: top layout, narrow // windows). A click explains that their support is not finished yet. // With FolderView Plus and FolderView3 (or FolderView2) installed together, a warning instead, // also on the Dashboard and the Plugins page: Plus does not support it (it pauses its folders, // "safe mode", and asks to remove the other plugin), and Aurora then follows FolderView3. function folderViewBeta(info) { const view = window.auroraDockerView || (window.auroraFolderViewConflict ? { folderView: true, both: true, name: window.auroraFolderViewConflict } : null); if (!(view && view.folderView) || info.subPage) return; const both = Boolean(view.both); if (!(both ? ['Docker', 'VMs', 'Dashboard', 'Plugins'] : ['Docker', 'VMs']).includes(info.tab)) return; const name = view.name || 'FolderView'; const chip = function () { const button = element('button', 'tp-fv-beta' + (both ? ' tp-fv-conflict' : '')); button.type = 'button'; // The plugin's name with "support" (left out in a narrow top bar) and a small tag. const text = element('span', 'tp-fv-beta-text', both ? name + ' + Plus' : name); if (!both) text.append(element('span', 'tp-fv-beta-word', ' support')); if (both) { button.title = name + ' and FolderView Plus are both installed. This is not supported. Click for details.'; button.append(element('i', 'fa fa-exclamation-triangle'), text, element('span', 'tp-fv-beta-tag', 'Not supported')); } else { button.title = name + ' support is in beta and still has compatibility issues. Click for details.'; button.append(element('i', 'fa fa-info-circle'), text, element('span', 'tp-fv-beta-tag', 'Beta')); } button.addEventListener('click', function () { if (both) { modal({ title: name + ' and FolderView Plus are both installed', message: [ 'Running both folder plugins at the same time is not supported. FolderView Plus says so itself: while ' + name + ' is installed it pauses its own folders ("safe mode") and asks to remove the other plugin.', 'Aurora then follows ' + name + '. Keep the one you want to use and remove the other under Plugins.', ], confirmLabel: 'Open Plugins', cancelLabel: 'Close', }).then(function (open) { if (open) location.href = '/Plugins'; }); return; } modal({ title: name + ' support is in beta', message: [ 'Aurora works with FolderView3 and FolderView Plus, but their support is still in beta: some pages and settings can still show compatibility issues.', 'I am working on full support. If something looks wrong, please report it on GitHub (Settings > User Utilities > Aurora Theme has a form for it).', ], confirmLabel: 'OK', hideCancel: true, }); }); return button; }; const crumb = document.querySelector('#header .tp-crumb'); if (crumb && !crumb.querySelector('.tp-fv-beta')) crumb.append(chip()); const heading = document.querySelector('#displaybox .tp-heading-text'); if (heading && !heading.querySelector('.tp-fv-beta')) heading.append(chip()); } function summaryStrip(id, label, items) { let strip = document.getElementById(id); if (!strip) { strip = element('section', 'tp-summary'); strip.id = id; strip.setAttribute('aria-label', label); } // Only redrawn when a value changed: the callers run on every change of the lists and // tiles, and other scripts watching the page (FolderView Plus) react to each redraw. const signature = JSON.stringify(items); if (strip.tpSignature === signature) return strip; strip.tpSignature = signature; strip.replaceChildren(...items.map(function (item) { const cell = element('div', 'tp-summary-item'); const value = element('div', 'tp-summary-value' + (item.tone ? ' tp-' + item.tone : '')); value.append(element('span', '', item.value), element('span', 'tp-summary-detail', item.detail)); cell.append(element('div', 'tp-summary-label', item.label), value); return cell; })); return strip; } // Docker tab: totals, filter tabs and search above the container table, kept current as // Unraid refreshes the rows. // With FolderView installed: a switch between Aurora's view (columns, groups, filters) and // FolderView's folders, for the Docker and VMs tabs alike. Stored on the server for every // browser (include/docker-view.php); the page reloads to apply it. Null without FolderView. function folderViewSwitch() { if (!(window.auroraDockerView && window.auroraDockerView.folderView && PLUGIN_BASE) || folderViewPlus()) return null; const viewSwitch = element('div', 'tp-segment tp-view-switch'); viewSwitch.setAttribute('role', 'group'); viewSwitch.setAttribute('aria-label', 'List view'); [['aurora', 'Aurora'], ['folderview', 'FolderView3']].forEach(function (option) { const button = element('button', 'tp-tab', option[1]); button.type = 'button'; const active = (window.auroraDockerView.mode === 'aurora') === (option[0] === 'aurora'); button.setAttribute('aria-selected', String(active)); button.title = option[0] === 'aurora' ? 'Aurora\'s list: columns, groups, filters and search' : 'The FolderView3 plugin\'s folders'; const save = function () { button.disabled = true; const body = new URLSearchParams({ view: option[0], csrf_token: window.csrf_token || '' }); fetch(PLUGIN_BASE + '/include/docker-view.php', { method: 'POST', credentials: 'same-origin', body: body }) .then(function (response) { if (!response.ok) throw new Error('saving the view failed with status ' + response.status); location.reload(); }) .catch(function (error) { console.error('Aurora Theme view:', error); button.disabled = false; modal({ title: 'View not changed', message: 'The server did not accept the change.', confirmLabel: 'OK', hideCancel: true }); }); }; button.addEventListener('click', function () { if (active) return; if (option[0] !== 'folderview') { save(); return; } // FolderView's folders are fitted to the theme, but not all of the plugin is. modal({ title: 'Switch to FolderView3?', message: [ 'Your containers and VMs will be shown in FolderView3\'s folders, fitted to the Aurora look, and a folder opens with a single click.', 'Aurora doesn\'t support every feature of the FolderView3 plugin yet. Some of its options and layouts may look a little different here, or not appear at all.', 'You can switch back to Aurora\'s view at any time with the same switch.', ], cancelLabel: 'Stay with Aurora', confirmLabel: 'Switch to FolderView3', }).then(function (confirmed) { if (confirmed) save(); }); }); viewSwitch.append(button); }); return viewSwitch; } // VMs tab: the same switch, beside the page's title: the "Virtual Machines" tab, or the // title above the list when the page has no tabs (Display Settings). function vmViewSwitch() { const table = document.getElementById('kvm_table'); const viewSwitch = table && folderViewSwitch(); if (!viewSwitch) return; viewSwitch.classList.add('tp-title-switch'); const anchor = table.closest('.TableContainer') || table; let title = anchor.previousElementSibling; while (title && !title.matches('div.title')) title = title.previousElementSibling; const tabs = document.querySelector('#displaybox nav.tabs'); if (title) { const left = title.querySelector('span.left'); if (left) left.after(viewSwitch); else title.append(viewSwitch); } else if (tabs) { tabs.append(viewSwitch); } else { anchor.before(viewSwitch); } } function docker() { const table = document.getElementById('docker_containers'); const list = document.getElementById('docker_list'); if (!table || !list) return; // FolderView Plus has the Docker tab to itself, toolbar and counts included. It also watches // the whole page, so even the theme's summary above the table, redrawn on each change of the // list, kept it busy (the tab hung). The tab is left alone entirely. if (folderViewPlus()) return; const anchor = table.closest('.TableContainer') || table; const filters = [['all', 'All'], ['running', 'Running'], ['stopped', 'Stopped'], ['updates', 'Updates']]; let activeFilter = 'all'; // FolderView (folder.view, .view2, .view3) turns rows into folders and keeps the members of // closed folders inside the folder's row. Then the theme leaves the rows to it: no compact // columns, groups, filters or search (they would pull containers out of their folders), only // the summary above the table. const folderView = folderViewActive(); // Compact view: Unraid's ten columns in the order its table prints them. The view is only // offered while the table still has exactly these, so a changed Unraid release falls back to // the stock table instead of showing shuffled columns. const COLUMNS = ['name', 'version', 'network', 'ip', 'cport', 'ports', 'volumes', 'load', 'auto', 'uptime']; const headRow = table.querySelector('thead tr'); const canCompact = !folderView && Boolean(headRow) && headRow.children.length === COLUMNS.length; let compact = canCompact; try { compact = canCompact && localStorage.getItem('tft-docker-view') !== 'full'; } catch (error) { /* storage blocked */ } if (canCompact) { [...headRow.children].forEach(function (cell, index) { cell.classList.add('tp-c-' + COLUMNS[index]); }); [['webui', 'WebUI'], ['status', 'Status'], ['cpu', 'CPU'], ['mem', 'Memory'], ['actions', '']].forEach(function (extra) { headRow.append(element('th', 'tp-extra tp-c-' + extra[0], extra[1])); }); } // Adds the image name, a status cell and an actions cell to one of Unraid's rows. The actions // call the same functions as Unraid's own context menu. Rows are rebuilt on every refresh, // so this runs again for each new row. function decorate(row) { if (!canCompact || row.dataset.tpReady || row.children.length !== COLUMNS.length) return; row.dataset.tpReady = '1'; [...row.children].forEach(function (cell, index) { cell.classList.add('tp-c-' + COLUMNS[index]); }); const nameCell = row.querySelector('td.ct-name'); const trigger = nameCell.querySelector('span.hand[id]'); const nameLink = nameCell.querySelector('.appname'); const name = nameLink ? nameLink.textContent.trim() : ''; const id = trigger ? trigger.id : ''; const state = nameCell.querySelector('.state'); const running = Boolean(nameCell.querySelector('i.started')); const paused = Boolean(nameCell.querySelector('i.paused')); const repository = nameCell.querySelector('div.advanced a'); const tag = row.querySelector('td.updatecolumn .fa-info-circle'); const inner = nameCell.querySelector('.inner'); if (inner && repository) { const tagText = tag && tag.parentElement ? tag.parentElement.textContent.trim() : ''; inner.append(element('span', 'tp-image', repository.textContent.trim() + (tagText ? ':' + tagText : ''))); } // WebUI: Unraid fills in the real address only while the container runs; a stopped // container still has placeholders like [IP] and [PORT:8080] in it. const webui = contextArguments(trigger)[7] || ''; const webuiCell = element('td', 'tp-extra tp-c-webui'); if (running && /^https?:\/\/[^\s\[\]'"<>]+$/.test(webui)) { const link = webuiOpener(element('a', 'tp-webui'), webui); link.title = 'Open ' + webui; link.setAttribute('aria-label', 'Open the web interface of ' + name); link.append(element('i', 'fa fa-external-link')); webuiCell.append(link); } else { webuiCell.append(element('span', 'tp-webui-none', '—')); } // Phones show the addresses and mounts under the card (enhance.css), without an empty line // for a container that has none. Long paths may break after a "/" only, not inside a // folder name. row.querySelectorAll('td.tp-c-ports, td.tp-c-volumes').forEach(function (cell) { cell.classList.toggle('tp-c-empty', !cell.textContent.trim()); }); row.querySelectorAll('td.tp-c-volumes .docker_readmore').forEach(function (box) { const walker = document.createTreeWalker(box, NodeFilter.SHOW_TEXT); const texts = []; while (walker.nextNode()) texts.push(walker.currentNode); texts.forEach(function (node) { const parts = node.nodeValue.split(/(?<=\/)(?=.)/); if (parts.length < 2) return; const pieces = []; parts.forEach(function (part, index) { if (index) pieces.push(document.createElement('wbr')); pieces.push(document.createTextNode(part)); }); node.replaceWith(...pieces); }); }); const statusCell = element('td', 'tp-extra tp-c-status'); statusCell.append(element('span', 'tp-pill' + (running ? ' tp-good' : paused ? ' tp-warn' : ''), state ? state.textContent.trim() : '')); const actions = element('div', 'tp-actions'); const control = function (action) { return function () { if (typeof window.eventControl === 'function') window.eventControl({ action: action, container: id }, 'loadlist'); }; }; actions.append(iconButton('navicon', 'View ' + name + ' logs', function () { if (typeof window.openTerminal === 'function') window.openTerminal('docker', name, '.log'); })); if (paused) actions.append(iconButton('play', 'Resume ' + name, control('resume'))); else if (running) actions.append(iconButton('refresh', 'Restart ' + name, control('restart')), iconButton('stop', 'Stop ' + name, control('stop'), 'tp-danger')); else actions.append(iconButton('play', 'Start ' + name, control('start'), 'tp-primary')); actions.append(iconButton('folder-o', 'Group for ' + name, function (event) { event.stopPropagation(); store.openMenu(event.currentTarget, name); })); // Unraid's full menu (WebUI, console, edit, remove, ...) opens from the container icon; // the click is passed on with this button's position so the menu appears next to it. actions.append(iconButton('ellipsis-h', 'More actions for ' + name, function (event) { event.stopPropagation(); if (trigger) openMenuFrom(trigger, event.currentTarget); })); const actionsCell = element('td', 'tp-extra tp-c-actions'); actionsCell.append(actions); row.append(webuiCell, statusCell, actionsCell); } const toolbar = element('div', 'tp-toolbar'); const tabs = element('div', 'tp-tabs'); tabs.setAttribute('role', 'tablist'); tabs.setAttribute('aria-label', 'Filter containers'); const search = element('input', 'tp-search'); search.type = 'text'; search.placeholder = 'Search containers'; search.setAttribute('aria-label', 'Search containers'); const counts = {}; filters.forEach(function (filter) { const tab = element('button', 'tp-tab', filter[1] + ' '); tab.type = 'button'; tab.setAttribute('role', 'tab'); tab.dataset.filter = filter[0]; counts[filter[0]] = element('span', 'tp-tab-count', '0'); tab.append(counts[filter[0]]); tab.addEventListener('click', function () { activeFilter = filter[0]; render(); }); tabs.append(tab); }); const tools = element('div', 'tp-tools'); tools.append(search); if (canCompact) { const viewButton = element('button', 'tp-tab'); viewButton.type = 'button'; viewButton.setAttribute('aria-selected', 'true'); viewButton.addEventListener('click', function () { compact = !compact; try { localStorage.setItem('tft-docker-view', compact ? 'compact' : 'full'); } catch (error) { /* storage blocked */ } render(); }); tools.append(viewButton); toolbar.tpViewButton = viewButton; } // Unraid's Basic / Advanced switch, shown as a two-part control in the toolbar. The original // switch stays in the page (hidden) and is clicked for real, so Unraid's own script still // does the switching and remembers the choice. const nativeToggle = document.querySelector('.ToggleViewMode input.advancedview'); const nativeSwitch = nativeToggle && nativeToggle.parentElement.querySelector('.switch-button-background'); let modeButtons = []; if (nativeToggle && nativeSwitch) { const segment = element('div', 'tp-segment'); segment.setAttribute('role', 'group'); segment.setAttribute('aria-label', 'Detail level'); modeButtons = ['Basic', 'Advanced'].map(function (label, index) { const button = element('button', 'tp-tab', label); button.type = 'button'; button.addEventListener('click', function () { if (nativeToggle.checked !== Boolean(index)) nativeSwitch.click(); render(); }); segment.append(button); return button; }); tools.append(segment); nativeToggle.closest('.ToggleViewMode').classList.add('tp-native-hidden'); } if (toolbar.tpViewButton) tools.append(toolbar.tpViewButton); const newGroupButton = element('button', 'tp-tab', '+ Group'); newGroupButton.type = 'button'; newGroupButton.title = 'Create an application group'; newGroupButton.setAttribute('aria-selected', 'true'); newGroupButton.addEventListener('click', function () { store.create().then(function (group) { if (group) store.save(); }); }); if (!folderView) tools.append(newGroupButton); // With FolderView installed: Aurora's view or FolderView's folders (viewSwitch), beside the // "Docker Containers" title above the table; in the toolbar if there is none. const viewSwitch = folderViewSwitch(); if (viewSwitch) { let title = anchor.previousElementSibling; while (title && !title.matches('div.title')) title = title.previousElementSibling; if (title) { viewSwitch.classList.add('tp-title-switch'); const left = title.querySelector('span.left'); if (left) left.after(viewSwitch); else title.append(viewSwitch); } else { tools.prepend(viewSwitch); } } if (folderView) { tabs.hidden = true; search.hidden = true; } toolbar.append(tabs, tools); search.addEventListener('input', render); // ---- Application groups (groupStore): collapsible sections in the list, each with its // containers in Unraid's order. Changing the groups saves Unraid's order as the list now // shows it, so the containers start in the order they are listed. const UNGROUPED = '\u0000ungrouped'; // Unraid lists images no container uses as "(orphan image)" rows after the containers; the // Aurora view collects them in an "Orphans" section at the end, with a button removing all. const ORPHANS = '\u0000orphans'; const collapsed = collapsedState('tft-docker-collapsed'); const groupRows = new Map(); const store = groupStore('docker', render, function () { if (!folderView) saveUnraidOrder('docker', list); }); // Starts or stops several containers (or removes images, with key "image") one after // another, then lets Unraid reload the list once. function runAll(action, ids, key) { const jq = window.jQuery; if (!jq || !window.eventURL || !ids.length) return; jq('div.spinner.fixed').show('slow'); let chain = Promise.resolve(); ids.forEach(function (id) { chain = chain.then(function () { return new Promise(function (resolve) { jq.post(window.eventURL, { action: action, [key || 'container']: id }).always(resolve); }); }); }); chain.then(function () { if (typeof window.loadlist === 'function') window.loadlist(); else jq('div.spinner.fixed').hide('slow'); }); } function groupRow(key, group) { if (groupRows.has(key)) return groupRows.get(key); const onToggle = function () { collapsed.toggle(key); render(); }; const row = key === ORPHANS ? sectionHeader(store, null, { noun: 'orphan image', onToggle: onToggle, removeAll: removeImages, }) : sectionHeader(store, group, { noun: 'container', load: true, onToggle: onToggle, start: function (items) { runAll('start', items.map(function (item) { return item.id; })); }, stop: function (items) { runAll('stop', items.map(function (item) { return item.id; })); }, }); if (key === ORPHANS) row.classList.add('tp-group-orphans'); groupRows.set(key, row); return row; } // "Remove all" in the Orphans section: Unraid's own "remove_image" for each, after asking. function removeImages(items) { modal({ title: 'Remove ' + items.length + ' orphan ' + (items.length === 1 ? 'image' : 'images') + '?', message: 'No container uses ' + (items.length === 1 ? 'this image' : 'these images') + '. ' + (items.length === 1 ? 'It' : 'They') + ' will be deleted from Docker: ' + items.map(function (item) { return item.name; }).join(', ') + '.', confirmLabel: 'Remove all', danger: true, }).then(function (confirmed) { if (confirmed) runAll('remove_image', items.map(function (item) { return item.id; }), 'image'); }); } // Unraid's orphan image rows: the image's id (its icon's id), tags and icon. function orphanRows() { return [...list.querySelectorAll(':scope > tr > td span.hand[onclick*="addDockerImageContext"]')].map(function (trigger) { const row = trigger.closest('tr'); const tags = contextArguments(trigger)[1] || ''; const image = trigger.querySelector('img'); row.classList.add('tp-orphan'); return { row: row, id: trigger.id, name: tags.split(',').filter(function (tag) { return tag && tag !== ':'; }).join(', ') || trigger.id.replace(/^sha256:/, '').slice(0, 12), src: image && image.src, trigger: trigger, get text() { return row.textContent.toLowerCase(); }, running: false, paused: false, }; }); } // Text is only written when it changed: writing inside the list wakes the refresh observer. function setText(node, text) { if (node.textContent !== text) node.textContent = text; } // Puts the section headers and their containers in order at the top of the list: the groups // in their saved order, then "Ungrouped"; the containers in each section (and the whole list // when there are no groups) in Unraid's order. Runs after every refresh, and moves nothing // when the order is already right. function layoutGroups(rows, orphans) { const sections = store.groups.map(function (group) { return { key: 'g:' + group.name, group: group, title: group.name, items: rows.filter(function (item) { return group.containers.includes(item.name); }) }; }); const grouped = sections.length > 0; if (grouped) { const rest = rows.filter(function (item) { return !store.of(item.name); }); if (rest.length) sections.push({ key: UNGROUPED, group: null, title: 'Ungrouped', items: rest }); } if (orphans.length) sections.push({ key: ORPHANS, group: null, title: 'Orphans', items: orphans, orphans: true }); const wanted = new Set(sections.map(function (section) { return section.key; })); groupRows.forEach(function (row, key) { if (!wanted.has(key)) { row.remove(); groupRows.delete(key); } }); // Without groups the containers come first, as a plain list. const order = grouped ? [] : rows.map(function (item) { return item.row; }); sections.forEach(function (section, index) { const header = groupRow(section.key, section.group); header.tpItems = section.items; header.tpKey = section.key; section.index = index; section.collapsed = collapsed.has(section.key); if (section.orphans) { updateSectionHeader(header, section, { noun: 'orphan image', noRunning: true, app: function (item) { return { name: item.name, src: item.src, state: 'stopped', target: item.trigger }; }, }); order.push(header); section.items.forEach(function (item) { item.collapsed = section.collapsed; order.push(item.row); }); return; } updateSectionHeader(header, section, { noun: 'container', groupCount: store.groups.length, app: function (item) { const image = item.row.querySelector('td.ct-name img.img'); const trigger = item.row.querySelector('td.ct-name span.hand[id]'); return { name: item.name, src: image && image.src, state: item.paused ? 'paused' : item.running ? 'running' : 'stopped', update: item.update, target: trigger }; }, }); const load = sectionLoad(section.items); setText(header.querySelector('.tp-group-cpu'), load.cpu); setText(header.querySelector('.tp-group-mem'), load.mem); header.querySelector('.tp-group-mem').hidden = !load.mem; header.querySelector('.tp-group-load').hidden = !load.cpu; order.push(header); section.items.forEach(function (item) { item.collapsed = section.collapsed; order.push(item.row); }); }); if (!sections.length) return; order.forEach(function (node, index) { if (list.children[index] !== node) list.insertBefore(node, list.children[index] || null); }); } // CPU and memory of a section's containers added up ({ cpu: "3.20%", mem: "1.4GiB / 15.2GiB" }), from // the values Unraid writes into each row every few seconds ("1.5%", "464KiB / 15.2GiB"; the // decimal mark follows Display Settings). Empty while nothing in the section runs. In the // compact view they sit in the table's CPU and Memory columns (enhance.css). const UNITS = { B: 1, KIB: 1024, MIB: 1024 ** 2, GIB: 1024 ** 3, TIB: 1024 ** 4, KB: 1e3, MB: 1e6, GB: 1e9, TB: 1e12 }; const number = function (text) { return parseFloat(String(text).replace(',', '.')) || 0; }; // CPU values with two decimals ("0.00%", "1.50%") in the decimal mark Unraid uses for them // (Display Settings, Number format). let decimalMark = window.auroraDecimalMark === ',' ? ',' : '.'; function cpuText(value) { return value.toFixed(2).replace('.', decimalMark) + '%'; } // Unraid writes a container's CPU as "0%" or "1.5%" every few seconds; it is rewritten with // two decimals like the groups' sums. Only text that differs is written, so the list's // observer settles after one more pass. function formatCpu(rows) { rows.forEach(function (item) { const cell = item.row.querySelector('[class^="cpu-"]'); if (!cell) return; const text = cell.textContent.trim(); if (!/^[\d.,]+%$/.test(text)) return; if (text.includes(',')) decimalMark = ','; const formatted = cpuText(number(text)); if (text !== formatted) cell.textContent = formatted; }); } function sectionLoad(items) { let cpu = 0; let bytes = 0; let limit = 0; let running = 0; const toBytes = function (text) { const value = String(text).trim().match(/^([\d.,]+)\s*([KMGT]?i?B)$/i); return value ? number(value[1]) * (UNITS[value[2].toUpperCase()] || 1) : 0; }; items.forEach(function (item) { if (!item.running) return; running++; const cpuCell = item.row.querySelector('[class^="cpu-"]'); const memCell = item.row.querySelector('[class^="mem-"]'); if (cpuCell) cpu += number(cpuCell.textContent); // "464KiB / 15.2GiB": used and the container's limit (the server's memory unless set). const parts = memCell ? memCell.textContent.split('/') : []; bytes += toBytes(parts[0] || ''); limit = Math.max(limit, toBytes(parts[1] || '')); }); if (!running) return { cpu: '', mem: '' }; // Written like Unraid's own values: "2.3MiB / 15.2GiB". const size = function (value) { const unit = ['TiB', 'GiB', 'MiB', 'KiB'].find(function (name) { return value >= UNITS[name.toUpperCase()]; }) || 'B'; const amount = value / UNITS[unit.toUpperCase()]; return (amount >= 100 || unit === 'B' ? String(Math.round(amount)) : amount.toFixed(1).replace('.', decimalMark)) + unit; }; // Nothing to add up (Unraid shows "0 / 0" while it has no reading): no memory total. return { cpu: cpuText(cpu), mem: bytes ? size(bytes) + (limit ? ' / ' + size(limit) : '') : '' }; } // FolderView: the list stays in FolderView's order (folders and containers where they were // dragged to, an open folder's containers under it). A container right after a folder (or // after an open folder's containers) is marked, so enhance.css can set the folder apart // from it, and a long folder name gets its full name as the tooltip. function arrangeFolders() { let afterFolder = false; [...list.children].forEach(function (row) { const isFolder = row.classList.contains('folder'); const inFolder = row.classList.contains('folder-element'); const loose = !isFolder && !inFolder && Boolean(row.querySelector(':scope > td.ct-name')); row.classList.toggle('tp-after-folders', loose && afterFolder); if (isFolder || inFolder) afterFolder = true; else if (loose) afterFolder = false; const folderName = isFolder && row.querySelector('.folder-appname'); if (folderName && folderName.title !== folderName.textContent.trim()) folderName.title = folderName.textContent.trim(); }); } function describe(row) { const name = row.querySelector('td.ct-name'); const update = row.querySelector('td.updatecolumn'); const appName = name.querySelector('.appname'); const trigger = name.querySelector('span.hand[id]'); return { row: row, name: appName ? appName.textContent.trim() : '', id: trigger ? trigger.id : '', collapsed: false, // Read only when searched or filtered (Aurora's view): the whole row's text is costly to // read again on every change of a long list. get text() { return row.textContent.toLowerCase(); }, running: Boolean(name.querySelector('i.started')), paused: Boolean(name.querySelector('i.paused')), // "update ready" (and "rebuild ready") carry a flash icon; "not available" (no update // information, e.g. a container without a template) is orange too, but not an update. update: Boolean(update && (update.querySelector('.orange-text .fa-flash') || /update ready/i.test(update.textContent))), }; } function render() { if (list.tpSorting) return; // Containers only: a FolderView folder row has a name cell of its own (td.folder-name). const rows = [...list.querySelectorAll('td.ct-name:not(.folder-name)')].map(function (cell) { return describe(cell.parentElement); }); if (!rows.length) return; formatCpu(rows); // Long names end in "…" (enhance.css); the whole name is the tooltip. A container with // an update gets the orange dot on its icon. rows.forEach(function (item) { const name = item.row.querySelector('td.ct-name .appname'); if (name && name.title !== item.name) name.title = item.name; item.row.classList.toggle('tp-update', item.update); }); const orphans = folderView ? [] : orphanRows(); if (folderView) { arrangeFolders(); } else { rows.forEach(function (item) { decorate(item.row); }); layoutGroups(rows, orphans); } table.classList.toggle('tp-compact', compact); table.classList.toggle('tp-advanced', Boolean(nativeToggle && nativeToggle.checked)); modeButtons.forEach(function (button, index) { button.setAttribute('aria-selected', String(Boolean(nativeToggle.checked) === Boolean(index))); }); if (toolbar.tpViewButton) { toolbar.tpViewButton.textContent = compact ? 'All columns' : 'Compact view'; toolbar.tpViewButton.title = compact ? 'Show every column of Unraid\'s table' : 'Show the compact container list'; } const running = rows.filter(function (item) { return item.running; }).length; const paused = rows.filter(function (item) { return item.paused; }).length; const updates = rows.filter(function (item) { return item.update; }).length; const stopped = rows.length - running - paused; const strip = summaryStrip('tp-docker-summary', 'Docker summary', [ { label: 'Containers', value: rows.length, detail: 'total' }, { label: 'Running', value: running, detail: paused ? paused + ' paused' : 'started', tone: 'good' }, { label: 'Stopped', value: stopped, detail: 'not running' }, { label: 'Updates available', value: updates, detail: updates === 1 ? 'image' : 'images', tone: updates ? 'warn' : '' }, ]); if (!strip.isConnected) anchor.before(strip); if (!toolbar.isConnected) anchor.before(toolbar); counts.all.textContent = rows.length; counts.running.textContent = running; counts.stopped.textContent = stopped; counts.updates.textContent = updates; tabs.querySelectorAll('.tp-tab').forEach(function (tab) { tab.setAttribute('aria-selected', String(tab.dataset.filter === activeFilter)); }); const query = search.value.trim().toLowerCase(); if (folderView) return; rows.forEach(function (item) { const matchesFilter = activeFilter === 'all' || (activeFilter === 'running' && item.running) || (activeFilter === 'stopped' && !item.running && !item.paused) || (activeFilter === 'updates' && item.update); item.visible = matchesFilter && item.text.includes(query); item.row.classList.toggle('tp-filtered-out', !item.visible || item.collapsed); }); // Orphan images are no containers: they show with "All" only. orphans.forEach(function (item) { item.visible = activeFilter === 'all' && item.text.includes(query); item.row.classList.toggle('tp-filtered-out', !item.visible || item.collapsed); }); // A section whose containers are all filtered away is hidden with them; an empty group // stays visible so it can be filled, unless a filter or search is active. groupRows.forEach(function (header) { const items = header.tpItems || []; const filtering = activeFilter !== 'all' || query !== ''; const show = items.length ? items.some(function (item) { return item.visible; }) : !filtering; header.classList.toggle('tp-filtered-out', !show); }); } // FolderView: a click anywhere on a folder's row opens or closes it, its name included // (FolderView only does that with the arrow button). Its icon keeps FolderView's menu (Edit, // Start, ...), and the preview icons, buttons and switches keep doing their own thing. if (folderView) { list.addEventListener('click', function (event) { const row = event.target.closest('tr.folder'); if (!row || row.parentElement !== list || event.button !== 0) return; if (event.target.closest('button, input, select, label, span.hand, .folder-preview, .switch-button-background')) return; const arrow = row.querySelector('button.folder-dropdown'); if (!arrow) return; event.preventDefault(); event.stopPropagation(); arrow.click(); }, true); } // Status under the table header while Unraid asks Docker for the list; with many containers // that can take a while, and a longer wait gets a message saying so. const loading = element('div', 'tp-docker-loading'); loading.setAttribute('role', 'status'); const loadingText = element('span', 'tp-docker-loading-text', 'Loading containers…'); loading.append(element('span', 'tp-loader'), loadingText); table.after(loading); // Unraid's own spinner would show over it after half a second (see enhance.css). document.body.classList.add('tp-docker-loading-on'); const loadingTimers = [ [3000, 'Still loading. Docker is taking a moment to list the containers.'], [10000, 'Docker has not answered yet. The service may still be starting, or a container is busy.'], ].map(function (step) { return setTimeout(function () { loadingText.textContent = step[1]; }, step[0]); }); // The list is ready once Unraid replaced its empty placeholder row with containers or with // its "No Docker containers installed" row. function markReady() { if (list.classList.contains('tp-ready') || !(list.querySelector('td.ct-name') || list.textContent.trim())) return; list.classList.add('tp-ready'); loadingTimers.forEach(clearTimeout); loading.remove(); document.body.classList.remove('tp-docker-loading-on'); } // Rendered straight away when Unraid inserts its rows, before the browser paints them, so // the list never shows ungrouped. render() only moves or writes what changed, so its own // changes settle after one more pass. The list is shown once it is laid out (see // #docker_list.tp-ready in enhance.css, which also reveals it by itself after two seconds). // With FolderView, which builds its folders in many small steps and has nothing for the // theme to group, render() waits until the list has been still for a moment (debounce): // running it on every step made a list of 300 containers settle half a second later. const update = function () { render(); markReady(); }; new MutationObserver(folderView ? debounce(update) : update).observe(list, { childList: true, subtree: true }); roomToDragToTop(list); if (!folderView) { store.load(); dropIntoSections(list, store, function (row) { const name = row.querySelector('td.ct-name .appname'); return name ? name.textContent.trim() : ''; }, UNGROUPED, render); } else if (window.jQuery) { // FolderView's list is dragged by Unraid's own drag and drop too; it is left alone meanwhile. window.jQuery(list).on('sortstart', function () { list.tpSorting = true; }); window.jQuery(list).on('sortstop', function () { list.tpSorting = false; setTimeout(render); }); } render(); markReady(); } // Dashboard: uptime, CPU, memory and array at a glance, read from Unraid's own tiles and // refreshed whenever Unraid updates those values. function dashboardSummary() { const grid = document.querySelector('#displaybox div.frame'); if (!grid || !document.getElementById('db_box1')) return; const text = function (selector) { const node = document.querySelector(selector); return node ? node.textContent.replace(/\s+/g, ' ').trim() : ''; }; const arrayHeader = document.querySelector('#array_list .tile-header-left .section > span'); const memoryInfo = [...document.querySelectorAll('.head_info')].map(function (node) { return node.textContent; }).find(function (value) { return /Memory:/.test(value); }) || ''; function render() { const uptime = text('span.uptime'); const uptimeParts = uptime.match(/^(\d+\s+\w+),?\s*(.*)$/); const cpu = text('span.cpu.load'); const memory = text('span.sys0_.load'); const memorySize = (memoryInfo.match(/Memory:\s*([\d.,]+\s*\w+)/) || [])[1] || ''; const array = arrayHeader ? arrayHeader.textContent.replace(/\s+/g, ' ').trim() : ''; const arrayParts = array.match(/^(.*?)\s+used of\s+(.*?)\s*\(([\d.,]+)\s*%\)/); const items = []; if (uptime) items.push({ label: 'Uptime', value: uptimeParts ? uptimeParts[1] : uptime, detail: uptimeParts ? uptimeParts[2] : '' }); if (cpu) items.push({ label: 'CPU load', value: cpu, detail: 'overall', tone: 'accent' }); if (memory) items.push({ label: 'Memory', value: memory, detail: memorySize ? 'of ' + memorySize : 'in use', tone: 'accent' }); if (arrayParts) items.push({ label: 'Array', value: arrayParts[3] + '%', detail: arrayParts[1] + ' of ' + arrayParts[2], tone: 'good' }); if (!items.length) return; const strip = summaryStrip('tp-dashboard-summary', 'Server summary', items); if (!strip.isConnected) grid.before(strip); } const watch = new MutationObserver(debounce(render)); ['span.uptime', 'span.cpu.load', 'span.sys0_.load'].forEach(function (selector) { const node = document.querySelector(selector); if (node) watch.observe(node, { childList: true, characterData: true, subtree: true }); }); if (arrayHeader) watch.observe(arrayHeader, { childList: true, characterData: true, subtree: true }); render(); } // Dashboard load charts are drawn by SmoothieCharts on a canvas, so CSS cannot colour them. // Their options are adjusted once: theme colours, a thicker smooth line with a soft fill. function dashboardCharts() { const styles = getComputedStyle(document.documentElement); const token = function (name, fallback) { return styles.getPropertyValue(name).trim() || fallback; }; const primary = token('--tp-primary', '#4f8bff'); const second = '#d97706'; const grid = token('--tp-border', '#232833'); const muted = token('--tp-muted', '#8b93a5'); const soft = function (colour, alpha) { const hex = colour.replace('#', ''); if (!/^[0-9a-f]{6}$/i.test(hex)) return 'transparent'; return 'rgba(' + [0, 2, 4].map(function (index) { return parseInt(hex.slice(index, index + 2), 16); }).join(',') + ',' + alpha + ')'; }; [window.cpuchart, window.netchart].forEach(function (chart) { if (!chart || !chart.options || !Array.isArray(chart.seriesSet)) return; chart.options.interpolation = 'bezier'; if (chart.options.grid) { chart.options.grid.strokeStyle = grid; chart.options.grid.fillStyle = 'transparent'; chart.options.grid.borderVisible = false; } if (chart.options.labels) { chart.options.labels.fillStyle = muted; chart.options.labels.fontFamily = token('--tp-font-body', 'sans-serif'); } chart.seriesSet.forEach(function (entry, index) { const colour = index === 0 ? primary : second; entry.options.strokeStyle = colour; entry.options.lineWidth = 2; entry.options.fillStyle = chart.seriesSet.length === 1 ? soft(colour, 0.12) : undefined; }); }); } // Dashboard, processor tile: Unraid fills it from the Unraid API's live feed only, so it shows // 0% and N/A whenever that service is not running. When no figures have come after a few // seconds, the plugin reads the processor counters itself and feeds them into the same // functions the live feed uses. It steps back as soon as the API delivers. function dashboardCpu() { const chart = window.cpuchart; if (!chart || typeof window.updateCPUBarCharts !== 'function' || !document.getElementById('cpu-total-power') || !PLUGIN_BASE) return; const URL_CPU = PLUGIN_BASE + '/include/cpu.php'; let previous = null; let ours = null; let timer = null; let busy = false; const apiDelivers = function () { const data = chart.customData && chart.customData.cpuData; return Boolean(data) && data !== ours; }; const percent = function (from, to) { const all = to[1] - from[1]; return all > 0 ? Math.max(0, Math.min(100, (to[0] - from[0]) / all * 100)) : 0; }; const setValue = function (node, text) { // "Total Power: N/A" keeps its icon and (translated) label; only the value changes. const last = node.lastChild; if (last && last.nodeType === Node.TEXT_NODE && last.textContent.includes(':')) { last.textContent = last.textContent.replace(/:.*$/, ': ' + text); } else { node.textContent = text; } }; function show(sample) { if (!previous || !sample.total || !previous.total) return; const seconds = sample.t - previous.t; const cpus = sample.cpus.map(function (counters, index) { const before = previous.cpus[index] || counters; const all = counters[1] - before[1]; return { percentTotal: percent(before, counters), percentUser: all > 0 ? Math.max(0, Math.min(100, (counters[2] - before[2]) / all * 100)) : 0, }; }); ours = { percentTotal: percent(previous.total, sample.total), cpus: cpus }; chart.customData = chart.customData || {}; chart.customData.cpuData = ours; chart.customData.cpuLoad = Math.floor(ours.percentTotal); if (typeof window.addChartCpu === 'function') window.addChartCpu(chart.customData.cpuLoad); window.updateCPUBarCharts(); // Package power from the energy counters, and package temperatures, where the hardware has them. if (window.cpupower) return; if (seconds > 0 && sample.energy.length && sample.energy.length === previous.energy.length) { const watts = sample.energy.map(function (counter, index) { let used = counter[0] - previous.energy[index][0]; if (used < 0) used += counter[1]; return used / seconds / 1e6; }); setValue(document.getElementById('cpu-total-power'), watts.reduce(function (sum, value) { return sum + value; }, 0).toFixed(2) + ' W'); watts.forEach(function (value, index) { const node = document.getElementById('cpu-power' + index); if (node) node.textContent = value.toFixed(2) + ' W'; }); } sample.temps.forEach(function (celsius, index) { const node = document.getElementById('cpu-temp' + index); if (!node || celsius === null) return; const fahrenheit = window.tempunit === 'F'; node.textContent = Math.round(fahrenheit ? celsius * 9 / 5 + 32 : celsius) + ' °' + (fahrenheit ? 'F' : 'C'); }); } function poll() { if (apiDelivers()) { clearInterval(timer); return; } if (busy || document.hidden) return; busy = true; fetch(URL_CPU, { credentials: 'same-origin', cache: 'no-store' }) .then(function (response) { if (!response.ok) throw new Error('processor figures failed with status ' + response.status); return response.json(); }) .then(function (sample) { if (apiDelivers()) return; show(sample); previous = sample; }) .catch(function (error) { clearInterval(timer); console.error('Aurora Theme processor figures:', error); }) .finally(function () { busy = false; }); } setTimeout(function () { if (apiDelivers()) return; poll(); timer = setInterval(poll, 1500); }, 4000); } // Docker tab and Dashboard: the container list is loaded again when Unraid's record of image // updates changes while the page is open (include/docker-updates.php), so a container updated // in the background (CA Auto Update, "Update All" in another tab, the scheduled check) no // longer shows "update ready". Updates started on the page itself already reload the list // when their dialog is closed; the record is read again after every list load, so those do // not load it twice. Checked every 20 seconds while the page is visible, and when it is // shown again. // Never while a check or an update is still running (the server's "busy", or "checking..." // in the list after "Check for Updates" here): the list would show half the results, and // Unraid's list code running beside the check could save the old results over the new ones. // It is then asked again after a few seconds. function dockerUpdateWatch() { const list = document.getElementById('docker_list') || document.getElementById('docker_view'); if (!list || typeof window.loadlist !== 'function') return; const url = PLUGIN_BASE + '/include/docker-updates.php'; let known = null; let busy = false; let soon = 0; function read() { return fetch(url, { credentials: 'same-origin', cache: 'no-store' }) .then(function (response) { return response.ok ? response.json() : {}; }) .then(function (data) { return typeof data.stamp === 'string' ? data : null; }); } function waiting() { if (list.querySelector('.updatecolumn .fa-spin') || list.tpSorting) return true; const dialog = document.querySelector('.sweet-alert.showSweetAlert, .sweet-alert.visible'); return Boolean(dialog && dialog.offsetParent !== null); } function check() { if (busy || document.hidden) return; busy = true; read() .then(function (data) { if (!data) return; if (known === null || data.stamp === known) { known = data.stamp; return; } if (data.busy || waiting()) { clearTimeout(soon); soon = setTimeout(check, 6000); return; } known = data.stamp; window.loadlist(); }) .catch(function () {}) .finally(function () { busy = false; }); } if (window.jQuery) { window.jQuery(document).on('ajaxComplete', function (event, xhr, settings) { if (/DockerContainers\.php|DashboardApps\.php/.test((settings && settings.url) || '')) { read().then(function (data) { if (data) known = data.stamp; }).catch(function () {}); } }); } check(); setInterval(check, 20000); document.addEventListener('visibilitychange', function () { if (!document.hidden) check(); }); } // Dashboard, Docker tile: an orange dot on the icon of every container with an update ready, // running or not (issue #24). Unraid redraws the tile's chips on each refresh. function dashboardUpdates() { const view = document.getElementById('docker_view'); // FolderView Plus marks updates on its own tile. if (!view || folderViewPlus()) return; const mark = function () { view.querySelectorAll('span.outer.apps').forEach(function (chip) { // A closed FolderView folder keeps its containers' chips in a hidden store; they are // FolderView's and stay as they are. if (chip.closest('.folder-storage')) return; const update = chipUpdate(chip); if (chip.classList.contains('tp-update') !== update) chip.classList.toggle('tp-update', update); // The tooltip is only taken away again if it is ours. const hand = chip.querySelector(':scope > span.hand'); if (hand && update && !hand.title) hand.title = 'Update available'; else if (hand && !update && hand.title === 'Update available') hand.removeAttribute('title'); }); }; // Not debounced: marked before the next paint, so a stopped container with an update that // Unraid has just hidden (enhance.css shows it again) does not flicker. mark(); new MutationObserver(mark).observe(view, { childList: true, subtree: true }); } // Dashboard, Docker tile: a click on a container opens a dialog with its state and everything // Unraid's small context menu offers, as large buttons. The buttons call Unraid's own functions. function dashboardContainers() { const view = document.getElementById('docker_view'); // FolderView Plus handles clicks on its tile itself; Unraid's own menu stays. if (!view || folderViewPlus()) return; const safeUrl = function (url) { return /^https?:\/\//i.test(url || '') ? url : ''; }; function open(chip) { // Only the chip's own icon: a FolderView folder holds its containers' chips inside it, and // a folder keeps its own menu (Expand, Stop, ...). if (chip.matches('[class*="folder-"]')) return false; const trigger = chip.querySelector(':scope > span.hand[onclick*="addDockerContainerContext"]'); const args = contextArguments(trigger); if (args.length < 11 || typeof window.eventControl !== 'function') return false; const ct = { name: args[0], image: args[1], template: args[2], started: args[3] === '1', paused: args[4] === '1', update: args[5], autostart: args[6] === 'true', webui: safeUrl(args[7]), tswebui: safeUrl(args[8]), shell: args[9], id: args[10], support: safeUrl(args[11]), project: safeUrl(args[12]), registry: safeUrl(args[13]), donate: safeUrl(args[14]), readme: safeUrl(args[15]), }; const state = ct.started ? (ct.paused ? 'paused' : 'running') : 'stopped'; const previousFocus = document.activeElement; const backdrop = element('div', 'tp-modal-backdrop'); const box = element('div', 'tp-modal tp-ct-modal tp-ct-' + state); box.setAttribute('role', 'dialog'); box.setAttribute('aria-modal', 'true'); box.setAttribute('aria-labelledby', 'tp-ct-title'); const head = element('div', 'tp-ct-head'); const icon = element('div', 'tp-ct-icon'); const picture = trigger.querySelector('img, i'); if (picture) icon.append(picture.cloneNode(true)); const text = element('div', 'tp-ct-text'); const title = element('h2', 'tp-modal-title', ct.name); title.id = 'tp-ct-title'; const meta = element('div', 'tp-ct-meta'); meta.append(element('span', 'tp-ct-status', { running: 'Running', paused: 'Paused', stopped: 'Stopped' }[state])); meta.append(element('span', 'tp-ct-tag', ct.autostart ? 'Autostart on' : 'Autostart off')); if (ct.update === '1') meta.append(element('span', 'tp-ct-tag tp-ct-update', 'Update available')); text.append(title, meta); const closeButton = element('button', 'tp-ct-close'); closeButton.type = 'button'; closeButton.setAttribute('aria-label', 'Close'); closeButton.append(element('i', 'fa fa-times')); head.append(icon, text, closeButton); box.append(head); function close() { document.removeEventListener('keydown', onKey, true); backdrop.remove(); if (previousFocus && previousFocus.focus) previousFocus.focus(); } function button(iconName, label, run, kind) { const node = element('button', 'tp-ct-action' + (kind ? ' tp-ct-' + kind : '')); node.type = 'button'; node.append(element('i', 'fa fa-' + iconName), element('span', '', label)); node.addEventListener('click', function () { close(); run(); }); return node; } function link(iconName, label, url, webui) { const node = element('a', 'tp-ct-action tp-ct-link'); if (webui) webuiOpener(node, url); else { node.href = url; node.target = '_blank'; node.rel = 'noopener noreferrer'; } node.append(element('i', 'fa fa-' + iconName), element('span', '', label)); node.addEventListener('click', close); return node; } const control = function (action) { return function () { window.eventControl({ action: action, container: ct.id }, 'loadlist'); }; }; const call = function (name) { const args = [].slice.call(arguments, 1); return function () { if (typeof window[name] === 'function') window[name].apply(window, args); }; }; const primary = element('div', 'tp-ct-primary'); if (state === 'running') { if (ct.webui) primary.append(link('globe', 'Open WebUI', ct.webui, true)); primary.append(button('stop', 'Stop', control('stop'), 'danger-soft'), button('refresh', 'Restart', control('restart')), button('pause', 'Pause', control('pause'))); } else if (state === 'paused') { primary.append(button('play', 'Resume', control('resume'), 'go'), button('refresh', 'Restart', control('restart')), button('stop', 'Stop', control('stop'), 'danger-soft')); } else { primary.append(button('play', 'Start', control('start'), 'go')); } const firstPrimary = primary.firstElementChild; if (firstPrimary) firstPrimary.classList.add('tp-ct-main'); box.append(primary); const tools = element('div', 'tp-ct-grid'); if (state === 'running') tools.append(button('terminal', 'Console', call('openTerminal', 'docker', ct.name, ct.shell))); tools.append(button('align-left', 'Logs', call('openTerminal', 'docker', ct.name, '.log'))); if (ct.update === '1') tools.append(button('cloud-download', 'Update', call('updateContainer', ct.name), 'accent')); if (ct.template) tools.append(button('pencil', 'Edit', call('editContainer', ct.name, ct.template))); tools.append(button('trash', 'Remove', call('rmContainer', ct.name, ct.image, ct.id), 'danger')); box.append(element('h3', 'tp-ct-section', 'Manage'), tools); const links = element('div', 'tp-ct-grid'); if (state === 'running' && ct.tswebui) links.append(link('globe', 'Tailscale WebUI', ct.tswebui, true)); if (ct.readme) links.append(link('book', 'Read me first', ct.readme)); if (ct.project) links.append(link('life-ring', 'Project page', ct.project)); if (ct.support) links.append(link('question-circle', 'Support', ct.support)); if (ct.registry) links.append(link('info-circle', 'More info', ct.registry)); if (ct.donate) links.append(link('heart', 'Donate', ct.donate)); if (links.children.length) box.append(element('h3', 'tp-ct-section', 'Links'), links); function onKey(event) { if (event.key === 'Escape') { event.stopPropagation(); close(); } else if (event.key === 'Tab') { const stops = [...box.querySelectorAll('button, a[href]')]; const index = stops.indexOf(document.activeElement); event.preventDefault(); stops[(index + (event.shiftKey ? stops.length - 1 : 1) + stops.length) % stops.length].focus(); } } closeButton.addEventListener('click', close); backdrop.addEventListener('click', function (event) { event.stopPropagation(); if (event.target === backdrop) close(); }); document.addEventListener('keydown', onKey, true); backdrop.append(box); document.body.append(backdrop); (firstPrimary || closeButton).focus(); return true; } // FolderView folder: a click opens or closes it in place (FolderView's own expandFolderDocker); // its menu (Stop all, Edit, ...) moves to the right click or a long press. const folderId = function (chip) { const hand = chip.matches('[class*="folder-docker"]') && chip.querySelector(':scope > span.hand[id^="folder-id-"]'); return hand ? hand.id.slice('folder-id-'.length) : ''; }; // The menu is shown by Unraid's menu script on the click that builds it, so the right click // sends the folder icon a click of its own, which the handler below lets through. let menuClick = false; view.addEventListener('contextmenu', function (event) { const chip = event.target.closest('span.outer.apps'); const hand = chip && folderId(chip) && chip.querySelector(':scope > span.hand'); if (!hand) return; event.preventDefault(); menuClick = true; hand.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window, clientX: event.clientX, clientY: event.clientY })); menuClick = false; }); // Captured before Unraid's own handler, which would open the small context menu instead. view.addEventListener('click', function (event) { const chip = event.target.closest('span.outer.apps'); if (!chip || event.button !== 0 || menuClick) return; const id = folderId(chip); if (id && typeof expandFolderDocker === 'function') { event.preventDefault(); event.stopPropagation(); expandFolderDocker(id); return; } if (open(chip)) { event.preventDefault(); event.stopPropagation(); } }, true); } // Dashboard, Processor tile: instead of a "Physical CPU n Power: ... Temperature: ..." line // and a full-width row for every core, the cores are a grid of small tiles (number, load, bar) // and power and temperature are chips. Unraid's own elements stay in place and keep being // updated by Unraid; package lines without readings (N/A, -1) are hidden. function dashboardCores() { const total = document.getElementById('cpu-total-power'); const tile = total && total.closest('tbody'); if (!tile || tile.classList.contains('tp-cpu-tile')) return; tile.classList.add('tp-cpu-tile'); const missing = function (text) { return !text.trim() || /N\/A|^\s*-/.test(text); }; // A chip that shows the value part ("12.3 W") of a source element and hides when there is none. const chip = function (icon, title, source, valueOf) { const node = element('span', 'tp-cpu-chip'); const glyph = element('i', 'fa ' + icon); glyph.setAttribute('aria-hidden', 'true'); const value = element('span', 'tp-cpu-chip-value'); node.title = title; node.append(glyph, value); const sync = function () { const text = valueOf(source.textContent); node.hidden = missing(text); if (value.textContent !== text) value.textContent = text; }; new MutationObserver(sync).observe(source, { childList: true, characterData: true, subtree: true }); sync(); return node; }; const afterColon = function (text) { return text.includes(':') ? text.slice(text.lastIndexOf(':') + 1).trim() : text.trim(); }; // Summary chips: total power and, with one processor, its temperature. const stats = element('div', 'tp-cpu-stats'); stats.append(chip('fa-bolt', 'Total power', total, afterColon)); const temp0 = document.getElementById('cpu-temp0'); const headTemp = temp0 && temp0.closest('.head_info'); if (headTemp) stats.append(chip('fa-thermometer-half', 'Temperature', temp0, function (text) { return text.trim(); })); const headInfo = total.closest('.head_info'); if (headInfo) { headInfo.parentNode.insertBefore(stats, headInfo); headInfo.classList.add('tp-cpu-source'); if (headTemp) headTemp.classList.add('tp-cpu-source'); } // Per processor package: a short "CPU n" line with power and temperature chips. tile.querySelectorAll('span.cpu_open [id^="cpu-power"]').forEach(function (power) { const index = power.id.replace('cpu-power', ''); const line = power.closest('span.cpu_open'); const row = line.closest('tr'); const temp = document.getElementById('cpu-temp' + index); const chips = [chip('fa-bolt', 'Power', power, function (text) { return text.trim(); })]; if (temp && line.contains(temp)) chips.push(chip('fa-thermometer-half', 'Temperature', temp, function (text) { return text.trim(); })); [...line.childNodes].forEach(function (node) { node.remove(); }); const hidden = element('span', 'tp-cpu-source'); hidden.append(power); if (temp && chips.length > 1) hidden.append(temp); line.append(element('span', 'tp-cpu-pkg-name', 'CPU ' + index), ...chips, hidden); if (row) row.classList.add('tp-cpu-pkg'); const update = function () { if (row) row.classList.toggle('tp-cpu-pkg-empty', chips.every(function (node) { return node.hidden; })); }; chips.forEach(function (node) { new MutationObserver(update).observe(node, { attributes: true, attributeFilter: ['hidden'] }); }); update(); }); tile.querySelectorAll('td.thin').forEach(function (cell) { cell.closest('tr').classList.add('tp-cpu-gap'); }); // Core rows: "CPU 3" / "CPU 0 - HT 12" becomes "3" / "0 · 12", the full text stays as a tooltip. // Intel hybrid CPUs add "(P-Core)" / "(E-Core)"; that becomes a small "P" / "E" badge, as the // written-out type made the tile wider than its column. tile.querySelectorAll('tr.cpu_open').forEach(function (row) { row.classList.add('tp-core'); const label = row.querySelector('span.w26'); if (!label) return; const full = label.textContent.replace(/\s+/g, ' ').trim(); const match = full.match(/^CPU\s+(\d+)\s*(\([^)]*\))?\s*(?:-\s*HT\s+(\d+))?/); if (!match) return; row.title = full; label.textContent = match[1] + (match[3] ? ' · ' + match[3] : ''); if (match[2]) { const type = match[2].replace(/[()]/g, '').trim(); const badge = element('span', 'tp-core-type', type.split('-')[0] || type); badge.title = type; label.append(badge); } if (match[3]) row.classList.add('tp-core-pair'); }); } // Dashboard, System tile: Unraid draws its four rings in grey from a colour list in the page // script. The list gets the theme's colours (RAM: system, free, VMs, Docker, ...), and the // boot, log and Docker rings are drawn by the theme in a colour that follows the usage level. function dashboardMemory() { const charts = document.querySelector('.tile-system-memory-charts'); if (!charts) return; charts.classList.add('tp-rings'); const styles = getComputedStyle(document.documentElement); const token = function (name, fallback) { return styles.getPropertyValue(name).trim() || fallback; }; if (Array.isArray(window.colors)) { const palette = [token('--tp-primary', '#4f8bff'), token('--tp-ring-track', '#2a3140'), '#a78bfa', '#22d3ee', '#f472b6', '#fbbf24', '#34d399', '#fb7185', '#818cf8', '#2dd4bf', '#f97316', '#60a5fa']; palette.forEach(function (colour, index) { if (index < window.colors.length) window.colors[index] = colour; }); } // Unraid colours the percentage green, orange or red; that colour gives the level. const levelOf = function (node) { const match = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(node.style.color || ''); if (!match) return 'ok'; const red = +match[1]; const green = +match[2]; if (red > 180 && green < 90) return 'critical'; if (red > 180) return 'warning'; return 'ok'; }; [...charts.querySelectorAll('div.pie[id^="sys"]')].forEach(function (pie) { const value = pie.querySelector('span[class^="sys"]'); if (!value) return; const update = function () { pie.dataset.level = levelOf(value); const percent = parseFloat(value.textContent); if (!isNaN(percent)) pie.style.setProperty('--tp-p', Math.max(0, Math.min(100, percent))); }; new MutationObserver(update).observe(value, { childList: true, characterData: true, subtree: true, attributes: true, attributeFilter: ['style'] }); update(); }); } // Dashboard, GPU Statistics plugin (one tile per GPU, #tblGPUDash1, 2, ...): its title is loose // text in the line below the icon, and vendor, driver and device sit in a little table of // their own under the header. The title becomes the tile heading, the device follows it as one // line, and load, temperature and processes become a short line under that; the readings get // their layout in enhance.css. The plugin's script finds its values by class, so the elements // are moved, not copied. function dashboardGpu() { document.querySelectorAll('table.dashboard > tbody[id^="tblGPUDash"]:not(.tp-gpu)').forEach(function (tile) { const section = tile.querySelector('.tile-header-left > .section'); if (!section) return; tile.classList.add('tp-gpu'); const name = section.firstChild; if (name && name.nodeType === Node.TEXT_NODE && name.textContent.trim()) { name.replaceWith(element('h3', 'tile-header-main', name.textContent.trim())); } section.querySelectorAll(':scope > br').forEach(function (br) { br.remove(); }); const meta = section.querySelector(':scope > span'); if (meta) meta.classList.add('tp-gpu-meta'); // The device name under the heading; vendor and driver ("Intel i915") lead the readings line. const find = function (key) { return tile.querySelector('[class^="gpu-' + key + '"], [class*=" gpu-' + key + '"]'); }; const device = element('span', 'tp-gpu-device'); const driver = element('span', 'tp-gpu-driver'); [[find('name'), device], [find('vendor'), driver], [find('driver'), driver]].forEach(function (pair) { if (!pair[0]) return; const row = pair[0].closest('tr'); if (row) row.classList.add('tp-gpu-moved'); pair[1].append(pair[0]); }); if (device.childElementCount || driver.childElementCount) { const heading = section.querySelector('.tile-header-main'); if (heading) heading.after(device); else section.prepend(device); if (meta) meta.prepend(driver); else device.after(driver); const header = tile.querySelector('tr.header'); if (header) header.classList.add('tp-gpu-moved'); } // Cells by what they hold, for enhance.css: a label (span.w36), a reading with a label // (span.w26) and a usage bar (span.w44 with .usage-disk). Classes rather than :has() // in the stylesheet: a :has() rule on every td is checked again on each change of any // page, which slowed down busy pages (FolderView Plus's Docker list). Marked again if the // plugin adds rows. const markCells = function () { tile.querySelectorAll(':scope > tr > td').forEach(function (cell) { cell.classList.toggle('tp-gpu-w36', Boolean(cell.querySelector(':scope > span.w36'))); cell.classList.toggle('tp-gpu-w26', Boolean(cell.querySelector(':scope > span.w26'))); }); tile.querySelectorAll(':scope > tr > td > span.w44').forEach(function (span) { span.classList.toggle('tp-gpu-bar', Boolean(span.querySelector(':scope > .usage-disk'))); }); }; markCells(); new MutationObserver(markCells).observe(tile, { childList: true }); // A clock of 0 means the GPU is idle: an Intel GPU with nothing to do stops its clock // (power saving). It reads "idle" instead (enhance.css). The plugin rewrites the value // every few seconds, so it is watched and only marked, not replaced. tile.querySelectorAll('[class^="gpu-clock"], [class*=" gpu-clock"]').forEach(function (clock) { const mark = function () { clock.toggleAttribute('data-tp-idle', /^0(\.0+)?(\s*[MG]Hz)?$/i.test(clock.textContent.trim())); }; new MutationObserver(mark).observe(clock, { childList: true, characterData: true, subtree: true }); mark(); }); }); } // Dashboard, Docker tile. With the theme's groups: see dashboardGroups. With FolderView's // folders: the tile keeps FolderView's order, a line sets each folder apart from the // containers after it, and each folder chip shows its apps' icons. function dashboardDockerOrder() { const view = document.getElementById('docker_view'); if (!view) return; if (!folderViewActive()) { dashboardGroups(view, 'docker'); return; } // FolderView Plus lays out its folders on the tile itself. if (folderViewPlus()) return; // A line between a folder and the containers right after it, wherever the folder is (the // tile follows FolderView's order). Lines are only added or removed where that changed, so // the tile's observer settles. const dividers = function (cell) { [...cell.querySelectorAll(':scope > .tp-chip-divider')].forEach(function (line) { const before = line.previousElementSibling; const after = line.nextElementSibling; if (!(before && before.classList.contains('folder-showcase-outer') && after && after.matches('span.outer.apps'))) line.remove(); }); [...cell.querySelectorAll(':scope > span.outer.apps')].forEach(function (chip) { const before = chip.previousElementSibling; if (!before || !before.classList.contains('folder-showcase-outer')) return; const line = element('div', 'tp-chip-divider'); line.setAttribute('aria-hidden', 'true'); chip.before(line); }); }; const arrange = function () { const first = view.querySelector('td > span.outer.apps, td > .folder-showcase-outer'); if (!first) return; const cell = first.parentElement; const folders = [...cell.children].filter(function (node) { return node.classList.contains('folder-showcase-outer'); }); folders.forEach(function (folder) { // A closed folder keeps its containers' chips in its hidden store, an open one in its panel. const inner = folder.querySelector(':scope > span.outer > .inner'); if (!inner) return; let strip = inner.querySelector(':scope > .tp-group-apps'); if (!strip) { strip = element('span', 'tp-group-apps'); inner.append(strip); } appStrip(strip, chipApps([...folder.querySelectorAll('.folder-storage > span.outer, .folder-showcase > span.outer')]), 4); }); dividers(cell); nameTooltips(cell); }; let queued = false; new MutationObserver(function () { if (queued) return; queued = true; requestAnimationFrame(function () { queued = false; arrange(); }); }).observe(view, { childList: true, subtree: true }); arrange(); } // Dashboard, Virtual Machines tile: the theme's VM groups (not while FolderView arranges it). function dashboardVmGroups() { const view = document.getElementById('vm_view'); if (view && !folderViewActive()) dashboardGroups(view, 'vm'); } // A Dashboard chip's name: the container's name element, or the VM's name text. function chipName(chip) { const inner = chip.querySelector('.inner'); const named = inner && inner.querySelector('.appname'); if (named) return named.textContent.trim(); return inner && inner.firstChild ? inner.firstChild.textContent.trim() : ''; } // Whether a Dashboard container chip has an image update ready (the "update" argument of its // icon's addDockerContainerContext call is 1). VM chips have none. function chipUpdate(chip) { const trigger = chip.querySelector(':scope > span.hand[onclick*="addDockerContainerContext"]'); return Boolean(trigger) && contextArguments(trigger)[5] === '1'; } // Dashboard chips as apps for an icon strip; a click on an icon is a click on the chip's icon. function chipApps(chips) { return chips.map(function (chip) { const hand = chip.querySelector(':scope > span.hand'); const image = hand && hand.querySelector('img'); const state = chip.classList.contains('paused') ? 'paused' : chip.classList.contains('started') ? 'running' : 'stopped'; return { name: chipName(chip), src: image && image.src, state: state, update: chipUpdate(chip), target: hand }; }); } // Long names end in "…" (enhance.css); the whole name is the tooltip. function nameTooltips(cell) { cell.querySelectorAll('span.outer:not(.tp-group-chip) .inner').forEach(function (inner) { const name = chipName(inner.parentElement); if (name && inner.title !== name) inner.title = name; }); } // Dashboard, Docker or Virtual Machines tile: the theme's groups as chips that open and close a // panel with their members, in the groups' order and then "Ungrouped" (only while there are // groups, as on the tabs); the members in Unraid's order. Each group chip shows its members' // icons, which open their menus. Open and closed is shared with the tab. function dashboardGroups(view, type) { const kind = type === 'vm' ? 'vms' : 'apps'; const noun = type === 'vm' ? 'VM' : 'container'; const collapsed = collapsedState(type === 'vm' ? 'tft-vm-collapsed' : 'tft-docker-collapsed'); const UNGROUPED_KEY = '\u0000ungrouped'; // the tabs' key for their "Ungrouped" section const divider = element('div', 'tp-chip-divider'); divider.setAttribute('aria-hidden', 'true'); const parts = new Map(); // key: the collapsed-state key shared with the tab ('g:' + name, or UNGROUPED_KEY). const groupPart = function (name, key) { if (parts.has(key)) return parts.get(key); const chip = element('span', 'outer solid ' + kind + ' tp-group-chip'); chip.setAttribute('role', 'button'); chip.tabIndex = 0; const icon = element('span', 'tp-group-chip-icon'); icon.append(element('i', 'fa fa-folder-open')); const inner = element('span', 'inner'); inner.append(element('span', 'tp-group-chip-name', name), element('span', 'tp-group-chip-count'), element('span', 'tp-group-apps')); chip.append(icon, inner, element('i', 'fa fa-chevron-down tp-group-chip-caret')); const panel = element('div', 'tp-group-panel'); const toggle = function () { collapsed.toggle(key); arrange(); }; chip.addEventListener('click', toggle); chip.addEventListener('keydown', function (event) { if (event.target === chip && (event.key === 'Enter' || event.key === ' ')) { event.preventDefault(); toggle(); } }); const part = { chip: chip, panel: panel, name: inner.firstChild, icon: icon.firstChild, count: inner.children[1], apps: inner.children[2] }; parts.set(key, part); return part; }; const arrange = function () { const first = view.querySelector('td > span.outer.' + kind + ':not(.tp-group-chip), td > .tp-group-panel'); if (!first) return; const cell = first.parentElement; const chips = [...cell.querySelectorAll(':scope > span.outer.' + kind + ':not(.tp-group-chip), :scope > .tp-group-panel > span.outer.' + kind)]; const placed = new Set(); const wanted = []; const sections = store.groups.map(function (group) { const members = chips.filter(function (chip) { return !placed.has(chip) && group.containers.includes(chipName(chip)); }); members.forEach(function (chip) { placed.add(chip); }); return { name: group.name, key: 'g:' + group.name, members: members }; }); if (sections.some(function (section) { return section.members.length; })) { sections.push({ name: 'Ungrouped', key: UNGROUPED_KEY, members: chips.filter(function (chip) { return !placed.has(chip); }) }); } const keys = new Set(sections.map(function (section) { return section.key; })); parts.forEach(function (part, key) { if (keys.has(key)) return; part.chip.remove(); part.panel.remove(); parts.delete(key); }); sections.forEach(function (section) { const part = groupPart(section.name, section.key); const members = section.members; if (!members.length) { part.chip.remove(); part.panel.remove(); return; } members.forEach(function (chip) { placed.add(chip); }); if (members.some(function (chip, index) { return part.panel.children[index] !== chip; }) || part.panel.children.length !== members.length) { part.panel.replaceChildren(...members); } const isCollapsed = collapsed.has(section.key); const running = members.filter(function (chip) { return chip.classList.contains('started'); }).length; const count = members.length + ' ' + noun + (members.length === 1 ? '' : 's') + ' · ' + running + ' running'; if (part.count.textContent !== count) part.count.textContent = count; appStrip(part.apps, chipApps(members), 4); part.chip.title = section.name + ': ' + count; part.chip.setAttribute('aria-expanded', String(!isCollapsed)); part.chip.classList.toggle('tp-collapsed', isCollapsed); part.icon.className = 'fa ' + (isCollapsed ? 'fa-folder' : 'fa-folder-open'); part.panel.hidden = isCollapsed; wanted.push(part.chip, part.panel); }); const loose = chips.filter(function (chip) { return !placed.has(chip); }); if (wanted.length && loose.length) wanted.push(divider); else divider.remove(); wanted.push(...loose); wanted.forEach(function (node, index) { if (cell.children[index] !== node) cell.insertBefore(node, cell.children[index] || null); }); nameTooltips(cell); }; const store = groupStore(type, arrange); let queued = false; new MutationObserver(function () { if (queued) return; queued = true; requestAnimationFrame(function () { queued = false; arrange(); }); }).observe(view, { childList: true, subtree: true }); store.load(); arrange(); } // Dashboard, Virtual Machines Usage tile (VM Manager settings, "Show VM usage"): Unraid puts a // running VM's icon, name and its five readings (guest and host CPU, memory, disk, network) in // one small chip, where the values ran into each other. Each VM becomes a full card: icon and // name on top, the readings in a grid below. Unraid rewrites the readings by their ids, so they // are moved into the grid, not copied. function dashboardVmUsage() { const tile = document.getElementById('vm_view_usage'); if (!tile) return; const arrange = function () { tile.querySelectorAll('span.outer.vmsuse:not(.tp-vmuse)').forEach(function (chip) { chip.classList.add('tp-vmuse'); const readings = element('span', 'tp-vmuse-readings'); chip.querySelectorAll(':scope > span[id^="vmmetrics-"]').forEach(function (reading) { readings.append(reading); }); chip.querySelectorAll(':scope > br').forEach(function (br) { br.remove(); }); chip.append(readings); }); }; // Unraid adds the VM rows when the dashboard loads its app lists, and again on refresh. new MutationObserver(arrange).observe(tile, { childList: true }); arrange(); } // Airflow tile: Unraid prints fan names and speeds as rows of three. They become one row of // small cards that wraps only when there are more fans than fit. The speed elements are moved, // not copied, so Unraid keeps updating them by their ids. function dashboardFans() { const firstSpeed = document.querySelector('table.dashboard td > span[id^="fan"]'); if (!firstSpeed) return; const cell = firstSpeed.parentElement; if (cell.querySelector('.tp-fans')) return; const spans = [...cell.children].filter(function (node) { return node.tagName === 'SPAN'; }); const speeds = spans.filter(function (node) { return /^fan\d+$/.test(node.id); }); const names = spans.filter(function (node) { return !node.id; }); if (!speeds.length || names.length !== speeds.length) return; const grid = element('div', 'tp-fans'); speeds.forEach(function (speed, index) { const card = element('div', 'tp-fan'); const icon = element('i', 'fa fa-asterisk tp-fan-icon'); icon.setAttribute('aria-hidden', 'true'); const text = element('div', 'tp-fan-text'); speed.className = 'tp-fan-speed'; text.append(element('span', 'tp-fan-name', names[index].textContent.trim()), speed); card.append(icon, text); grid.append(card); // A fan at 0 RPM is shown as stopped. const mark = function () { card.classList.toggle('tp-fan-idle', /^\s*0\s/.test(speed.textContent)); }; new MutationObserver(mark).observe(speed, { childList: true, characterData: true, subtree: true }); mark(); }); cell.replaceChildren(grid); } // Unraid's "External Link" dialog puts the address, the checkbox and its label in one bold // run of text. It is rearranged into an address box and a clickable label; the checkbox itself // is moved, not replaced, so Unraid still reads it when OK is pressed. function externalLinkDialog() { const arrange = function () { const box = document.getElementById('Link_Always_Allow'); if (!box || box.dataset.tpArranged) return; const bold = box.closest('b'); const holder = box.closest('span'); if (!bold || !holder) return; box.dataset.tpArranged = '1'; const address = bold.firstChild ? bold.firstChild.textContent.trim() : ''; let rest = ''; for (let node = box.nextSibling; node; node = node.nextSibling) rest += node.textContent; const label = element('label', 'tp-link-allow'); label.append(box, rest.trim()); bold.replaceWith(element('div', 'tp-link-url', address), label); holder.querySelectorAll('br').forEach(function (br) { br.remove(); }); }; new MutationObserver(arrange).observe(document.body, { childList: true, subtree: true }); arrange(); } // Main menu: every tab gets its name as a tooltip, for the narrow top bar where tabs show only // their icon. On phones, where the tabs scroll sideways, the open tab is scrolled into view. function navTitles() { document.querySelectorAll('#menu .nav-tile:not(.right) .nav-item a').forEach(function (link) { if (!link.title) link.title = link.textContent.trim(); }); const active = document.querySelector('#menu .nav-tile:not(.right) .nav-item.active'); const row = active && active.parentElement; if (!row || row.scrollWidth <= row.clientWidth) return; const a = active.getBoundingClientRect(); const r = row.getBoundingClientRect(); row.scrollLeft += a.left - r.left - (r.width - a.width) / 2; } // Menu: Unraid's array usage bar (Display Settings, "Show array utilization indicator") // becomes a widget at the start of the quick actions: a drive icon, "Array" with the // percentage, and a thin bar in the usage colour Unraid chose (its warning and critical // levels). It opens Main. Unraid's own bar stays in the page, hidden. function usageBar() { const native = document.querySelector('#menu .nav-tile.right > .usage-bar'); if (!native) return; const fill = native.querySelector('span'); const text = fill ? fill.textContent.trim() : ''; const percent = /^(\d+)%$/.exec(text); const tone = !fill ? '' : fill.classList.contains('redbar') ? 'danger' : fill.classList.contains('orangebar') ? 'warn' : fill.classList.contains('greenbar') ? 'good' : ''; const widget = element('a', 'tp-usage' + (tone ? ' tp-usage-' + tone : '') + (percent ? '' : ' tp-usage-off')); widget.href = '/Main'; widget.title = (percent ? 'Array ' + text + ' used' : 'Array ' + (text || 'offline')) + '. Open Main'; const icon = element('span', 'tp-usage-icon'); icon.append(element('i', 'fa fa-hdd-o')); const head = element('span', 'tp-usage-head'); head.append(element('span', 'tp-usage-label', 'Array'), element('span', 'tp-usage-value', percent ? text : text || 'offline')); const track = element('span', 'tp-usage-track'); const bar = element('span', 'tp-usage-fill'); bar.style.width = percent ? Math.min(100, Number(percent[1])) + '%' : '0'; track.append(bar); const body = element('span', 'tp-usage-body'); body.append(head, track); widget.append(icon, body); native.classList.add('tp-native-hidden'); // Where it goes: with the menu as a bar across the top of a wide screen, the menu row has no // room left (Stats and Tools were pushed out of view), so it sits in the top bar after // Unraid's logo; in the sidebar and on phones it stays with the quick actions. const row = native.parentElement; const wide = window.matchMedia('(min-width: 1024px)'); const place = function () { const header = document.getElementById('header'); const logo = header && header.querySelector(':scope > unraid-header-os-version'); const inHeader = Boolean(logo) && wide.matches && document.documentElement.classList.contains('tp-topbar'); widget.classList.toggle('tp-usage-header', inHeader); if (inHeader) { if (widget.previousElementSibling !== logo) logo.after(widget); } else if (widget.parentElement !== row) { row.prepend(widget); } }; place(); wide.addEventListener('change', place); } // Menu: when the next page takes a moment to arrive (Docker with many containers), "Opening // Docker…" shows at the bottom so the click does not look missed. Quick pages never show it. function navProgress() { let timer = 0; let notice = null; document.addEventListener('click', function (event) { if (event.defaultPrevented || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return; const link = event.target.closest && event.target.closest('#menu .nav-tile:not(.right) .nav-item a[href]'); if (!link || link.target === '_blank' || link.origin !== location.origin || link.getAttribute('href').startsWith('#')) return; const name = (link.title || link.textContent).trim(); clearTimeout(timer); timer = setTimeout(function () { if (!notice) { notice = element('div', 'tp-nav-progress'); notice.setAttribute('role', 'status'); notice.append(element('span', 'tp-loader'), element('span', 'tp-nav-progress-text')); document.body.append(notice); } notice.querySelector('.tp-nav-progress-text').textContent = 'Opening ' + name + '…'; }, 250); }); // Coming back with the browser's Back button can restore this page as it was left. window.addEventListener('pageshow', function (event) { if (!event.persisted) return; clearTimeout(timer); if (notice) notice.remove(); notice = null; }); } // Help button: Unraid's button only shows or hides the page's help boxes, so on pages without // any (Dashboard, Main, Docker, ...) a click did nothing. There it is dimmed, with the tooltip // "No help on this page". function helpButton() { const items = [...document.querySelectorAll('.nav-item.HelpButton')]; if (!items.length) return; let marked; // Runs after every change of the page (a list rebuilt every few seconds): it only touches // the button when the answer changes. const mark = function () { const none = !document.querySelector('.inline_help'); if (none === marked) return; marked = none; items.forEach(function (item) { const link = item.querySelector('a'); item.classList.toggle('tp-help-none', none); if (!link) return; if (!link.dataset.tpTitle) link.dataset.tpTitle = link.title || 'Help'; link.title = none ? 'No help on this page' : link.dataset.tpTitle; link.setAttribute('aria-disabled', none ? 'true' : 'false'); }); }; mark(); // Lists rebuilt inside a table (Docker, VMs, live values) never add or remove help. const later = debounce(mark); new MutationObserver(function (records) { if (records.some(function (record) { return !(record.target.closest && record.target.closest('table')); })) later(); }).observe(document.getElementById('displaybox') || document.body, { childList: true, subtree: true }); } // Tools pages: their help text is given a clearer structure. Paragraphs written all in // italics or starting with "NOTE" become notes, warnings ("DO NOT ...", red text) become // warning callouts, all-caps lines (the EULA's sections) become headings and "Name Copyright" // lines (Credits) become rows. On Diagnostics the list of collected files becomes cards. function toolText(info) { if (info.tab !== 'Tools' || !location.pathname.split('/')[2]) return; const content = document.querySelector('#displaybox .content'); if (!content || content.classList.contains('tp-tool-text')) return; content.classList.add('tp-tool-text'); const textOf = function (node) { return node.textContent.replace(/\s+/g, ' ').trim(); }; content.querySelectorAll('p').forEach(function (p) { if (p.closest('.unapi, pre, table, blockquote, dd, dt')) return; const text = textOf(p); if (!text) return; const looseText = [...p.childNodes].filter(function (node) { return node.nodeType === 3; }).map(function (node) { return node.textContent; }).join('').trim(); const red = p.querySelector(':scope > font[color=red], :scope > .red-text'); const only = p.children.length === 1 && !looseText ? p.firstElementChild : null; if (p.matches('.warning, .notice, .red-text, .orange-text') || /^(DO NOT|WARNING|CAUTION|IMPORTANT)\b/.test(text) || (red && textOf(red) === text)) p.classList.add('tp-callout', 'tp-callout-warn'); else if (p.matches('.blue-text') || /^NOTE\b/i.test(text) || (!looseText && p.querySelector(':scope > em, :scope > i:not(.fa)'))) p.classList.add('tp-callout'); else if (only && only.matches('strong, b') && text.length < 80) p.classList.add('tp-text-subheading'); else if (text.length < 90 && /[A-Z]{3}/.test(text) && text === text.toUpperCase()) p.classList.add('tp-text-heading'); else if (p.firstChild && p.firstChild.nodeType === 1 && p.firstChild.matches('strong, b') && /copyright/i.test(text)) p.classList.add('tp-credit'); }); diagnosticsList(content); } // Diagnostics: "+ /config
copy all ..." in a pre block, rebuilt as one card per entry. function diagnosticsList(content) { const holder = content.querySelector('pre.pre > p'); if (!holder || !/\/Diagnostics$/.test(location.pathname)) return; const items = []; let item = null; const addText = function (text) { if (item && text.trim()) item.desc.push(document.createTextNode(text.replace(/\s+/g, ' '))); }; [...holder.childNodes].forEach(function (node) { if (node.nodeType === 3) { const parts = node.textContent.split('+'); addText(parts[0]); parts.slice(1).forEach(function (part) { item = { title: '', desc: [] }; items.push(item); addText(part); }); } else if (!item || node.nodeType !== 1) { return; } else if (!item.title && node.matches('em')) { item.title = node.textContent.trim(); } else if (node.matches('br')) { if (item.desc.length) item.desc.push(document.createElement('br')); } else { item.desc.push(node); } }); if (items.length < 2 || items.some(function (entry) { return !entry.title; })) return; const icons = [[/^\/config$/, 'fa-cog'], [/^\/config\/shares/, 'fa-share-alt'], [/^syslog/i, 'fa-file-text-o'], [/^system/i, 'fa-microchip'], [/^smart/i, 'fa-hdd-o'], [/^docker/i, 'fa-cube']]; const grid = element('div', 'tp-collect'); items.forEach(function (entry) { const match = icons.find(function (pair) { return pair[0].test(entry.title); }); const icon = element('i', 'fa ' + (match ? match[1] : 'fa-file-o')); icon.setAttribute('aria-hidden', 'true'); const head = element('div', 'tp-collect-title'); head.append(icon, element('span', '', entry.title)); while (entry.desc.length && entry.desc[entry.desc.length - 1].nodeName === 'BR') entry.desc.pop(); const desc = element('p', 'tp-collect-desc'); desc.append(...entry.desc); const card = element('div', 'tp-collect-item'); card.append(head, desc); grid.append(card); }); holder.closest('pre').replaceWith(grid); } // Community Applications: its Change Log, Credits and Statistics view is shown as a modal by // the theme; a click on the dimmed backdrop closes it like its own close button. App names in // the search field are not spell-checked (no red underline). // Dashboard, Disk Location tile: each bay lists the colours of its drive's orbs (grey for an // empty bay, green, yellow, red, blue) in data-tp-orbs, which theme.css tints the bay by. // Asked with :has() in the stylesheet instead, every table cell on every page took part in // each restyle (FolderView Plus's Docker list loaded half again as slowly). function diskLocationBays() { const tile = document.getElementById('tblDiskLocation'); if (!tile) return; tile.querySelectorAll('.flex-container-layout_h > div').forEach(function (bay) { const colours = []; bay.querySelectorAll('i.orb-disklocation').forEach(function (orb) { const colour = (orb.className.match(/\b(grey|green|yellow|red|blue)-orb-disklocation\b/) || [])[1]; if (colour && !colours.includes(colour)) colours.push(colour); }); if (colours.length) bay.dataset.tpOrbs = colours.join(' '); }); } // Tables in the page content: a row whose first cell is bold (inline) is a section heading; // theme.css styles its other cells by the class set here. function sectionRows() { document.querySelectorAll('#displaybox table > tbody > tr > td:first-child[style*="bold"]').forEach(function (cell) { if (/font-weight:\s*bold/.test(cell.getAttribute('style'))) cell.parentElement.classList.add('tp-section-row'); }); } function caOverlay() { const search = document.getElementById('searchBox'); if (search) search.spellcheck = false; const overlay = document.getElementById('alternateView'); if (!overlay) return; overlay.addEventListener('click', function (event) { if (event.target !== overlay) return; const close = overlay.querySelector('.alternateViewClose'); if (close) close.click(); }); } // Settings and Tools: a search field above the panels. Typing filters the panels by their // name (and page name); sections without a match are hidden with them. function panelSearch(info) { if (info.subPage || !/^(Settings|Tools)$/.test(info.tab)) return; const panels = [...document.querySelectorAll('#displaybox .Panels .Panel')]; if (!panels.length || document.querySelector('.tp-panel-search')) return; const items = panels.map(function (panel) { const link = panel.querySelector('a'); const page = link ? (link.getAttribute('href') || '').split('/').pop().replace(/([a-z0-9])([A-Z])/g, '$1 $2') : ''; return { panel: panel, text: (panel.textContent + ' ' + page).toLowerCase() }; }); const wrap = element('div', 'tp-panel-search'); const field = element('input', 'tp-panel-search-input'); field.type = 'search'; field.placeholder = 'Search ' + info.tab.toLowerCase() + '…'; field.setAttribute('aria-label', 'Search ' + info.tab.toLowerCase()); const count = element('span', 'tp-panel-search-count'); count.setAttribute('aria-live', 'polite'); wrap.append(field, count); const empty = element('p', 'tp-panel-search-empty', 'Nothing matches this search.'); empty.hidden = true; const heading = document.querySelector('#displaybox .tp-heading'); const firstGroup = panels[0].parentElement; const firstTitle = firstGroup.previousElementSibling && firstGroup.previousElementSibling.matches('div.title') ? firstGroup.previousElementSibling : firstGroup; if (heading) heading.append(wrap); else firstTitle.before(wrap); (document.querySelector('.tp-fav-title') || firstTitle).before(empty); function filter() { const words = field.value.trim().toLowerCase().split(/\s+/).filter(Boolean); let shown = 0; items.forEach(function (item) { const match = words.every(function (word) { return item.text.includes(word); }); item.panel.classList.toggle('tp-filtered-out', !match); if (match) shown += 1; }); // Worked out each time: favourites move cards between sections. const groups = new Set(document.querySelectorAll('#displaybox .Panels')); groups.forEach(function (group) { // The Favourites section stays visible (with its hint) while nothing is searched. const visible = (!words.length && group.classList.contains('tp-fav-panels')) || [...group.children].some(function (child) { return child.matches('.Panel') && !child.classList.contains('tp-filtered-out'); }); group.classList.toggle('tp-filtered-out', !visible); const title = group.previousElementSibling; if (title && title.matches('div.title')) title.classList.toggle('tp-filtered-out', !visible); }); count.textContent = words.length ? shown + ' of ' + items.length : ''; empty.hidden = shown > 0; } field.addEventListener('input', filter); document.addEventListener('tp-panels-moved', filter); // "/" jumps to the search field, as on many sites; Escape clears it. document.addEventListener('keydown', function (event) { if (event.key === '/' && !/^(input|textarea|select)$/i.test(document.activeElement.tagName)) { event.preventDefault(); field.focus(); } }); field.addEventListener('keydown', function (event) { if (event.key === 'Escape') { field.value = ''; filter(); } }); } // Settings and Tools: a "Favourites" section above the others. Each card gets a star; a starred // card moves into Favourites and leaves its own section, and goes back to its old place when // the star is removed. The list is kept on the flash drive, so it is the same in every browser. function panelFavourites(info) { if (info.subPage || !/^(Settings|Tools)$/.test(info.tab)) return; // Favourites switched off in Display Settings. if (document.documentElement.classList.contains('tp-no-favourites')) return; const panels = [...document.querySelectorAll('#displaybox .Panels .Panel')]; if (!panels.length || document.querySelector('.tp-fav-panels')) return; const URL_FAVOURITES = PLUGIN_BASE + '/include/favourites.php'; const keyOf = function (panel) { const link = panel.querySelector('a[href]'); if (!link) return ''; try { return new URL(link.href, location.href).pathname.replace(/^\/+|\/+$/g, ''); } catch (error) { return ''; } }; const cards = []; panels.forEach(function (panel, index) { const key = keyOf(panel); if (!key) return; cards.push({ panel: panel, key: key, home: panel.parentElement, order: index }); const star = element('button', 'tp-fav-toggle'); star.type = 'button'; star.append(element('i', 'fa fa-star')); panel.append(star); star.addEventListener('click', function (event) { event.preventDefault(); event.stopPropagation(); toggle(key); }); }); if (!cards.length) return; const firstGroup = panels[0].parentElement; const firstTitle = firstGroup.previousElementSibling && firstGroup.previousElementSibling.matches('div.title') ? firstGroup.previousElementSibling : firstGroup; const title = element('div', 'title tp-fav-title'); const left = element('span', 'left'); left.append(element('i', 'fa fa-star title'), document.createTextNode('Favourites')); title.append(left); const hint = element('p', 'tp-fav-hint', 'Star a card to pin it here. Starred cards leave their own section until the star is removed.'); const group = element('div', 'Panels tp-fav-panels'); firstTitle.before(title, group, hint); let favourites = []; function place() { cards.forEach(function (card) { const starred = favourites.includes(card.key); const star = card.panel.querySelector('.tp-fav-toggle'); const name = (card.panel.querySelector('.PanelText') || card.panel).textContent.trim(); star.classList.toggle('tp-on', starred); star.setAttribute('aria-pressed', starred ? 'true' : 'false'); star.title = (starred ? 'Remove ' : 'Add ') + name + (starred ? ' from favourites' : ' to favourites'); star.setAttribute('aria-label', star.title); card.panel.classList.toggle('tp-fav', starred); }); // Favourites in the order they were starred; every other card back at its old place. favourites.forEach(function (key) { const card = cards.find(function (item) { return item.key === key; }); if (card) group.append(card.panel); }); cards.filter(function (card) { return !favourites.includes(card.key); }) .sort(function (a, b) { return a.order - b.order; }) .forEach(function (card) { card.home.append(card.panel); }); hint.hidden = group.children.length > 0; document.querySelectorAll('#displaybox .Panels').forEach(function (section) { const empty = section !== group && !section.querySelector('.Panel'); section.classList.toggle('tp-empty-section', empty); const sectionTitle = section.previousElementSibling; if (sectionTitle && sectionTitle.matches('div.title')) sectionTitle.classList.toggle('tp-empty-section', empty); }); document.dispatchEvent(new CustomEvent('tp-panels-moved')); } function toggle(key) { const before = favourites.slice(); favourites = favourites.includes(key) ? favourites.filter(function (item) { return item !== key; }) : favourites.concat(key); place(); if (!PLUGIN_BASE) return; const body = new URLSearchParams({ favourites: JSON.stringify(favourites), csrf_token: window.csrf_token || '' }); fetch(URL_FAVOURITES, { method: 'POST', credentials: 'same-origin', body: body }) .then(function (response) { if (!response.ok) throw new Error('saving favourites failed with status ' + response.status); }) .catch(function (error) { console.error('Aurora Theme favourites:', error); favourites = before; place(); modal({ title: 'Favourite not saved', message: 'The server did not accept the change, so the card is back where it was.', confirmLabel: 'OK', hideCancel: true }); }); } place(); if (!PLUGIN_BASE) return; fetch(URL_FAVOURITES, { credentials: 'same-origin' }) .then(function (response) { return response.json(); }) .then(function (data) { favourites = Array.isArray(data.favourites) ? data.favourites.filter(function (item) { return typeof item === 'string'; }) : []; place(); }) .catch(function (error) { console.error('Aurora Theme favourites:', error); }); } // Drop-down lists: the open list of a stays the real control: choosing an // entry sets it and fires the events its page listens for. Touch devices keep the native // picker, which suits them better. function selectLists() { if (window.matchMedia && window.matchMedia('(pointer: coarse)').matches) return; let open = null; const usable = function (select) { return select.matches('select') && !select.multiple && !select.disabled && (!select.size || select.size <= 1) && !select.closest('.tp-native-select') && select.options.length > 0; }; function close(returnFocus) { if (!open) return; const current = open; open = null; current.panel.remove(); current.select.classList.remove('tp-select-open'); current.select.setAttribute('aria-expanded', 'false'); if (returnFocus) current.select.focus({ preventScroll: true }); } function choose(entry) { const select = open.select; const changed = select.selectedIndex !== entry.index; close(true); if (!changed) return; select.selectedIndex = entry.index; select.dispatchEvent(new Event('input', { bubbles: true })); select.dispatchEvent(new Event('change', { bubbles: true })); } function setActive(position) { const entries = open.entries; if (!entries.length) return; const next = Math.max(0, Math.min(entries.length - 1, position)); if (open.active >= 0) entries[open.active].node.classList.remove('tp-active'); open.active = next; entries[next].node.classList.add('tp-active'); // Scrolled by hand: scrollIntoView could scroll the page too, and that closes the list. const node = entries[next].node; const panel = open.panel; if (node.offsetTop < panel.scrollTop) panel.scrollTop = node.offsetTop - 6; else if (node.offsetTop + node.offsetHeight > panel.scrollTop + panel.clientHeight) panel.scrollTop = node.offsetTop + node.offsetHeight - panel.clientHeight + 6; } function show(select) { close(false); const panel = element('div', 'tp-select-panel'); panel.setAttribute('role', 'listbox'); const entries = []; const addOption = function (option, parent) { const node = element('div', 'tp-select-option', option.textContent.trim() || ' '); node.setAttribute('role', 'option'); if (option.disabled || (option.parentElement && option.parentElement.disabled)) { node.classList.add('tp-disabled'); node.setAttribute('aria-disabled', 'true'); } if (option.selected) { node.classList.add('tp-selected'); node.setAttribute('aria-selected', 'true'); } if (option.hidden || option.style.display === 'none') return; parent.append(node); if (node.classList.contains('tp-disabled')) return; const entry = { node: node, index: option.index }; entries.push(entry); node.addEventListener('mousemove', function () { if (open && open.entries[open.active] !== entry) setActive(entries.indexOf(entry)); }); node.addEventListener('click', function () { choose(entry); }); }; [...select.children].forEach(function (child) { if (child.matches('optgroup')) { panel.append(element('div', 'tp-select-group', child.label)); [...child.children].forEach(function (option) { addOption(option, panel); }); } else if (child.matches('option')) { addOption(child, panel); } }); if (!entries.length) return; panel.addEventListener('mousedown', function (event) { event.preventDefault(); }); document.body.append(panel); // Under the field, or above it when there is more room there. const rect = select.getBoundingClientRect(); const below = window.innerHeight - rect.bottom - 12; const above = rect.top - 12; const upwards = below < 220 && above > below; panel.style.minWidth = rect.width + 'px'; panel.style.maxHeight = Math.max(160, Math.min(360, upwards ? above : below)) + 'px'; const left = Math.max(8, Math.min(rect.left, window.innerWidth - panel.offsetWidth - 8)); panel.style.left = left + 'px'; if (upwards) panel.style.bottom = (window.innerHeight - rect.top + 6) + 'px'; else panel.style.top = (rect.bottom + 6) + 'px'; panel.classList.add(upwards ? 'tp-upwards' : 'tp-downwards'); select.classList.add('tp-select-open'); select.setAttribute('aria-expanded', 'true'); open = { select: select, panel: panel, entries: entries, active: -1, typed: '', typedAt: 0 }; const selected = entries.findIndex(function (entry) { return entry.index === select.selectedIndex; }); setActive(selected >= 0 ? selected : 0); } document.addEventListener('mousedown', function (event) { const select = event.target.closest && event.target.closest('select'); if (open && open.panel.contains(event.target)) return; if (select && event.button === 0 && usable(select)) { event.preventDefault(); if (open && open.select === select) close(true); else { select.focus({ preventScroll: true }); show(select); } return; } close(false); }, true); document.addEventListener('keydown', function (event) { if (!open) { // Alt+Down, F4, Space or Enter on a focused list open the themed list as well. const select = document.activeElement; if (select && select.matches && select.matches('select') && usable(select) && ((event.altKey && event.key === 'ArrowDown') || event.key === 'F4' || event.key === ' ' || event.key === 'Enter')) { event.preventDefault(); show(select); } return; } const key = event.key; if (key === 'Escape' || key === 'Tab') { if (key === 'Escape') event.preventDefault(); close(key === 'Escape'); } else if (key === 'ArrowDown' || key === 'ArrowUp') { event.preventDefault(); setActive(open.active + (key === 'ArrowDown' ? 1 : -1)); } else if (key === 'Home' || key === 'End' || key === 'PageUp' || key === 'PageDown') { event.preventDefault(); const step = { Home: -1e6, End: 1e6, PageUp: -8, PageDown: 8 }[key]; setActive(open.active + step); } else if (key === 'Enter' || key === ' ') { event.preventDefault(); if (open.active >= 0) choose(open.entries[open.active]); } else if (key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) { // Type-ahead: jump to the first entry starting with the typed letters. event.preventDefault(); const now = Date.now(); open.typed = (now - open.typedAt > 700 ? '' : open.typed) + key.toLowerCase(); open.typedAt = now; const match = open.entries.findIndex(function (entry) { return entry.node.textContent.trim().toLowerCase().startsWith(open.typed); }); if (match >= 0) setActive(match); } }, true); window.addEventListener('resize', function () { close(false); }); document.addEventListener('scroll', function (event) { if (open && !open.panel.contains(event.target)) close(false); }, true); } // Docker container editor (add, edit, install from Apps): every host port field is checked // while typing and shows a green tick or a red cross on its right, with the reason under it. // Checked: a number from 1 to 65535; not twice in this form; not used by another container // on the server's address (from Unraid's own list on the page); not taken by a program on the // server (include/ports.php). A container with its own address (custom network) can reuse // ports, so only the first two checks apply there; the port a container already has when it // is edited is accepted as well. function dockerPorts() { const form = document.querySelector('input[name="contName"]'); if (!form || !document.getElementById('configLocation')) return; const nameField = form; const originalName = nameField.value.trim().toLowerCase(); const network = document.querySelector('select[name="contNetwork"]'); let listening = []; // Unraid's list: [{Name, Port: "bridge192.168.1.1080, 443"}] const allocations = (Array.isArray(window.Allocations) ? window.Allocations : []).map(function (entry) { const parts = [...new DOMParser().parseFromString(String(entry.Port || ''), 'text/html').querySelectorAll('span')].map(function (span) { return span.textContent.trim(); }); return { name: String(entry.Name || ''), network: parts[0] || '', ip: parts[1] || '', ports: (parts[2] || '').split(/\s*,\s*/).map(Number).filter(function (port) { return port > 0; }), }; }); const hostAddresses = new Set([location.hostname]); allocations.forEach(function (entry) { if (/^(bridge|host)$/.test(entry.network) && entry.ip) hostAddresses.add(entry.ip); }); const sharesHostAddress = function () { const mode = network ? network.value : 'bridge'; return mode === 'bridge' || mode === 'host'; }; function portRows() { return [...document.querySelectorAll('#configLocation input[name="confType[]"]')] .filter(function (type) { return type.value === 'Port'; }) .map(function (type) { const row = type.closest('[id^="ConfigNum"]'); const field = row && row.querySelector('input[name="confValue[]"]'); if (!field) return null; const value = function (name) { const node = row.querySelector('input[name="' + name + '"]'); return node ? node.value : ''; }; return { row: row, field: field, name: value('confName[]'), proto: (value('confMode[]') || 'tcp').toLowerCase(), required: value('confRequired[]') === 'true' }; }) .filter(Boolean); } // Returns [ok, message]; ok is null for an empty optional field (no mark). function check(item, rows) { const text = item.field.value.trim(); if (!text) return item.required ? [false, 'A port is required here.'] : [null, '']; // A single port, or a range such as 8000-8010. const match = /^(\d+)(?:-(\d+))?$/.exec(text); if (!match) return [false, 'Use a port number (1 to 65535) or a range such as 8000-8010.']; const first = Number(match[1]); const last = match[2] ? Number(match[2]) : first; if (first < 1 || last > 65535 || first > 65535) return [false, 'Ports go from 1 to 65535.']; if (last < first) return [false, 'A range starts with the lower port.']; const inRange = function (value) { return value >= first && value <= last; }; const span = function (value) { const parts = /^(\d+)(?:-(\d+))?$/.exec(String(value).trim()); return parts ? [Number(parts[1]), Number(parts[2] || parts[1])] : null; }; const twin = rows.find(function (other) { const range = other !== item && other.proto === item.proto && span(other.field.value); return range && range[0] <= last && range[1] >= first; }); if (twin) return [false, 'Also used for "' + (twin.name || 'another port') + '" in this container (' + item.proto.toUpperCase() + ').']; if (!sharesHostAddress()) return [true, 'Valid. This container has its own address, so the port cannot clash.']; const own = item.field.dataset.tpOriginal === text; const current = nameField.value.trim().toLowerCase(); const user = allocations.find(function (entry) { const name = entry.name.toLowerCase(); return name !== current && name !== originalName && hostAddresses.has(entry.ip) && entry.ports.some(inRange); }); if (user) { const taken = user.ports.find(inRange); return [false, 'Port ' + taken + ' is already used by the container "' + user.name + '".']; } const program = !own && listening.find(function (entry) { return inRange(entry.port) && entry.proto === item.proto; }); if (program) return [false, 'Port ' + program.port + ' is in use on the server' + (program.process ? ' by ' + program.process : '') + '.']; return [true, (first === last ? 'Port ' + first + ' is' : 'Ports ' + first + ' to ' + last + ' are') + ' free.']; } function decorate(item) { const field = item.field; if (field.dataset.tpPortReady) return; field.dataset.tpPortReady = '1'; field.dataset.tpOriginal = field.value.trim(); const wrap = element('span', 'tp-port-field'); field.before(wrap); wrap.append(field); const mark = element('span', 'tp-port-mark'); mark.setAttribute('aria-hidden', 'true'); wrap.append(mark); const message = element('span', 'tp-port-message'); message.id = 'tp-port-message-' + Math.random().toString(36).slice(2); message.setAttribute('aria-live', 'polite'); field.setAttribute('aria-describedby', message.id); const line = wrap.closest('.flex-row') || wrap.parentElement; line.after(message); field.addEventListener('input', run); } function run() { const rows = portRows(); rows.forEach(decorate); rows.forEach(function (item) { const result = check(item, rows); const wrap = item.field.parentElement; const message = document.getElementById(item.field.getAttribute('aria-describedby')); wrap.classList.toggle('tp-port-ok', result[0] === true); wrap.classList.toggle('tp-port-bad', result[0] === false); item.field.setAttribute('aria-invalid', result[0] === false ? 'true' : 'false'); wrap.title = result[1]; if (message) { message.textContent = result[0] === false ? result[1] : ''; message.hidden = result[0] !== false; } }); renderAllocations(); } // The same check for the host port in the "Add / Edit Configuration" dialog. function dialogCheck(event) { const field = event.target; if (!field.matches || !field.matches('.ui-dialog input[name="Value"]')) return; const dialog = field.closest('.ui-dialog'); const type = dialog.querySelector('select[name="Type"]'); const wrap = field.closest('.tp-port-field'); if (!type || type.value !== 'Port') { if (wrap) wrap.classList.remove('tp-port-ok', 'tp-port-bad'); return; } let holder = wrap; if (!holder) { holder = element('span', 'tp-port-field'); field.before(holder); holder.append(field, element('span', 'tp-port-mark')); field.focus(); } const mode = dialog.querySelector('select[name="Mode"]'); const editing = dialog.querySelector('.ui-dialog-title'); const item = { field: field, name: '', proto: mode ? mode.value : 'tcp', required: true }; const result = check(item, portRows().filter(function (row) { // When editing, the row being edited is not a duplicate of itself. return !(editing && /edit/i.test(editing.textContent) && row.field.dataset.tpOriginal === field.defaultValue); })); holder.classList.toggle('tp-port-ok', result[0] === true); holder.classList.toggle('tp-port-bad', result[0] === false); holder.title = result[1]; } document.addEventListener('input', dialogCheck); // "Show docker allocations": Unraid's plain list becomes a filterable view with one row // per container: network badge, address and its ports as chips (runs of ports such as // 25500-25599 as one chip). Ports that clash with this form's ports are marked red. const allocationBox = document.getElementById('dockerAllocations'); let allocationFilter = ''; const portRanges = function (ports) { const sorted = [...new Set(ports)].sort(function (a, b) { return a - b; }); const ranges = []; sorted.forEach(function (port) { const last = ranges[ranges.length - 1]; if (last && port === last[1] + 1) last[1] = port; else ranges.push([port, port]); }); return ranges; }; const parseRange = function (text) { const parts = /^(\d+)(?:-(\d+))?$/.exec(String(text).trim()); return parts ? [Number(parts[1]), Number(parts[2] || parts[1])] : null; }; function renderAllocations() { if (!allocationBox || !allocations.length) return; let view = allocationBox.querySelector(':scope > .tp-alloc'); if (!view) { view = element('div', 'tp-alloc'); const head = element('div', 'tp-alloc-head'); const search = element('input', 'tp-alloc-search'); search.type = 'search'; search.placeholder = 'Filter by container, network, address or port…'; search.setAttribute('aria-label', 'Filter docker allocations'); search.value = allocationFilter; search.addEventListener('input', function () { allocationFilter = search.value; renderAllocations(); }); head.append(search, element('span', 'tp-alloc-count')); const columns = element('div', 'tp-alloc-row tp-alloc-columns'); ['Container', 'Network', 'Address', 'Ports'].forEach(function (label) { columns.append(element('span', '', label)); }); view.append(head, columns, element('div', 'tp-alloc-list')); allocationBox.prepend(view); } allocationBox.querySelectorAll(':scope > dl').forEach(function (list) { list.hidden = true; }); const current = nameField.value.trim().toLowerCase(); const clashCheck = sharesHostAddress(); const formRanges = portRows().map(function (item) { return parseRange(item.field.value); }).filter(Boolean); const words = allocationFilter.trim().toLowerCase().split(/\s+/).filter(Boolean); const list = view.querySelector('.tp-alloc-list'); list.replaceChildren(); let shown = 0; allocations.forEach(function (entry) { const ranges = portRanges(entry.ports); const portText = ranges.map(function (range) { return range[0] === range[1] ? String(range[0]) : range[0] + '-' + range[1]; }).join(' '); const haystack = (entry.name + ' ' + entry.network + ' ' + entry.ip + ' ' + portText).toLowerCase(); const match = words.every(function (word) { if (/^\d+$/.test(word) && ranges.some(function (range) { return Number(word) >= range[0] && Number(word) <= range[1]; })) return true; return haystack.includes(word); }); if (!match) return; shown += 1; const self = entry.name.toLowerCase() === current || entry.name.toLowerCase() === originalName; const row = element('div', 'tp-alloc-row' + (self ? ' tp-alloc-self' : '')); const name = element('span', 'tp-alloc-name', entry.name); if (self) name.append(element('span', 'tp-alloc-badge', 'This container')); const kind = entry.network === 'bridge' ? 'bridge' : entry.network === 'host' ? 'host' : 'custom'; const net = element('span', 'tp-alloc-net'); net.append(element('span', 'tp-alloc-pill tp-net-' + kind, entry.network || '?')); const ports = element('span', 'tp-alloc-ports'); if (!ranges.length) ports.append(element('span', 'tp-alloc-none', entry.network === 'host' ? 'Host network: uses the ports it opens' : 'No published ports')); ranges.forEach(function (range) { const chip = element('span', 'tp-alloc-port', range[0] === range[1] ? String(range[0]) : range[0] + '–' + range[1]); if (range[0] !== range[1]) { chip.classList.add('tp-alloc-range'); chip.title = (range[1] - range[0] + 1) + ' ports'; } const clash = clashCheck && !self && hostAddresses.has(entry.ip) && formRanges.some(function (own) { return own[0] <= range[1] && own[1] >= range[0]; }); if (clash) { chip.classList.add('tp-alloc-clash'); chip.title = 'Also used in this container\'s settings'; } ports.append(chip); }); row.append(name, net, element('span', 'tp-alloc-ip', entry.ip || '—'), ports); list.append(row); }); if (!shown) list.append(element('p', 'tp-alloc-empty', 'No container matches this filter.')); view.querySelector('.tp-alloc-count').textContent = (words.length ? shown + ' of ' : '') + allocations.length + ' containers'; } if (allocationBox) new MutationObserver(function () { // Unraid fills the box (again) with its own list; the view is put back on top. if (!allocationBox.querySelector(':scope > .tp-alloc') || allocationBox.querySelector(':scope > dl:not([hidden])')) renderAllocations(); }).observe(allocationBox, { childList: true }); if (network) network.addEventListener('change', run); nameField.addEventListener('input', run); // Ports added or removed through the dialog, and rows Unraid rebuilds. new MutationObserver(debounce(run)).observe(document.getElementById('configLocation'), { childList: true, subtree: true }); run(); if (!PLUGIN_BASE) return; fetch(PLUGIN_BASE + '/include/ports.php', { credentials: 'same-origin' }) .then(function (response) { return response.json(); }) .then(function (data) { listening = Array.isArray(data.listening) ? data.listening : []; run(); }) .catch(function (error) { console.error('Aurora Theme ports:', error); }); } // VMs: Unraid opens a VM's details (disks, interfaces, snapshots) when its name is clicked, // which is easy to miss. Each VM gets a chevron button in front of its icon that does the same, // and turns while the details are open. Unraid remembers which VMs are open. // VMs tab: the theme's groups for VMs, as on the Docker tab: collapsible sections in the // groups' order and then "Ungrouped", the VMs in Unraid's order, each section with its VMs' // icons and start / stop for all of them; "+ Group" above the list and a folder button on each // VM. Not while FolderView arranges the list. function vmGroups() { const list = document.getElementById('kvm_list'); const table = document.getElementById('kvm_table'); if (!list || !table || folderViewActive()) return; const anchor = table.closest('.TableContainer') || table; const UNGROUPED = '\u0000ungrouped'; const collapsed = collapsedState('tft-vm-collapsed'); const headers = new Map(); const store = groupStore('vm', render, function () { saveUnraidOrder('vm', list); }); const toolbar = element('div', 'tp-toolbar tp-vm-toolbar'); const tools = element('div', 'tp-tools'); const newGroupButton = element('button', 'tp-tab', '+ Group'); newGroupButton.type = 'button'; newGroupButton.title = 'Create a group of VMs'; newGroupButton.setAttribute('aria-selected', 'true'); newGroupButton.addEventListener('click', function () { store.create().then(function (group) { if (group) store.save(); }); }); tools.append(newGroupButton); toolbar.append(tools); const nameOf = function (row) { const link = row.querySelector('td.vm-name .inner > a'); return link ? link.textContent.trim() : ''; }; function describe(row) { const cell = row.querySelector('td.vm-name'); const trigger = cell.querySelector('span.hand[id^="vm-"]'); return { row: row, name: nameOf(row), uuid: trigger ? trigger.id.slice(3) : '', trigger: trigger, running: Boolean(cell.querySelector('i.started')), paused: Boolean(cell.querySelector('i.paused')), }; } // A VM's row and the rows under it (its details), up to the next VM or section. function rowsOf(item) { const rows = [item.row]; for (let next = item.row.nextElementSibling; next && !next.hasAttribute('parent-id') && !next.classList.contains('tp-group'); next = next.nextElementSibling) rows.push(next); return rows; } // Starts or stops several VMs one after another, then lets Unraid reload the list once. function runAll(action, items) { const jq = window.jQuery; if (!jq || !items.length) return; jq('div.spinner.fixed').show('slow'); let chain = Promise.resolve(); items.forEach(function (item) { chain = chain.then(function () { return new Promise(function (resolve) { jq.post('/plugins/dynamix.vm.manager/include/VMajax.php', { action: action, uuid: item.uuid }).always(resolve); }); }); }); chain.then(function () { if (typeof window.loadlist === 'function') window.loadlist(); else jq('div.spinner.fixed').hide('slow'); }); } // An actions column at the end of the table, as on the Docker tab: start, or stop and // restart (resume when paused), the VM's group, and Unraid's full menu (the VM's icon menu, // opened next to the button). The detail rows under a VM span the new column too. const headRow = table.querySelector('thead tr'); if (headRow && !headRow.querySelector('.tp-vm-actions')) headRow.append(element('th', 'tp-vm-actions')); function decorate(item) { if (item.row.querySelector(':scope > td.tp-vm-actions')) return; const dispatch = function (action) { return function () { if (typeof window.ajaxVMDispatch === 'function') window.ajaxVMDispatch({ action: action, uuid: item.uuid }, 'loadlist'); }; }; const actions = element('div', 'tp-actions'); if (item.paused) actions.append(iconButton('play', 'Resume ' + item.name, dispatch('domain-resume'), 'tp-primary')); else if (item.running) actions.append(iconButton('refresh', 'Restart ' + item.name, dispatch('domain-restart')), iconButton('stop', 'Stop ' + item.name, dispatch('domain-stop'), 'tp-danger')); else actions.append(iconButton('play', 'Start ' + item.name, dispatch('domain-start'), 'tp-primary')); actions.append(iconButton('folder-o', 'Group for ' + item.name, function (event) { event.stopPropagation(); store.openMenu(event.currentTarget, item.name); })); actions.append(iconButton('ellipsis-h', 'More actions for ' + item.name, function (event) { event.stopPropagation(); if (item.trigger) openMenuFrom(item.trigger, event.currentTarget); })); const cell = element('td', 'tp-vm-actions'); cell.append(actions); item.row.append(cell); } function headerFor(key, group) { if (headers.has(key)) return headers.get(key); const row = sectionHeader(store, group, { noun: 'VM', onToggle: function () { collapsed.toggle(key); render(); }, start: function (items) { runAll('domain-start', items); }, stop: function (items) { runAll('domain-stop', items); }, }); headers.set(key, row); return row; } function render() { if (list.tpSorting) return; const items = [...list.querySelectorAll(':scope > tr[parent-id]')].map(describe).filter(function (item) { return item.name; }); if (!items.length) return; if (!toolbar.isConnected) anchor.before(toolbar); list.querySelectorAll(':scope > tr:not([parent-id]):not(.tp-group) > td[colspan="8"]').forEach(function (cell) { cell.colSpan = 9; }); items.forEach(decorate); const sections = store.groups.map(function (group) { return { key: 'g:' + group.name, group: group, title: group.name, items: items.filter(function (item) { return group.containers.includes(item.name); }) }; }); if (sections.length) { const rest = items.filter(function (item) { return !store.of(item.name); }); if (rest.length) sections.push({ key: UNGROUPED, group: null, title: 'Ungrouped', items: rest }); } const wanted = new Set(sections.map(function (section) { return section.key; })); headers.forEach(function (row, key) { if (!wanted.has(key)) { row.remove(); headers.delete(key); } }); if (!sections.length) { list.querySelectorAll(':scope > tr.tp-filtered-out').forEach(function (row) { row.classList.remove('tp-filtered-out'); }); return; } // Each VM's rows are collected before anything moves. const order = []; sections.forEach(function (section, index) { const header = headerFor(section.key, section.group); header.tpItems = section.items; header.tpKey = section.key; section.index = index; section.collapsed = collapsed.has(section.key); updateSectionHeader(header, section, { noun: 'VM', groupCount: store.groups.length, app: function (item) { const image = item.trigger && item.trigger.querySelector('img'); return { name: item.name, src: image && image.src, state: item.paused ? 'paused' : item.running ? 'running' : 'stopped', target: item.trigger }; }, }); order.push(header); section.items.forEach(function (item) { rowsOf(item).forEach(function (row) { row.classList.toggle('tp-filtered-out', section.collapsed); order.push(row); }); }); }); order.forEach(function (node, index) { if (list.children[index] !== node) list.insertBefore(node, list.children[index] || null); }); } // Unraid replaces the rows on every refresh; they are grouped again before they are painted. new MutationObserver(render).observe(list, { childList: true }); dropIntoSections(list, store, nameOf, UNGROUPED, render); roomToDragToTop(list); store.load(); render(); } function vmDetails() { const list = document.getElementById('kvm_list'); if (!list || typeof window.toggle_id !== 'function') return; function decorate() { list.querySelectorAll('tr[parent-id]').forEach(function (row) { // A FolderView folder row looks like a VM's (parent-id and a details row) but is not one, // and the rows in a closed folder's store are FolderView's. if (row.dataset.tpExpand || row.classList.contains('folder') || row.closest('.folder-storage')) return; const id = 'name-' + row.getAttribute('parent-id'); const details = document.getElementById(id); const cell = row.querySelector('td.vm-name'); if (!details || !cell) return; row.dataset.tpExpand = '1'; const nameLink = cell.querySelector('.inner > a'); const name = nameLink ? nameLink.textContent.trim() : 'this VM'; const button = element('button', 'tp-vm-expand'); button.type = 'button'; button.setAttribute('aria-controls', id); button.append(element('i', 'fa fa-chevron-right')); button.addEventListener('click', function (event) { event.preventDefault(); event.stopPropagation(); window.toggle_id(id); }); const outer = cell.querySelector('span.outer'); if (outer) outer.before(button); else cell.prepend(button); // The details are this row and the rows after it, up to the next VM. for (let next = details; next && !next.hasAttribute('parent-id') && !next.classList.contains('tp-group'); next = next.nextElementSibling) next.classList.add('tp-vm-details'); snapshotTools(row, details); const sync = function () { const open = details.style.display !== 'none'; button.classList.toggle('tp-open', open); row.classList.toggle('tp-vm-open', open); button.setAttribute('aria-expanded', open ? 'true' : 'false'); button.title = (open ? 'Hide details of ' : 'Show disks, interfaces and snapshots of ') + name; button.setAttribute('aria-label', button.title); }; new MutationObserver(sync).observe(details, { attributes: true, attributeFilter: ['style'] }); sync(); }); } // Snapshots in the details: a "Create snapshot" button in the section heading (the section // is added when the VM has none yet), and Revert / Remove / more buttons on each snapshot. // Everything calls Unraid's own functions, the same ones its menus use. function snapshotTools(row, details) { const trigger = row.querySelector('[onclick*="addVMContext"]'); const vm = contextArguments(trigger); const inner = details.querySelector('table'); if (vm.length < 4 || !inner || typeof window.selectsnapshot !== 'function') return; const name = vm[0]; const uuid = vm[1]; const state = vm[3]; const fstype = vm[7] || 'QEMU'; const small = function (icon, label, run, extra) { const button = element('button', 'tp-snap-button' + (extra ? ' ' + extra : '')); button.type = 'button'; button.title = label; button.setAttribute('aria-label', label); button.append(element('i', 'fa fa-' + icon)); button.addEventListener('click', function (event) { event.preventDefault(); event.stopPropagation(); run(event); }); return button; }; let heading = [...inner.querySelectorAll('thead th')].find(function (cell) { return cell.querySelector('i.fa-clone'); }); if (!heading) { const head = element('thead', 'child tp-snap-added'); const headRow = element('tr'); heading = element('th'); heading.colSpan = 6; heading.innerHTML = ' Snapshots'; headRow.append(heading); head.append(headRow); const body = element('tbody', 'child tp-snap-added'); const emptyRow = element('tr'); const emptyCell = element('td', 'tp-snap-empty', 'No snapshots yet.'); emptyCell.colSpan = 6; emptyRow.append(emptyCell); body.append(emptyRow); inner.append(head, body); } const create = element('button', 'tp-snap-create'); create.type = 'button'; create.append(element('i', 'fa fa-plus'), document.createTextNode(' Create snapshot')); create.addEventListener('click', function (event) { event.preventDefault(); event.stopPropagation(); window.selectsnapshot(uuid, name, '--generate', 'create', false, state, fstype); }); heading.append(create); inner.querySelectorAll('[id^="vmsnap-"][onclick*="addVMSnapContext"]').forEach(function (span) { const snap = contextArguments(span); const cell = span.closest('td'); if (snap.length < 5 || !cell || cell.querySelector('.tp-snap-actions')) return; const snapshot = snap[4]; const method = snap[5] || 'QEMU'; const busy = function () { if (window.jQuery) window.jQuery('#vm-' + uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); }; const actions = element('span', 'tp-snap-actions'); actions.append( small('fast-backward', 'Revert ' + name + ' to snapshot ' + snapshot, function () { window.selectsnapshot(uuid, name, snapshot, 'revert', true, state, method); }), small('trash', 'Remove snapshot ' + snapshot, function () { busy(); window.selectsnapshot(uuid, name, snapshot, 'remove', true); }, 'tp-snap-danger'), // Block commit / pull and the rest stay in Unraid's menu, opened here as well. small('ellipsis-h', 'More actions for snapshot ' + snapshot, function (event) { span.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, clientX: event.clientX, clientY: event.clientY })); }) ); cell.append(actions); }); } decorate(); new MutationObserver(debounce(decorate)).observe(list, { childList: true }); } // Main: temperatures and error counts in the device tables become small coloured chips. // Unraid redraws the rows every few seconds, so new rows are marked again as they come. function mainTables() { const tables = [...document.querySelectorAll('table.disk_status, table.usb_mounts')]; if (!tables.length) return; const wrap = function (cell, text) { const chip = element('span', 'tp-chip'); if (text !== undefined) chip.textContent = text; else while (cell.firstChild) chip.append(cell.firstChild); cell.replaceChildren(chip); }; tables.forEach(function (table) { const headings = [...table.querySelectorAll('thead tr:first-child > td, thead tr:first-child > th')].map(function (cell) { return cell.textContent.trim().toLowerCase(); }); const tempAt = headings.findIndex(function (text) { return text.startsWith('temp'); }); const errorsAt = headings.indexOf('errors'); const mark = function () { // The cell under a given heading, counting merged cells (colspan) as several columns. const cellAt = function (row, column) { let position = 0; for (const cell of row.children) { const span = cell.colSpan || 1; if (column < position + span) return span === 1 ? cell : null; position += span; } return null; }; table.querySelectorAll('tbody tr').forEach(function (row) { const temp = tempAt >= 0 && cellAt(row, tempAt); if (temp && !temp.dataset.tpDone) { temp.dataset.tpDone = '1'; const text = temp.textContent.trim(); const match = /^(\d+(?:[.,]\d+)?)\s*°\s*([CF])$/.exec(text); if (match) { const celsius = match[2] === 'F' ? (parseFloat(match[1]) - 32) * 5 / 9 : parseFloat(match[1]); temp.classList.add('tp-temp'); temp.dataset.level = celsius >= 55 ? 'hot' : celsius >= 45 ? 'warm' : 'ok'; wrap(temp); } else if (text === '*') { // Unraid shows "*" for a disk in standby. temp.classList.add('tp-temp'); temp.dataset.level = 'idle'; temp.title = 'Spun down'; wrap(temp, 'Standby'); } } const errors = errorsAt >= 0 && cellAt(row, errorsAt); if (errors && !errors.dataset.tpDone) { errors.dataset.tpDone = '1'; // Only the visible number counts; a warning icon's tooltip text may hold digits too. const visible = [...errors.childNodes].filter(function (node) { return node.nodeType === Node.TEXT_NODE; }).map(function (node) { return node.textContent; }).join(''); const count = parseInt(visible.replace(/[^\d]/g, ''), 10); if (count > 0) { const tooltip = errors.querySelector('a.info > span'); errors.classList.add('tp-errors'); if (tooltip) errors.title = tooltip.textContent.trim(); wrap(errors, String(count)); } } }); }; mark(); // Marked straight away, not after a delay: observer callbacks run before the browser // paints, so the redrawn rows never show unmarked (no flicker). Marked cells are skipped, // so the observer's own changes end the loop. new MutationObserver(mark).observe(table, { childList: true, subtree: true }); }); } // Main, Array Operation: Unraid lays the actions out as table rows (status, button, // explanation). The rows become cards in one grid, grouped by purpose, each with an icon and // a title: Array, Parity check, Last check, Disks, Mover, Power. Nothing is moved out of its // form, and the cells keep their ids, so Unraid's own scripts work unchanged. function arrayOperations() { const tables = [...document.querySelectorAll('#displaybox table.ArrayOperation-Table')]; if (!tables.length || document.querySelector('.tp-ops')) return; const outer = function (table) { return table.closest('form') || table; }; const first = outer(tables[0]); const last = outer(tables[tables.length - 1]); if (first.parentElement !== last.parentElement) return; const grid = element('div', 'tp-ops'); first.before(grid); // Empty paragraphs left by Unraid's markdown would push the cards down. for (let node = grid.previousElementSibling; node && node.matches('p, script'); node = node.previousElementSibling) { if (node.matches('p') && !node.textContent.trim()) node.hidden = true; } for (let node = first; node;) { const next = node.nextSibling; grid.append(node); if (node === last) break; node = next; } // A running parity check (or rebuild, clear, read check) adds one row per figure: total // size, elapsed time, position, speed, finish, errors. They are gathered as tiles, with a // progress bar, into the card of the running operation. Unraid updates the figures by the // ids of their cells (#line0 ...), so the cells themselves are moved, ids intact. const statRows = [...grid.querySelectorAll('table.ArrayOperation-Table > tbody > tr')].filter(function (row) { return row.querySelector('td[id^="line"]'); }); if (statRows.length) { const stats = element('td', 'tp-op-stats'); const bar = element('div', 'tp-progress'); const fill = element('span'); const percent = element('span', 'tp-progress-label'); bar.append(fill); const tiles = element('div', 'tp-stats'); statRows.forEach(function (row) { const value = row.querySelector('td[id^="line"]'); const tile = element('div', 'tp-stat'); tile.append(element('span', 'tp-stat-label', row.children[0].textContent.trim().replace(/:$/, ''))); value.classList.add('tp-stat-value'); tile.append(value); tiles.append(tile); row.classList.add('tp-op-skip'); }); stats.append(percent, bar, tiles); const position = stats.querySelector('#line2'); const progress = function () { const match = position && /([\d.,]+)\s*%/.exec(position.textContent); const value = match ? Math.max(0, Math.min(100, parseFloat(match[1].replace(',', '.')))) : null; bar.hidden = value === null; percent.textContent = value === null ? '' : value.toFixed(1) + ' %'; if (value !== null) fill.style.width = value + '%'; }; if (position) new MutationObserver(progress).observe(position, { childList: true, characterData: true, subtree: true }); progress(); const host = grid.querySelector('#pauseButton, #cancelButton'); if (host) host.closest('tr').append(stats); else { const row = element('tr'); row.append(element('td'), element('td'), stats); statRows[0].before(row); } } // Mover status (Unraid 7.3.3 and later): up to five rows (file being processed, action, // progress, transfers) that Unraid shows and hides while the mover runs. They become tiles // in the Mover card that follow their row's hidden state; the cells keep their ids, which // Unraid writes to. const moverRows = [...grid.querySelectorAll('tr[id^="moverrow"]')]; const moverButton = grid.querySelector('#mover-button'); if (moverRows.length && moverButton) { const stats = element('td', 'tp-op-stats tp-op-mover-stats'); const bar = element('div', 'tp-progress'); const fill = element('span'); const percent = element('span', 'tp-progress-label'); bar.append(fill); const tiles = element('div', 'tp-stats'); const syncs = []; moverRows.forEach(function (row, index) { const label = row.children[1]; const value = row.children[2]; if (!label || !value) return; const tile = element('div', 'tp-stat' + (index === 0 ? ' tp-stat-wide' : '')); label.classList.add('tp-stat-label'); value.classList.add('tp-stat-value'); tile.append(label, value); tiles.append(tile); row.classList.add('tp-op-skip'); syncs.push(function () { tile.hidden = row.hidden; }); new MutationObserver(update).observe(row, { attributes: true, attributeFilter: ['hidden'] }); new MutationObserver(update).observe(value, { childList: true, characterData: true, subtree: true }); }); function update() { syncs.forEach(function (sync) { sync(); }); const visible = [...tiles.children].some(function (tile) { return !tile.hidden; }); stats.hidden = !visible; moverButton.closest('tr').classList.toggle('tp-op-busy', visible); const progress = grid.querySelector('#moverline2'); const match = progress && /([\d.,]+)\s*%/.exec(progress.textContent); const value = match ? Math.max(0, Math.min(100, parseFloat(match[1].replace(',', '.')))) : null; bar.hidden = value === null; percent.textContent = value === null ? '' : value.toFixed(1) + ' %'; if (value !== null) fill.style.width = value + '%'; } stats.append(percent, bar, tiles); moverButton.closest('tr').append(stats); update(); } const KINDS = [ ['running', 'Operation in progress', 'tachometer', '#pauseButton, #cancelButton'], ['array', 'Array', 'hdd-o', '#stop-button, [name="cmdStart"], #cmdStart, [name="startMode"], [name="cmdStop"]'], ['parity', 'Parity check', 'shield', '[name="cmdCheck"], [name^="cmdCheck"], [name="cmdNoCheck"], [name="optionCorrect"]'], ['history', 'Last check', 'history', 'input[onclick*="parityHistory"]'], ['disks', 'Disks', 'refresh', '#spinup-button, #spindown-button'], ['mover', 'Mover', 'exchange', '#mover-button'], ['power', 'Power', 'power-off', '[name="reboot"], [name="shutdown"]'], ]; const DANGER = /^(stop|reboot|shutdown|cancel)$/i; const PRIMARY = /^(start|check|move|resume)$/i; const blank = function (cell) { return !cell.textContent.trim() && !cell.querySelector('input, select, button, img, i'); }; // Stopped array: Unraid puts "Maintenance mode" in a row of its own under Start, which made // a second "Array" card. Its checkbox moves into the Start card's description, where it // belongs; it stays in the same form, so Start still sends it. const startButton = grid.querySelector('[name="cmdStart"], #cmdStart'); const startMode = grid.querySelector('input[type="checkbox"][name="startMode"]'); const modeRow = startMode && startMode.closest('tr'); if (startButton && modeRow && !modeRow.contains(startButton) && startButton.form === startMode.form) { const target = startButton.closest('tr').lastElementChild; const label = startMode.closest('label') || startMode; const hint = modeRow.lastElementChild ? modeRow.lastElementChild.textContent.replace(/\s+/g, ' ').trim() : ''; if (hint) label.title = hint; target.append(label); modeRow.classList.add('tp-op-skip'); } grid.querySelectorAll('table.ArrayOperation-Table > tbody > tr').forEach(function (row) { const cells = [...row.children]; if (row.classList.contains('tp-op-skip') || row.querySelector('td.line') || cells.every(blank)) { row.classList.add('tp-op-skip'); return; } let kind = KINDS.find(function (entry) { return row.querySelector(entry[3]); }); if (!kind && row.id === 'clearstats') kind = ['stats', 'Statistics', 'eraser']; if (!kind) { const strong = row.querySelector('td:last-child strong, td:last-child b'); const button = row.querySelector('input[type="button"], input[type="submit"], button'); kind = row.querySelector('.tp-op-stats') ? ['running', 'Progress', 'tachometer'] : ['other', (strong && strong.textContent.trim()) || (button && button.value) || 'Action', 'cog']; } kind = kind.slice(); if (kind[0] === 'running') { // "Parity-Check in progress." gives the card its title. const first = cells[0] ? cells[0].textContent.trim().replace(/\s+in progress\.?$/i, '') : ''; if (first) kind[1] = first; } if (kind[0] === 'history' && /current operation/i.test(row.textContent)) kind[1] = 'History'; row.classList.add('tp-op', 'tp-op-' + kind[0]); const roles = cells.length >= 3 ? ['state', 'actions', 'desc'] : ['actions', 'desc']; cells.forEach(function (cell, index) { if (cell.classList.contains('tp-op-stats')) return; cell.classList.add('tp-op-' + (roles[index] || 'desc')); if (blank(cell)) cell.classList.add('tp-op-blank'); }); const head = element('td', 'tp-op-head'); const icon = element('span', 'tp-op-icon'); icon.append(element('i', 'fa fa-' + kind[2])); head.append(icon, element('span', 'tp-op-title', kind[1])); row.prepend(head); row.querySelectorAll('input[type="button"], input[type="submit"]').forEach(function (button) { if (DANGER.test(button.value.trim())) button.classList.add('tp-op-danger'); else if (PRIMARY.test(button.value.trim())) button.classList.add('tp-op-primary'); }); if (kind[0] === 'parity') { const state = row.querySelector('td.tp-op-state'); if (state && state.textContent.trim()) state.classList.add(/invalid|error|disabled/i.test(state.textContent) ? 'tp-op-warn' : 'tp-op-good'); } }); } // Users tab: Unraid's avatar tiles become cards (avatar, name, role, description), grouped // as Administrators and Share users, with an overview strip, a search field in the heading // row and an "Add user" button. Default avatars become coloured initials. The cards stay // Unraid's links to each user's page; "Add user" submits Unraid's own form. function usersPage(info) { if (info.tab !== 'Users' || info.subPage) return; const cards = [...document.querySelectorAll('#displaybox .Panels > .user-list')]; if (!cards.length || document.querySelector('.tp-users')) return; const box = document.getElementById('displaybox'); // The "Users" title bar repeats the page heading. const firstTitle = box.querySelector(':scope > div.title, .content > div.title'); if (firstTitle && /^users$/i.test(firstTitle.textContent.trim()) && box.querySelector('.tp-heading')) firstTitle.classList.add('tp-hidden-title'); const form = [...box.querySelectorAll('form')].find(function (candidate) { return /UserAdd/.test(candidate.getAttribute('action') || ''); }); const addSubmit = form && form.querySelector('input[type="submit"]'); const addUser = function () { if (!form) return; if (form.requestSubmit) form.requestSubmit(addSubmit || undefined); else form.submit(); }; if (form) form.classList.add('tp-users-form'); const palette = [['#4f8bff', '#22d3ee'], ['#a78bfa', '#f472b6'], ['#34d399', '#22d3ee'], ['#fb923c', '#f43f5e'], ['#f59e0b', '#84cc16'], ['#38bdf8', '#818cf8']]; const hash = function (text) { let value = 0; for (const char of text) value = (value * 31 + char.charCodeAt(0)) >>> 0; return value; }; const people = []; box.querySelectorAll('.Panels').forEach(function (group) { const items = [...group.children].filter(function (child) { return child.matches('.user-list'); }); if (!items.length) return; const title = group.previousElementSibling && group.previousElementSibling.matches('div.title') ? group.previousElementSibling : null; const admin = title && /management/i.test(title.textContent); group.classList.add('tp-users'); if (title) { title.classList.add('tp-users-title'); const left = title.querySelector('.left') || title; const label = admin ? 'Administrators' : 'Share users'; const icon = left.querySelector('i'); left.replaceChildren(); if (icon) left.append(icon); left.append(document.createTextNode(label), element('span', 'tp-users-count', String(items.length))); title.title = admin ? 'Can log in to the console and this web interface' : 'Can reach the shares they are given access to'; } items.forEach(function (item) { const link = item.querySelector('a'); if (!link) return; const picture = link.querySelector('img'); const detail = link.querySelector('span'); const description = detail ? detail.textContent.trim() : ''; const name = [...link.childNodes].filter(function (node) { return node.nodeType === Node.TEXT_NODE; }).map(function (node) { return node.textContent; }).join(' ').trim(); const avatar = element('span', 'tp-user-avatar'); if (picture && !/\/user\.png(\?|$)/.test(picture.getAttribute('src') || '')) { picture.removeAttribute('width'); picture.removeAttribute('height'); avatar.append(picture); } else { const colours = palette[hash(name) % palette.length]; avatar.classList.add('tp-user-initials'); avatar.style.background = 'linear-gradient(135deg, ' + colours[0] + ', ' + colours[1] + ')'; avatar.textContent = (name.match(/[A-Za-z0-9]/g) || ['?']).slice(0, 2).join('').toUpperCase(); } const text = element('span', 'tp-user-text'); const top = element('span', 'tp-user-top'); top.append(element('strong', 'tp-user-name', name), element('span', 'tp-user-role' + (admin ? ' tp-admin' : ''), admin ? 'Administrator' : 'Share access')); text.append(top, element('span', 'tp-user-desc', description || (admin ? 'Console and web interface login' : 'No description'))); if (!description) text.lastChild.classList.add('tp-user-desc-empty'); const go = element('span', 'tp-user-go'); go.append(element('i', 'fa fa-chevron-right')); link.className = 'tp-user-card'; link.title = 'Edit ' + name; link.replaceChildren(avatar, text, go); item.classList.add('tp-user'); people.push({ item: item, text: (name + ' ' + description).toLowerCase(), admin: admin }); }); if (!admin && form) { const add = element('button', 'tp-user-add'); add.type = 'button'; add.append(element('span', 'tp-user-add-icon', '+'), element('span', '', 'Add user')); add.addEventListener('click', addUser); group.append(add); } }); const heading = box.querySelector('.tp-heading'); const strip = summaryStrip('tp-users-summary', 'Users overview', [ { label: 'Accounts', value: people.length, detail: 'in total' }, { label: 'Administrators', value: people.filter(function (person) { return person.admin; }).length, detail: 'web interface' }, { label: 'Share users', value: people.filter(function (person) { return !person.admin; }).length, detail: 'share access', tone: 'good' }, ]); if (heading) heading.after(strip); if (heading) { const tools = element('div', 'tp-users-tools'); const search = element('input', 'tp-panel-search-input tp-users-search'); search.type = 'search'; search.placeholder = 'Search users…'; search.setAttribute('aria-label', 'Search users'); const wrap = element('div', 'tp-panel-search'); wrap.append(search); tools.append(wrap); if (form) { const add = element('button', 'tp-users-add'); add.type = 'button'; add.append(element('i', 'fa fa-user-plus'), document.createTextNode(' Add user')); add.addEventListener('click', addUser); tools.append(add); } heading.append(tools); search.addEventListener('input', function () { const words = search.value.trim().toLowerCase().split(/\s+/).filter(Boolean); people.forEach(function (person) { person.item.classList.toggle('tp-filtered-out', !words.every(function (word) { return person.text.includes(word); })); }); }); } } // Plugins: Unraid checks every plugin for updates once per visit, with a single download that // is not retried, so now and then a row ends on "not available" or "cannot check" although // the plugin is fine. Such a row is checked again in the background, once per plugin and // visit, with Unraid's own "plugin check" (include/plugin-recheck.php, up to three tries). // Unraid's list code then redraws the row from the file that check left behind. function pluginRecheck() { const list = document.getElementById('plugin_list'); if (!list || typeof window.updateInfo !== 'function') return; const SHOW = '/plugins/dynamix.plugin.manager/include/ShowPlugins.php'; const tried = new Set(); const queue = []; let active = 0; // Failed rows by their icon, so this works in every language: the broken link of "not // available" and the red warning of "cannot check". function failed(cell) { return cell.querySelector('i.fa-unlink, .red-text > i.fa-exclamation-triangle'); } function next() { while (active < 2 && queue.length) recheck(queue.shift()); } function recheck(cell) { const box = cell.closest('tr') && cell.closest('tr').querySelector('input.remove[data$=".plg"]'); const plg = box ? box.getAttribute('data') : ''; if (!plg) return; const before = cell.innerHTML; const id = cell.id.replace(/^sid-/, ''); active++; cell.innerHTML = " checking..."; const body = new URLSearchParams({ plugin: plg, csrf_token: window.csrf_token || '' }); fetch(PLUGIN_BASE + '/include/plugin-recheck.php', { method: 'POST', credentials: 'same-origin', body: body }) .then(function (response) { return response.json(); }) .then(function (result) { if (!result.ok) throw new Error('check failed'); // "return" draws every row from the files the checks left, without downloading // again; only this plugin's two cells are taken from it. return fetch(SHOW + '?' + new URLSearchParams({ audit: plg.replace(/\.plg$/, '') + ':return' }), { credentials: 'same-origin', cache: 'no-store' }); }) .then(function (response) { return response.text(); }) .then(function (text) { const parts = text.split('\0'); const lines = parts[0].split('\n').filter(function (line) { return line.indexOf('vid-' + id + '::') === 0 || line.indexOf('sid-' + id + '::') >= 0; }); if (!lines.length) throw new Error('row not found'); window.updateInfo(lines.join('\n')); // Unraid shows "Update all" when more than one plugin has an update. if (window.jQuery) { if (parseInt(parts[1], 10) > 1) window.jQuery('#updateall').show(); window.jQuery('#plugin_table').trigger('update'); } }) .catch(function () { cell.innerHTML = before; }) .finally(function () { active--; next(); }); } function scan() { list.querySelectorAll('td[id^="sid-"]').forEach(function (cell) { if (tried.has(cell.id) || !failed(cell)) return; tried.add(cell.id); queue.push(cell); }); next(); } // Unraid fills the status cells when its check returns; "Check For Updates" starts over, // and then each plugin may be checked again once. new MutationObserver(scan).observe(list, { childList: true, subtree: true }); document.addEventListener('click', function (event) { if (event.target.closest && event.target.closest('#checkall')) tried.clear(); }, true); scan(); } function start() { const info = pageInfo(); [function () { topBar(info); }, themeSwitch, function () { serverCard(info); }, function () { pageHeading(info); }, function () { folderViewBeta(info); }, function () { panelFavourites(info); }, function () { panelSearch(info); }, function () { usersPage(info); }, selectLists, lockState, docker, dockerPorts, vmViewSwitch, vmGroups, vmDetails, mainTables, arrayOperations, dashboardSummary, dashboardCharts, dashboardCpu, dashboardCores, dashboardContainers, dashboardUpdates, dockerUpdateWatch, dashboardDockerOrder, dashboardVmGroups, dashboardMemory, dashboardGpu, dashboardVmUsage, dashboardFans, diskLocationBays, sectionRows, externalLinkDialog, caOverlay, helpButton, navTitles, usageBar, navProgress, pluginRecheck, function () { toolText(info); }].forEach(function (enhancer) { // One failing enhancer must not stop the others or the page's own scripts. try { enhancer(); } catch (error) { console.error('Aurora Theme:', error); } }); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start(); })(); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/scripts/schedule.php <<'AURORA_THEME_EOF' #!/usr/bin/php /dev/null\n"; } file_put_contents('/boot/config/plugins/' . AURORA_PLUGIN . '/autoupdate.cron', $entry . "\n"); exec('/usr/local/sbin/update_cron'); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/scripts/stats.js <<'AURORA_THEME_EOF' // Stats page of the Aurora Theme. Data comes from include/stats.php: stored history for // the hour / day / week / month ranges, or a fresh one-second measurement for "Live". (function () { 'use strict'; const root = document.getElementById('tft-stats'); if (!root) return; const RANGES = [['live', 'Live'], ['hour', 'Last hour'], ['day', 'Last 24 hours'], ['week', 'Last 7 days'], ['month', 'Last 31 days']]; // Point layout from the endpoint: [time, cpu %, memory %, rx B/s, tx B/s, read B/s, write B/s] const CHARTS = [ { title: 'CPU load', unit: '%', max: 100, series: [{ name: 'CPU', column: 1, scale: 1 }] }, { title: 'Memory', unit: '%', max: 100, series: [{ name: 'Memory', column: 2, scale: 1 }] }, { title: 'Network', unit: 'Mbps', series: [{ name: 'Download', column: 3, scale: 8 / 1e6 }, { name: 'Upload', column: 4, scale: 8 / 1e6 }] }, { title: 'Disk I/O', unit: 'MB/s', series: [{ name: 'Read', column: 5, scale: 1 / 1e6 }, { name: 'Write', column: 6, scale: 1 / 1e6 }] }, ]; const LIVE_POINTS = 120; const LIVE_INTERVAL = 3000; const HISTORY_INTERVAL = 60000; let range = 'hour'; let points = []; let timer; let request = 0; function element(tag, className, text) { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; } // ---- Static page structure const toolbar = element('div', 'tp-toolbar'); const tabs = element('div', 'tp-tabs'); tabs.setAttribute('role', 'tablist'); tabs.setAttribute('aria-label', 'Time range'); RANGES.forEach(function (entry) { const tab = element('button', 'tp-tab', entry[1]); tab.type = 'button'; tab.setAttribute('role', 'tab'); tab.dataset.range = entry[0]; tab.addEventListener('click', function () { setRange(entry[0]); }); tabs.append(tab); }); toolbar.append(tabs); const summary = element('section', 'tp-summary'); summary.setAttribute('aria-label', 'Statistics summary'); const note = element('p', 'tfs-note'); const grid = element('div', 'tfs-grid'); const hosts = CHARTS.map(function (chart) { const card = element('section', 'tfs-card'); const head = element('div', 'tfs-card-head'); const title = element('h2', 'tfs-card-title', chart.title + ' '); title.append(element('small', '', chart.unit)); head.append(title); if (chart.series.length > 1) { const legend = element('div', 'tfs-legend'); chart.series.forEach(function (series, index) { const item = element('span', '', series.name); const swatch = element('i', 'tfs-swatch'); swatch.style.setProperty('--tfs-series', 'var(--tfs-series-' + (index + 1) + ')'); item.prepend(swatch); legend.append(item); }); head.append(legend); } const host = element('div', 'tfs-chart'); card.append(head, host); grid.append(card); return host; }); root.append(toolbar, summary, note, grid); renderDisks(); // ---- Formatting const timeFormat = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }); const secondFormat = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' }); const dayFormat = new Intl.DateTimeFormat(undefined, { weekday: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); function formatTime(seconds) { const date = new Date(seconds * 1000); return (range === 'live' ? secondFormat : range === 'week' || range === 'month' ? dayFormat : timeFormat).format(date); } function formatValue(value) { return value >= 100 ? value.toFixed(0) : value >= 10 ? value.toFixed(1) : value.toFixed(2); } function formatSize(kib) { const units = ['KB', 'MB', 'GB', 'TB', 'PB']; let value = kib; let unit = 0; while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit += 1; } return (value >= 100 ? value.toFixed(0) : value.toFixed(1)) + ' ' + units[unit]; } // 1, 2 or 5 times a power of ten: a readable top for the value axis. function niceMax(value) { if (value <= 0) return 1; const power = Math.pow(10, Math.floor(Math.log10(value))); const step = [1, 2, 5, 10].find(function (candidate) { return candidate * power >= value; }); return step * power; } // ---- Charts function drawChart(host, chart) { const data = chart.series.map(function (series) { return points.map(function (point) { return point[series.column] * series.scale; }); }); const times = points.map(function (point) { return point[0]; }); const width = host.clientWidth || 600; const height = 190; const pad = { left: 38, right: 6, top: 8, bottom: 22 }; const plotWidth = width - pad.left - pad.right; const plotHeight = height - pad.top - pad.bottom; const max = chart.max || niceMax(Math.max.apply(null, data.flat().concat(0))); const first = times[0]; const span = Math.max(1, times[times.length - 1] - first); const x = function (index) { return pad.left + (times.length > 1 ? (times[index] - first) / span : 0.5) * plotWidth; }; const y = function (value) { return pad.top + (1 - Math.min(value, max) / max) * plotHeight; }; const path = function (values) { return values.map(function (value, index) { return (index ? 'L' : 'M') + x(index).toFixed(1) + ' ' + y(value).toFixed(1); }).join(''); }; const gridLines = [0, 0.25, 0.5, 0.75, 1].map(function (step) { const gy = y(max * step); return '' + '' + Number((max * step).toPrecision(3)) + ''; }).join(''); host.innerHTML = '' + gridLines + '' + ''; const svg = host.querySelector('svg'); svg.setAttribute('aria-label', chart.title + ' in ' + chart.unit + ' over time'); if (!times.length) { host.append(element('div', 'tfs-empty', range === 'live' ? 'Measuring…' : 'No data stored for this range yet.')); return; } const namespace = 'http://www.w3.org/2000/svg'; const ticks = svg.querySelector('.tfs-ticks'); (plotWidth < 380 ? [0, 0.5, 1] : [0, 0.25, 0.5, 0.75, 1]).forEach(function (step) { const label = document.createElementNS(namespace, 'text'); label.setAttribute('class', 'tfs-axis'); label.setAttribute('x', pad.left + step * plotWidth); label.setAttribute('y', height - 5); label.setAttribute('text-anchor', step === 0 ? 'start' : step === 1 ? 'end' : 'middle'); label.textContent = formatTime(first + step * span); ticks.append(label); }); const marks = svg.querySelector('.tfs-marks'); const hover = svg.querySelector('.tfs-hover'); const dots = data.map(function (values, index) { const colour = 'var(--tfs-series-' + (index + 1) + ')'; if (data.length === 1 && values.length > 1) { const area = document.createElementNS(namespace, 'path'); area.setAttribute('class', 'tfs-area'); area.setAttribute('d', path(values) + 'L' + x(values.length - 1).toFixed(1) + ' ' + y(0) + 'L' + x(0).toFixed(1) + ' ' + y(0) + 'Z'); area.style.setProperty('--tfs-series', colour); marks.append(area); } const line = document.createElementNS(namespace, 'path'); line.setAttribute('class', 'tfs-line'); line.setAttribute('d', path(values)); line.style.setProperty('--tfs-series', colour); marks.append(line); const dot = document.createElementNS(namespace, 'circle'); dot.setAttribute('class', 'tfs-dot'); dot.setAttribute('r', 4); dot.style.setProperty('--tfs-series', colour); hover.append(dot); return dot; }); // One readout for every series at the time nearest the pointer. const tip = element('div', 'tfs-tip'); tip.hidden = true; host.append(tip); host.onpointermove = function (event) { const pointerX = event.clientX - host.getBoundingClientRect().left; let index = 0; for (let i = 1; i < times.length; i += 1) { if (Math.abs(x(i) - pointerX) < Math.abs(x(index) - pointerX)) index = i; } hover.hidden = false; const hair = hover.querySelector('.tfs-hair'); hair.setAttribute('x1', x(index)); hair.setAttribute('x2', x(index)); dots.forEach(function (dot, series) { dot.setAttribute('cx', x(index)); dot.setAttribute('cy', y(data[series][index])); }); tip.replaceChildren(element('div', 'tfs-tip-time', formatTime(times[index]))); chart.series.forEach(function (series, order) { const row = element('div', 'tfs-tip-row'); const swatch = element('i', 'tfs-swatch'); swatch.style.setProperty('--tfs-series', 'var(--tfs-series-' + (order + 1) + ')'); row.append(swatch, element('span', '', series.name), element('strong', '', formatValue(data[order][index]) + ' ' + chart.unit)); tip.append(row); }); tip.hidden = false; const left = x(index) + 12; tip.style.left = (left + tip.offsetWidth > width ? x(index) - 12 - tip.offsetWidth : left) + 'px'; }; host.onpointerleave = function () { hover.hidden = true; tip.hidden = true; }; } function renderSummary() { const column = function (index, scale) { return points.map(function (point) { return point[index] * scale; }); }; const average = function (values) { return values.length ? values.reduce(function (sum, value) { return sum + value; }, 0) / values.length : null; }; const peak = function (values) { return values.length ? Math.max.apply(null, values) : null; }; const show = function (value, suffix) { return value === null ? '–' : formatValue(value).replace(/\.0+$/, '') + suffix; }; const items = [ ['Average CPU', show(average(column(1, 1)), '%'), 'load', 'accent'], ['Peak CPU', show(peak(column(1, 1)), '%'), 'load', ''], ['Average memory', show(average(column(2, 1)), '%'), 'in use', 'accent'], ['Peak download', show(peak(column(3, 8 / 1e6)), ''), 'Mbps', ''], ]; summary.replaceChildren.apply(summary, items.map(function (item) { const cell = element('div', 'tp-summary-item'); const value = element('div', 'tp-summary-value' + (item[3] ? ' tp-' + item[3] : '')); value.append(element('span', '', item[1]), element('span', 'tp-summary-detail', item[2])); cell.append(element('div', 'tp-summary-label', item[0]), value); return cell; })); } function render() { tabs.querySelectorAll('.tp-tab').forEach(function (tab) { tab.setAttribute('aria-selected', String(tab.dataset.range === range)); }); note.textContent = range === 'live' ? 'Measured every few seconds while this page is open.' : 'One point is stored per minute. History is kept in memory for 31 days and starts again after a reboot.'; renderSummary(); hosts.forEach(function (host, index) { drawChart(host, CHARTS[index]); }); } function renderDisks() { let disks = []; try { disks = JSON.parse(root.dataset.disks || '[]'); } catch (error) { disks = []; } if (!disks.length) return; const card = element('section', 'tfs-card'); card.setAttribute('aria-label', 'Disk utilisation'); disks.forEach(function (disk) { const percent = Math.max(0, Math.min(100, Math.round(disk.used / disk.size * 100))); const row = element('div', 'tfs-disk'); const track = element('div', 'tfs-track'); const fill = element('div', 'tfs-fill'); fill.style.width = percent + '%'; track.append(fill); row.append(element('span', 'tfs-disk-name', disk.name), track, element('span', 'tfs-disk-value', formatSize(disk.used) + ' / ' + formatSize(disk.size) + ' · ' + percent + '%')); card.append(row); }); root.append(element('h2', 'tfs-section', 'Disk utilisation'), card); } // ---- Data function load() { const current = ++request; fetch(root.dataset.endpoint + '?range=' + encodeURIComponent(range), { credentials: 'same-origin' }) .then(function (response) { return response.json(); }) .then(function (data) { if (current !== request) return; // the range changed while this was in flight const fresh = Array.isArray(data.points) ? data.points : []; points = range === 'live' ? points.concat(fresh).slice(-LIVE_POINTS) : fresh; render(); }) .catch(function (error) { console.error('Aurora Theme stats:', error); }) .finally(function () { if (current === request) timer = setTimeout(load, range === 'live' ? LIVE_INTERVAL : HISTORY_INTERVAL); }); } function setRange(next) { range = next; points = []; clearTimeout(timer); render(); load(); } window.addEventListener('resize', function () { hosts.forEach(function (host, index) { drawChart(host, CHARTS[index]); }); }); setRange(range); })(); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/scripts/updated.js <<'AURORA_THEME_EOF' // "Aurora Theme updated": shown once on the first page opened after the plugin's version changed // (an automatic update, "Update now", the Plugins page). include/updated.php gives the versions // and the release notes of every release since the previous version, so nothing is missed when // several were skipped; closing the dialog tells it so, and it is not shown again. Loaded by AuroraTheme.page only while that record exists. (function () { 'use strict'; const endpoint = '/plugins/Aurora-Theme/include/updated.php'; function element(tag, className, text) { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; } // Bold, code and links of one line of the release notes, as nodes (never as HTML). function inline(text) { const fragment = document.createDocumentFragment(); const pattern = /\*\*([^*]+)\*\*|`([^`]+)`|\[([^\]]+)\]\((https:\/\/[^\s)]+)\)/g; let last = 0; let match; while ((match = pattern.exec(text))) { fragment.append(text.slice(last, match.index)); if (match[1] !== undefined) fragment.append(element('strong', '', match[1])); else if (match[2] !== undefined) fragment.append(element('code', '', match[2])); else { const link = element('a', '', match[3]); link.href = match[4]; link.target = '_blank'; link.rel = 'noopener'; fragment.append(link); } last = pattern.lastIndex; } fragment.append(text.slice(last)); return fragment; } // The release notes (Markdown): headings, lists and paragraphs. function notes(markdown) { const box = element('div', 'tp-upd-notes'); let list = null; let paragraph = null; markdown.replace(/\r/g, '').split('\n').forEach(function (raw) { const line = raw.trim(); const heading = /^#{1,6}\s+(.*)$/.exec(line); const item = /^[-*+]\s+(.*)$/.exec(line); if (!line) { list = null; paragraph = null; return; } if (heading) { list = null; paragraph = null; // The section title already says it. if (/^what['’]?s new$/i.test(heading[1].replace(/\*\*/g, '').trim())) return; box.append(element('h4', '', heading[1].replace(/\*\*/g, ''))); } else if (item) { paragraph = null; if (!list) { list = element('ul'); box.append(list); } const li = element('li'); li.append(inline(item[1])); list.append(li); } else if (list && /^\s{2,}/.test(raw)) { list.lastElementChild.append(' ', inline(line)); } else { list = null; if (!paragraph) { paragraph = element('p'); box.append(paragraph); } else paragraph.append(' '); paragraph.append(inline(line)); } }); return box; } function when(seconds) { if (!seconds) return ''; const date = new Date(seconds * 1000); const today = new Date(); const time = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); if (date.toDateString() === today.toDateString()) return 'today, ' + time; return date.toLocaleDateString([], { day: 'numeric', month: 'short' }) + ', ' + time; } function show(info) { const back = info.to < info.from; const backdrop = element('div', 'tp-upd-backdrop'); const dialog = element('div', 'tp-upd'); dialog.setAttribute('role', 'dialog'); dialog.setAttribute('aria-modal', 'true'); dialog.setAttribute('aria-labelledby', 'tp-upd-title'); const head = element('div', 'tp-upd-head'); const badge = element('span', 'tp-upd-badge'); badge.append(element('i', 'fa fa-' + (back ? 'undo' : 'magic'))); const heading = element('div', 'tp-upd-heading'); heading.append( element('span', 'tp-upd-eyebrow', back ? 'Aurora Theme changed' : 'Aurora Theme updated'), element('h2', 'tp-upd-title', 'Version ' + info.to) ); heading.lastChild.id = 'tp-upd-title'; head.append(badge, heading); const facts = element('div', 'tp-upd-facts'); const fact = function (icon, text) { const chip = element('span', 'tp-upd-fact'); chip.append(element('i', 'fa fa-' + icon), text); facts.append(chip); }; fact('code-fork', (back ? 'back from ' : 'from ') + info.from); if (info.time) fact('clock-o', when(info.time)); fact(info.automatic ? 'refresh' : 'hand-pointer-o', info.automatic ? 'automatic update' : 'installed by hand'); // One release: its notes. Several (versions were skipped): each with its version and date, // newest first. const releases = Array.isArray(info.releases) ? info.releases : null; const body = element('div', 'tp-upd-body'); body.append(element('div', 'tp-upd-section', releases && releases.length > 1 ? "What's new in " + releases.length + ' versions' : "What's new")); if (!releases) body.append(element('p', 'tp-upd-empty', 'The release notes could not be loaded from GitHub just now.')); else if (!releases.length) body.append(element('p', 'tp-upd-empty', 'There are no release notes for this version on GitHub.')); else if (releases.length === 1) body.append(releases[0].notes ? notes(releases[0].notes) : element('p', 'tp-upd-empty', 'This release has no notes.')); else releases.forEach(function (release) { const block = element('section', 'tp-upd-release'); const title = element('div', 'tp-upd-release-head'); const name = element('a', 'tp-upd-release-version', release.version); name.href = release.page || 'https://github.com/Tunefish92/Aurora-Theme/releases'; name.target = '_blank'; name.rel = 'noopener'; title.append(name); const date = release.date ? new Date(release.date) : null; if (date && !isNaN(date)) title.append(element('span', 'tp-upd-release-date', date.toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' }))); if (release.version === info.to) title.append(element('span', 'tp-upd-release-tag', 'installed now')); block.append(title, release.notes ? notes(release.notes) : element('p', 'tp-upd-empty', 'This release has no notes.')); body.append(block); }); const actions = element('div', 'tp-upd-actions'); const link = element('a', 'tp-upd-button', 'Release notes on GitHub'); link.href = info.page || 'https://github.com/Tunefish92/Aurora-Theme/releases'; link.target = '_blank'; link.rel = 'noopener'; const settings = element('a', 'tp-upd-button', 'Settings'); settings.href = '/Settings/AuroraThemeSettings'; const ok = element('button', 'tp-upd-button tp-upd-primary', 'Got it'); ok.type = 'button'; actions.append(link, settings, ok); dialog.append(head, facts, body, actions); backdrop.append(dialog); document.body.append(backdrop); ok.focus(); let closed = false; const close = function () { if (closed) return; closed = true; document.removeEventListener('keydown', key, true); backdrop.classList.add('tp-upd-leaving'); setTimeout(function () { backdrop.remove(); }, 180); fetch(endpoint, { method: 'POST', body: new URLSearchParams({ csrf_token: window.csrf_token || '' }) }).catch(function () {}); }; const key = function (event) { if (event.key === 'Escape') { event.stopPropagation(); close(); } }; document.addEventListener('keydown', key, true); ok.addEventListener('click', close); settings.addEventListener('click', close); backdrop.addEventListener('click', function (event) { if (event.target === backdrop) close(); }); } function start() { fetch(endpoint, { credentials: 'same-origin', cache: 'no-store' }) .then(function (response) { return response.ok ? response.json() : {}; }) .then(function (info) { if (info && info.to && info.from) show(info); }) .catch(function () {}); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start(); })(); AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/styles/enhance.css <<'AURORA_THEME_EOF' /* Elements added by scripts/enhance.js. Falls back to Unraid's own variables when the Tapeory colours are switched off. */ .tp-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0 0 2rem; overflow: hidden; border: 1px solid var(--tp-border, var(--table-border-color)); border-radius: 18px; background-color: var(--tp-elevated, var(--table-background-color)); box-shadow: var(--tp-shadow-sm, none); } .tp-summary-item { min-width: 0; padding: 1.5rem 2rem; } .tp-summary-item + .tp-summary-item { border-left: 1px solid var(--tp-border, var(--table-border-color)); } .tp-summary-label { color: var(--tp-muted, var(--alt-text-color)); font: 600 1.1rem var(--tp-font-heading, inherit); letter-spacing: 0.08em; text-transform: uppercase; } .tp-summary-value { display: flex; align-items: baseline; gap: 0.7rem; margin-top: 0.4rem; font: 700 2.4rem/1.15 var(--tp-font-heading, inherit); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; } .tp-summary-detail { color: var(--tp-muted, var(--alt-text-color)); font: 400 1.1rem var(--tp-font-body, inherit); letter-spacing: 0; } .tp-summary-value.tp-good { color: var(--green-500); } .tp-summary-value.tp-warn { color: var(--orange-300); } .tp-summary-value.tp-accent { color: var(--tp-primary, var(--link-text-color)); } @media (max-width: 767px) { .tp-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); } .tp-summary-item { padding: 1.2rem 1.4rem; } .tp-summary-item:nth-child(3) { border-top: 1px solid var(--tp-border, var(--table-border-color)); border-left: 0; } .tp-summary-item:nth-child(4) { border-top: 1px solid var(--tp-border, var(--table-border-color)); } } /* ---- Top bar: breadcrumb and array status */ .tp-crumb { display: flex; align-items: center; gap: 1.4rem; margin-right: auto; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.3rem; white-space: nowrap; } .tp-crumb strong { color: var(--text-color); font: 600 1.3rem var(--tp-font-heading, inherit); } .tp-pill { display: inline-flex; align-items: center; gap: 0.6rem; padding: 0.3rem 1rem; border-radius: 999px; color: var(--tp-neutral-text, var(--text-color)); font: 600 1.1rem var(--tp-font-heading, inherit); white-space: nowrap; background-color: var(--tp-neutral-bg, var(--mild-background-color)); } .tp-pill:before { width: 6px; height: 6px; border-radius: 50%; background-color: currentColor; content: ""; } .tp-pill.tp-good { color: var(--tp-success-text, var(--green-800)); background-color: var(--tp-success-bg, var(--green-100)); } .tp-pill.tp-warn { color: var(--tp-warn-text, var(--orange-300)); background-color: var(--tp-warn-bg, var(--mild-background-color)); } /* Docker and VMs tabs with FolderView3 or FolderView Plus: their support is in beta (enhance.js, folderViewBeta()). A calm pill with the plugin's name and a small tag, in the top bar, or under the page heading where there is none. */ button.tp-fv-beta { display: inline-flex; align-items: center; gap: 0.7rem; margin: 0; padding: 0.4rem 0.5rem 0.4rem 1.1rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 999px; color: var(--tp-text, var(--text-color)); font: 500 1.2rem var(--tp-font-body, inherit); letter-spacing: 0; text-transform: none; white-space: nowrap; background: var(--tp-subtle, var(--mild-background-color)); box-shadow: none; cursor: pointer; transition: border-color 0.25s, background-color 0.25s; } button.tp-fv-beta:hover { border-color: var(--tp-warn-text, #f59e0b); color: var(--tp-text, var(--text-color)); background: var(--tp-subtle, var(--mild-background-color)); transform: none; } button.tp-fv-beta > .fa { color: var(--tp-warn-text, #f59e0b); font-size: 1.3rem; } .tp-fv-beta-tag { padding: 0.2rem 0.8rem; border-radius: 999px; color: var(--tp-warn-text, #f59e0b); font: 700 1rem var(--tp-font-heading, inherit); letter-spacing: 0.06em; text-transform: uppercase; background: var(--tp-warn-bg, rgba(245, 158, 11, 0.16)); } /* FolderView Plus and FolderView3 installed together: not supported. */ button.tp-fv-beta.tp-fv-conflict { border-color: color-mix(in srgb, var(--tp-danger, #ef4444) 45%, var(--tp-border, transparent)); } button.tp-fv-beta.tp-fv-conflict:hover { border-color: var(--tp-danger, #ef4444); } button.tp-fv-beta.tp-fv-conflict > .fa, .tp-fv-conflict .tp-fv-beta-tag { color: var(--tp-danger, var(--red-600, #ef4444)); } .tp-fv-conflict .tp-fv-beta-tag { background: var(--tp-danger-soft, rgba(239, 68, 68, 0.16)); } .tp-heading-text > .tp-fv-beta { margin-top: 1rem; } @media (min-width: 1024px) { :root.tp-sidebar .tp-heading-text > .tp-fv-beta { display: none; } } /* A narrow top bar keeps room for the profile: the icon and the tag only (the name is in the tooltip and the dialog). */ @media (max-width: 1199px) { .tp-crumb .tp-fv-beta-text { display: none; } } /* The top bar pieces only make sense in the sidebar layout, where the header is slim. */ :root:not(.tp-sidebar) .tp-crumb, .tp-server { display: none; } @media (max-width: 1023px) { .tp-crumb { display: none; } } /* ---- Sidebar: connected server */ .tp-server-label { display: block; margin: 0 1rem 0.9rem; color: var(--tp-header-muted, #a7b0c0); font: 600 1rem var(--tp-font-heading, inherit); letter-spacing: 0.11em; text-transform: uppercase; opacity: 0.75; } .tp-server-card { display: flex; flex-direction: column; gap: 0.2rem; padding: 1rem 1.2rem; border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 12px; background-color: rgba(255, 255, 255, 0.05); } .tp-server-card b { color: var(--tp-header-text, #ffffff); font: 600 1.3rem var(--tp-font-heading, inherit); } .tp-server-card b { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } /* ---- Top right: dark / light switch */ /* Phones: the header has no room for it; the theme stays switchable in Display Settings. */ @media (max-width: 767px) { #UserProfile > .tp-theme-switch { display: none !important; } } #UserProfile > .tp-theme-switch { display: grid; flex: none; place-items: center; align-self: center; width: 3.6rem; min-width: 0; height: 3.6rem; min-height: 0; margin: 0; padding: 0; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 50%; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.6rem; background-color: var(--tp-elevated, transparent); box-shadow: none; cursor: pointer; transition: border-color 0.35s, color 0.35s; } /* The icon shows the active theme: moon in blue while dark, sun in amber while light. */ #UserProfile > .tp-theme-switch.tp-dark { color: var(--tp-primary, var(--brand-orange)); } #UserProfile > .tp-theme-switch:not(.tp-dark) { color: #f59e0b; } #UserProfile > .tp-theme-switch:hover, #UserProfile > .tp-theme-switch:focus-visible { border-color: currentColor; } #UserProfile > .tp-theme-switch .fa { transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); } #UserProfile > .tp-theme-switch:hover .fa { transform: rotate(-20deg) scale(1.1); } #UserProfile > .tp-theme-switch.tp-busy { cursor: progress; } #UserProfile > .tp-theme-switch.tp-busy .fa { animation: fa-spin 1s linear infinite; } /* ---- Page heading */ .tp-heading { margin: 1.4rem 0 2.4rem; } .tp-eyebrow { margin: 0 0 0.5rem; color: var(--tp-primary, var(--link-text-color)); font: 600 1rem var(--tp-font-heading, inherit); letter-spacing: 0.115em; text-align: left; text-transform: uppercase; } .tp-heading h1 { margin: 0; font: 600 2.7rem/1.2 var(--tp-font-heading, inherit); letter-spacing: -0.01em; } .tp-subtitle { margin: 0.6rem 0 0; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.2rem; text-align: left; } /* ---- Container and VM state as a pill. Not with FolderView Plus (tp-fvplus): its lists keep their own status, which the pill made too wide for its folder cells. */ .tp-enhance:where(:not(.tp-fvplus)) .inner > span.state { display: inline-flex; align-items: center; gap: 0.6rem; margin-top: 0.3rem; padding: 0.1rem 0.9rem; border-radius: 999px; color: var(--tp-neutral-text, var(--text-color)); font: 600 1.1rem var(--tp-font-heading, inherit); background-color: var(--tp-neutral-bg, var(--mild-background-color)); } .tp-enhance:where(:not(.tp-fvplus)) .inner > span.state:before { width: 6px; height: 6px; border-radius: 50%; background-color: currentColor; content: ""; } .tp-enhance:where(:not(.tp-fvplus)) .inner > i.started ~ span.state { color: var(--tp-success-text, var(--green-800)); background-color: var(--tp-success-bg, var(--green-100)); } .tp-enhance:where(:not(.tp-fvplus)) .inner > i.paused ~ span.state { color: var(--tp-warn-text, var(--orange-300)); } /* The pill's dot replaces the play / stop icon; the spinner shown while a state changes stays. */ .tp-enhance:where(:not(.tp-fvplus)) .inner > i.started:has(~ span.state), .tp-enhance:where(:not(.tp-fvplus)) .inner > i.stopped:has(~ span.state), .tp-enhance:where(:not(.tp-fvplus)) .inner > i.paused:has(~ span.state) { display: none; } /* ---- Docker list: hidden until it is grouped, so it never flashes in Unraid's plain order. If the script does not get to it, the list appears by itself after two seconds. */ .tp-enhance:not(.tp-fvplus) #docker_list:not(.tp-ready) { visibility: hidden; animation: tp-docker-reveal 0s linear 2s forwards; } @keyframes tp-docker-reveal { to { visibility: visible; } } /* ---- Loading status: under the Docker table header until the list arrives, and at the bottom of the page while a menu click waits for the next page. */ .tp-loader { flex: none; width: 1.8rem; height: 1.8rem; border: 2px solid var(--tp-border, var(--table-border-color)); border-top-color: var(--tp-primary, var(--brand-orange)); border-radius: 50%; animation: tp-loader-spin 0.8s linear infinite; } @keyframes tp-loader-spin { to { transform: rotate(360deg); } } .tp-docker-loading { display: flex; align-items: center; justify-content: center; gap: 1.2rem; padding: 4rem 2rem; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.3rem; text-align: center; } /* ---- Array usage widget at the start of the menu's quick actions, as tall as its buttons */ .tp-usage { --tp-usage-tone: var(--tp-primary, var(--brand-orange)); display: flex; flex: none; align-items: center; gap: 0.9rem; width: 17rem; height: 3.3rem; margin-right: 0.6rem; padding: 0 1.1rem 0 0.4rem; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 10px; color: var(--tp-header-text, var(--text-color)); text-decoration: none; background: rgba(255, 255, 255, 0.04); transition: border-color 0.3s, background-color 0.3s; } .tp-usage:hover, .tp-usage:focus-visible { border-color: color-mix(in srgb, var(--tp-usage-tone) 45%, transparent); color: var(--tp-header-text, var(--text-color)); text-decoration: none; background: color-mix(in srgb, var(--tp-usage-tone) 10%, transparent); } /* In the top bar (top layout on wide screens), after Unraid's logo: a pill as tall as the bar's round buttons. */ #header > .tp-usage.tp-usage-header { align-self: center; width: 18rem; height: 3.6rem; margin: 0 auto 0 2.4rem; padding: 0 1.4rem 0 0.5rem; /* From the top bar's own text colour: the bar stays dark in the light palette, and custom header colours (Display Settings) are followed. */ border-color: color-mix(in srgb, var(--tp-header-text, #ffffff) 12%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--tp-header-text, #ffffff) 5%, transparent); } #header > .tp-usage.tp-usage-header .tp-usage-track { background: color-mix(in srgb, var(--tp-header-text, #ffffff) 12%, transparent); } #header > .tp-usage.tp-usage-header .tp-usage-icon { border-radius: 50%; } .tp-usage-good { --tp-usage-tone: var(--tp-success-text, #22c55e); } .tp-usage-warn { --tp-usage-tone: var(--tp-warn-text, #f59e0b); } .tp-usage-danger { --tp-usage-tone: var(--tp-danger, #ef4444); } .tp-usage-off { --tp-usage-tone: var(--tp-header-muted, var(--alt-text-color)); } .tp-usage-icon { display: grid; flex: none; place-items: center; width: 2.5rem; height: 2.5rem; border-radius: 7px; color: var(--tp-usage-tone); font-size: 1.4rem; background: color-mix(in srgb, var(--tp-usage-tone) 16%, transparent); } .tp-usage-body { display: flex; flex: 1; flex-direction: column; gap: 0.45rem; min-width: 0; } .tp-usage-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.6rem; line-height: 1; } .tp-usage-label { color: var(--tp-header-muted, var(--alt-text-color)); font: 600 0.95rem/1 var(--tp-font-heading, inherit); letter-spacing: 0.1em; text-transform: uppercase; } .tp-usage-value { font: 700 1.25rem/1 var(--tp-font-heading, inherit); font-variant-numeric: tabular-nums; } .tp-usage-off .tp-usage-value { color: var(--tp-header-muted, var(--alt-text-color)); font-weight: 600; } .tp-usage-track { display: block; height: 0.4rem; overflow: hidden; border-radius: 999px; background: rgba(255, 255, 255, 0.1); } .tp-usage-fill { display: block; height: 100%; border-radius: inherit; background: var(--tp-usage-tone); box-shadow: 0 0 8px color-mix(in srgb, var(--tp-usage-tone) 60%, transparent); } .tp-docker-loading-on div.spinner.fixed { display: none !important; } .tp-nav-progress { position: fixed; bottom: 2.4rem; left: 50%; z-index: 10002; display: flex; align-items: center; gap: 1rem; max-width: calc(100vw - 32px); padding: 0.9rem 1.6rem 0.9rem 1.1rem; border: 1px solid var(--tp-border, var(--table-border-color)); border-radius: 999px; color: var(--tp-text, var(--text-color)); font: 600 1.25rem var(--tp-font-heading, inherit); white-space: nowrap; background-color: var(--tp-elevated, var(--table-background-color)); box-shadow: var(--tp-shadow-lg, 0 8px 24px rgba(0, 0, 0, 0.25)); transform: translateX(-50%); animation: tp-nav-progress-in 0.2s ease-out; } .tp-nav-progress .tp-loader { width: 1.5rem; height: 1.5rem; } .tp-nav-progress-text { overflow: hidden; text-overflow: ellipsis; } @keyframes tp-nav-progress-in { from { opacity: 0; transform: translate(-50%, 0.6rem); } } @media (prefers-reduced-motion: reduce) { .tp-loader { animation-duration: 2.4s; } .tp-nav-progress { animation: none; } } /* ---- Docker toolbar: filter tabs and search */ .tp-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1.2rem; margin: 0 0 1rem; } .tp-tabs { display: flex; flex-wrap: wrap; gap: 0.4rem; } .tp-toolbar button.tp-tab, .tp-title-switch button.tp-tab { display: inline-flex; align-items: center; gap: 0.7rem; min-width: 0; margin: 0; padding: 0.6rem 1.2rem; border: 1px solid transparent; border-radius: 999px; color: var(--tp-muted, var(--alt-text-color)); font: 500 1.2rem var(--tp-font-heading, inherit); letter-spacing: 0; text-transform: none; background: transparent; box-shadow: none; transform: none; } .tp-toolbar button.tp-tab:hover, .tp-title-switch button.tp-tab:hover { border-color: transparent; color: var(--text-color); background: transparent; box-shadow: none; transform: none; } .tp-toolbar button.tp-tab[aria-selected="true"], .tp-title-switch button.tp-tab[aria-selected="true"] { border-color: var(--tp-border, var(--border-color)); color: var(--tp-primary, var(--link-text-color)); font-weight: 600; background: var(--tp-elevated, var(--table-background-color)); box-shadow: var(--tp-shadow-sm, none); } .tp-tab-count { font: 1rem var(--tp-font-body, inherit); font-variant-numeric: tabular-nums; opacity: 0.7; } .tp-toolbar input.tp-search { width: 22rem; max-width: 100%; margin: 0; padding: 0.7rem 1rem; } .tp-filtered-out { display: none !important; } .tp-segment { display: inline-flex; gap: 0.2rem; padding: 0.3rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 999px; background-color: var(--tp-subtle, var(--mild-background-color)); } .tp-toolbar .tp-segment button.tp-tab { padding: 0.4rem 1.2rem; } .tp-native-hidden { display: none !important; } .tp-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 0.8rem; } /* ---- Docker: compact container list Unraid's own table and rows stay in place; each row becomes a grid, so columns can be hidden and reordered without rebuilding anything. Cells added by enhance.js carry .tp-extra. */ #docker_containers .tp-extra { display: none; } #docker_containers.tp-compact, #docker_containers.tp-compact > thead, #docker_containers.tp-compact > tbody { display: block; } #docker_containers.tp-compact > thead > tr, #docker_containers.tp-compact > tbody > tr.sortable { display: grid; grid-template-columns: minmax(22rem, 2.4fr) minmax(5.5rem, 0.45fr) minmax(9rem, 0.8fr) minmax(10rem, 1fr) minmax(6rem, 0.6fr) minmax(13rem, 1.3fr) minmax(12rem, 1.3fr) var(--tp-auto-column, minmax(8rem, 0.6fr)) 17.5rem; align-items: center; column-gap: 1.4rem; padding: 0 1.9rem; } #docker_containers.tp-compact > tbody > tr.sortable { min-height: 6.4rem; } /* Advanced view adds the start delay next to the autostart switch, which needs more room. */ #docker_containers.tp-compact.tp-advanced { --tp-auto-column: minmax(18rem, 0.9fr); } #docker_containers.tp-compact.tp-advanced > tbody > tr.sortable { padding-top: 0.8rem; padding-bottom: 0.8rem; } #docker_containers.tp-compact > tbody > tr.tp-filtered-out { display: none; } #docker_containers.tp-compact > thead > tr { min-height: 3.9rem; background-color: var(--tp-subtle, var(--table-header-background-color)); } #docker_containers.tp-compact th, #docker_containers.tp-compact tr.sortable > td { display: block; width: auto !important; min-width: 0; height: auto; padding: 0 !important; line-height: 1.4; text-align: left; background-color: transparent; } /* Hover tint on the row instead of on each cell, with the accent bar on the left. */ #docker_containers.tp-compact > tbody > tr.sortable { box-shadow: inset 3px 0 0 transparent; transition: background-color 0.35s, box-shadow 0.35s; } #docker_containers.tp-compact > tbody > tr.sortable:hover { background-color: var(--tp-primary-soft, var(--hover-table-row-background-color)); box-shadow: inset 3px 0 0 var(--tp-primary, var(--brand-orange)); } #docker_containers.tp-compact > tbody > tr.sortable:hover > td { background-color: transparent; } /* Column order: container, web UI, status, version, cpu, memory, ports, autostart, actions */ #docker_containers.tp-compact .tp-c-name { order: 1; } #docker_containers.tp-compact .tp-c-webui { order: 2; } #docker_containers.tp-compact .tp-c-status { order: 3; } #docker_containers.tp-compact .tp-c-version { order: 4; } #docker_containers.tp-compact .tp-c-cpu { order: 5; } #docker_containers.tp-compact .tp-c-mem { order: 6; } #docker_containers.tp-compact .tp-c-ports { order: 7; } #docker_containers.tp-compact .tp-c-auto { order: 8; } #docker_containers.tp-compact .tp-c-actions { order: 9; } #docker_containers.tp-compact .tp-c-network, #docker_containers.tp-compact .tp-c-ip, #docker_containers.tp-compact .tp-c-cport, #docker_containers.tp-compact .tp-c-volumes, #docker_containers.tp-compact .tp-c-uptime, #docker_containers.tp-compact th.tp-c-load { display: none !important; } /* Unraid keeps CPU and memory in one cell and updates them live. Dissolving the cell lets its two values sit in separate columns while Unraid goes on updating the same elements. */ #docker_containers.tp-compact td.tp-c-load { display: contents !important; } #docker_containers.tp-compact td.tp-c-load > [class^="cpu-"] { order: 5; } #docker_containers.tp-compact td.tp-c-load > [class^="mem-"] { order: 6; } #docker_containers.tp-compact td.tp-c-load > [class^="cpu-"], #docker_containers.tp-compact td.tp-c-load > [class^="mem-"] { min-width: 0; font: 600 1.2rem var(--tp-font-heading, inherit); font-variant-numeric: tabular-nums; } #docker_containers.tp-compact td.tp-c-load > .usage-disk, #docker_containers.tp-compact td.tp-c-load > br { display: none; } /* Container cell: icon, name, image */ #docker_containers.tp-compact td.ct-name .outer { display: flex; align-items: center; gap: 1.1rem; min-width: 0; margin: 0; } #docker_containers.tp-compact tr.sortable > td.ct-name { display: flex; align-items: center; } #docker_containers.tp-compact td.ct-name img.img { width: 3.6rem; height: 3.6rem; border-radius: 12px; } #docker_containers.tp-compact td.ct-name .inner { display: block; min-width: 0; } #docker_containers.tp-compact td.ct-name .appname { display: block; overflow: hidden; font: 600 1.3rem var(--tp-font-heading, inherit); text-overflow: ellipsis; white-space: nowrap; } #docker_containers.tp-compact td.ct-name .appname a { color: var(--text-color); } #docker_containers.tp-compact tr.sortable:hover td.ct-name .appname a { color: var(--tp-primary, var(--link-text-color)); } .tp-image { display: none; } #docker_containers.tp-compact .tp-image { display: block; margin-top: 0.3rem; overflow: hidden; color: var(--tp-muted, var(--alt-text-color)); font: 1rem var(--tp-font-body, inherit); font-variant-numeric: tabular-nums; text-overflow: ellipsis; white-space: nowrap; } /* The state moves to its own column, and the image line replaces the "Container ID / By" block. */ #docker_containers.tp-compact td.ct-name .inner > br, #docker_containers.tp-compact td.ct-name .inner > i, #docker_containers.tp-compact td.ct-name .inner > .state, #docker_containers.tp-compact td.ct-name > div.advanced { display: none !important; } #docker_containers.tp-compact tr.sortable > td.tp-c-auto { display: flex; align-items: center; } #docker_containers.tp-compact td.tp-c-auto .switch-button-label { min-width: 2.6rem; } /* Start delay ("wait"), shown by Unraid in advanced view for containers that autostart */ #docker_containers.tp-compact td.tp-c-auto span[id$="-wait"] { margin-left: 1.2rem; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.1rem; white-space: nowrap; } #docker_containers.tp-compact td.tp-c-auto input.wait { width: 4.6rem; margin: 0 0 0 0.6rem; padding: 0.3rem 0.6rem; text-align: center; } #docker_containers.tp-compact td.tp-c-ports { color: var(--tp-muted, var(--alt-text-color)); font: 1.1rem var(--tp-font-body, inherit); font-variant-numeric: tabular-nums; } /* WebUI link: a round gradient button with just the icon */ a.tp-webui, a.tp-ct-link[role="link"] { cursor: pointer; } a.tp-webui { display: inline-grid; place-items: center; width: 3.4rem; height: 3.4rem; border-radius: 50%; color: var(--tp-primary-text, #ffffff); font-size: 1.5rem; text-decoration: none; background: var(--tp-gradient, var(--brand-orange)); box-shadow: 0 6px 14px -6px var(--tp-primary, transparent); transition: transform 0.35s, box-shadow 0.35s, filter 0.35s; } a.tp-webui:hover { color: var(--tp-primary-text, #ffffff); box-shadow: 0 0 0 4px var(--tp-primary-soft, transparent), 0 10px 20px -8px var(--tp-primary, transparent); filter: brightness(1.1); transform: translateY(-2px) scale(1.06); } a.tp-webui .fa { transform: translate(0.5px, 0.5px); } .tp-webui-none { color: var(--tp-muted, var(--alt-text-color)); opacity: 0.5; } /* Row actions */ .tp-actions { display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem; } .tp-actions button.tp-icon-button { display: inline-grid; place-items: center; width: 3rem; min-width: 0; height: 3rem; margin: 0; padding: 0; border: 0; border-radius: 50%; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.3rem; letter-spacing: 0; background: transparent; box-shadow: none; transition: color 0.35s, background-color 0.35s, transform 0.35s; } .tp-actions button.tp-icon-button:hover { border: 0; color: var(--tp-primary, var(--link-text-color)); background: var(--tp-elevated, var(--background-color)); box-shadow: var(--tp-shadow-sm, none); transform: translateY(-1px); } .tp-actions button.tp-icon-button.tp-primary { color: var(--tp-primary, var(--link-text-color)); } .tp-actions button.tp-icon-button.tp-danger:hover { color: var(--red-600); } /* A group's "move up" in the first place, "move down" in the last. */ .tp-actions button.tp-icon-button:disabled, .tp-actions button.tp-icon-button:disabled:hover { color: var(--tp-muted, var(--alt-text-color)); background: transparent; box-shadow: none; opacity: 0.3; transform: none; cursor: default; } /* ---- Docker: long container names end in "…" (the full name is the tooltip), also in the full table that FolderView gets. */ #docker_list td.ct-name .appname { display: inline-block; max-width: 24rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; } /* In a FolderView3 folder the name and state sit in a box narrower than the cell, which cut a long name off mid-letter. There the name fills that box as a line of its own, so it ends in "…" (its line break is then not needed). */ #docker_list > tr.folder-element > td.ct-name .inner > .appname { display: block; } #docker_list > tr.folder-element > td.ct-name .inner > .appname + br { display: none; } /* ---- Docker with FolderView: folders on a tinted band with an accent edge, an open folder's containers on a lighter band of the same colour and indented, and space before the containers outside any folder (arrangeFolders() in enhance.js puts them in that order). FolderView Plus (tp-fvplus) colours its folders itself, so the bands are FolderView3's only. */ /* Theme filters and search are hidden while FolderView arranges the list. */ .tp-toolbar .tp-tabs[hidden], .tp-toolbar .tp-search[hidden] { display: none; } /* FolderView Plus: a folder's name cell also holds its internal id ("folder-") in an .appname, which Plus hides; the long-name rule above would show it again (issue #23). */ .tp-fvplus #docker_list tr.folder td.ct-name .folder-inner > .appname { display: none !important; } /* FolderView Plus: next to a wide preview a folder's name box is narrow, and its status ("7/7 stopped") ran past it, cut off mid-letter. It ends in "…" there, in the same room. */ .tp-fvplus :is(#docker_list, #kvm_list) tr.folder .folder-inner > .folder-state { max-width: calc(100% - 1.8rem); overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; } /* Preview icons of a closed folder: long names end in "…" too. */ #docker_list > tr.folder .folder-preview .appname { display: inline-block; max-width: 14rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; } :where(:root:not(.tp-fvplus)) #docker_list > tr.folder > td { background-color: color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 8%, transparent); } :where(:root:not(.tp-fvplus)) #docker_list > tr.folder > td:first-child { box-shadow: inset 3px 0 0 var(--tp-primary, var(--brand-orange)); } #docker_list > tr.folder .folder-appname { display: inline-block; max-width: 22rem; overflow: hidden; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; } :where(:root:not(.tp-fvplus)) #docker_list > tr.folder-element > td { background-color: color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 4%, transparent); } :where(:root:not(.tp-fvplus)) #docker_list > tr.folder-element > td:first-child { padding-left: 2.6rem !important; box-shadow: inset 3px 0 0 color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 45%, transparent); } :where(:root:not(.tp-fvplus)) #docker_list > tr.tp-after-folders > td { border-top: 1.6rem solid var(--tp-elevated, var(--background-color)) !important; } /* Dashboard, Docker tile with FolderView: folder chips tinted with an accent border; an open folder's containers in a panel across the tile under it; a line before the containers outside the folders (dashboardDockerOrder() in enhance.js). FolderView Plus lays out and colours its folders itself; in its "legacy" layout, which keeps the theme's grid, an open one gets the tile's full width. */ .tp-fvplus table.dashboard > tbody:not([data-fv-dashboard-layout]:not([data-fv-dashboard-layout="legacy"])) .folder-showcase-outer[expanded="true"] { grid-column: 1 / -1; } :where(:root:not(.tp-fvplus)) table.dashboard span.outer.solid[class*="folder-docker"] { border-color: color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 45%, transparent); background-color: color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 10%, var(--tp-subtle, transparent)); } :where(:root:not(.tp-fvplus)) table.dashboard .folder-showcase:not(:empty) { display: grid; grid-column: 1 / -1; grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr)); gap: 0.6rem; padding: 0.8rem; border: 1px solid color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 30%, transparent); border-radius: 12px; background-color: color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 5%, transparent); } /* Dashboard, Docker tile without FolderView: the theme's application groups. A chip like the folder chips (folder icon, name, count, caret) opens or closes a panel with the group's containers across the tile. */ table.dashboard span.outer.solid.tp-group-chip { cursor: pointer; border-color: color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 45%, transparent); background-color: color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 10%, var(--tp-subtle, transparent)); } table.dashboard span.outer.solid.tp-group-chip:focus-visible { outline: 2px solid var(--tp-primary, var(--brand-orange)); outline-offset: 2px; } .tp-group-chip-icon { display: grid; flex: none; place-items: center; width: 3.6rem; height: 3.6rem; border-radius: 10px; color: var(--tp-primary, var(--brand-orange)); font-size: 1.7rem; background: color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 16%, transparent); } table.dashboard span.outer.solid.tp-group-chip .inner { flex: 1 1 auto; } .tp-group-chip-name { display: block; overflow: hidden; font: 600 1.25rem/1.3 var(--tp-font-heading, inherit); text-overflow: ellipsis; white-space: nowrap; } .tp-group-chip-count { display: block; overflow: hidden; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.05rem; text-overflow: ellipsis; white-space: nowrap; } .tp-group-chip-caret { flex: none; margin-right: 0.4rem; color: var(--tp-muted, var(--alt-text-color)); transition: transform 0.25s; } .tp-group-chip.tp-collapsed .tp-group-chip-caret { transform: rotate(-90deg); } table.dashboard .tp-group-panel { display: grid; grid-column: 1 / -1; grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr)); gap: 0.6rem; padding: 0.8rem; border: 1px solid color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 30%, transparent); border-radius: 12px; background-color: color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 5%, transparent); } table.dashboard .tp-group-panel[hidden] { display: none; } /* An open group or FolderView folder reads as one card: its chip becomes a header across the tile with the panel joined below it; closed ones stay chips side by side. */ table.dashboard span.outer.solid.tp-group-chip:not(.tp-collapsed), :where(:root:not(.tp-fvplus)) table.dashboard .folder-showcase-outer[expanded="true"] > span.outer.solid { grid-column: 1 / -1; border-bottom-color: transparent; border-radius: 12px 12px 0 0; } table.dashboard span.outer.solid.tp-group-chip:not(.tp-collapsed) + .tp-group-panel, :where(:root:not(.tp-fvplus)) table.dashboard .folder-showcase-outer[expanded="true"] > .folder-showcase { margin-top: -0.6rem; border-top: 0; border-radius: 0 0 12px 12px; } table.dashboard span.outer.solid.tp-group-chip:not(.tp-collapsed):hover, :where(:root:not(.tp-fvplus)) table.dashboard .folder-showcase-outer[expanded="true"] > span.outer.solid:hover { transform: none; } /* Phones: two chips per row on the Docker and VM tiles (one per row made a long tile with many containers); the names end in "…" sooner. */ @media (max-width: 480px) { table.dashboard > tbody:is(#docker_view, #vm_view):not([data-fv-dashboard-layout]:not([data-fv-dashboard-layout="legacy"])) > tr.updated > td, table.dashboard .tp-group-panel, :where(:root:not(.tp-fvplus)) table.dashboard .folder-showcase:not(:empty) { grid-template-columns: repeat(2, minmax(0, 1fr)); } table.dashboard > :where(tbody:not([data-fv-dashboard-layout]:not([data-fv-dashboard-layout="legacy"]))) span.outer.solid img.img { width: 3rem; height: 3rem; } } .tp-chip-divider { grid-column: 1 / -1; height: 1px; margin: 0.6rem 0; background-color: var(--tp-border, var(--border-color)); } /* Long names on the tile's chips end in "…" (FolderView folders and VMs have them as text). */ table.dashboard > :where(tbody:not([data-fv-dashboard-layout]:not([data-fv-dashboard-layout="legacy"]))) span.outer.solid .inner { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* ---- Docker and VMs: application groups */ :is(#docker_list, #kvm_list) > tr.tp-group > td { height: auto; padding: 0.7rem 1.9rem; border-top: 1px solid var(--tp-border, var(--table-border-color)); background-color: var(--tp-subtle, var(--table-header-background-color)); } #docker_containers.tp-compact > tbody > tr.tp-group, #docker_containers.tp-compact > tbody > tr.tp-group > td { display: block; } .tp-group-head { display: flex; align-items: center; gap: 1rem; } /* ---- Running apps of a group or folder: small icons with a status dot (appStrip() in enhance.js), in the Docker tab's group headers and on the Dashboard's group and folder chips. */ .tp-group-apps { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; min-width: 0; } .tp-group-apps[hidden] { display: none; } .tp-group-head .tp-group-apps { flex: 1 1 auto; flex-wrap: nowrap; overflow: hidden; } /* A group's CPU and memory added up, in the same type as a container's values. In the compact view on wider screens they sit in the table's CPU and Memory columns (below); otherwise they are one line with small labels between the icons and the buttons. */ .tp-group-load { display: inline-flex; flex: none; gap: 1rem; white-space: nowrap; } .tp-group-load > span[hidden], .tp-group-load[hidden] { display: none; } .tp-group-load > span { color: var(--tp-text, var(--text-color)); font: 600 1.2rem var(--tp-font-heading, inherit); font-variant-numeric: tabular-nums; } .tp-group-load > span::before { margin-right: 0.4rem; color: var(--tp-muted, var(--alt-text-color)); font-size: 1rem; letter-spacing: 0.06em; } .tp-group-load > .tp-group-cpu::before { content: "CPU"; } .tp-group-load > .tp-group-mem::before { content: "RAM"; } /* Compact view, wider screens: the header uses the rows' grid, so the sums line up under the container's CPU and Memory values. */ @media (min-width: 768px) { #docker_containers.tp-compact > tbody > tr.tp-group > td { padding-right: 1.9rem; padding-left: 1.9rem; } #docker_containers.tp-compact .tp-group-head { display: grid; grid-template-columns: minmax(22rem, 2.4fr) minmax(5.5rem, 0.45fr) minmax(9rem, 0.8fr) minmax(10rem, 1fr) minmax(6rem, 0.6fr) minmax(13rem, 1.3fr) minmax(12rem, 1.3fr) var(--tp-auto-column, minmax(8rem, 0.6fr)) 17.5rem; column-gap: 1.4rem; } #docker_containers.tp-compact .tp-group-head > .tp-group-toggle { grid-column: 1; min-width: 0; } /* The name with the count under it, so both fit the first column. */ #docker_containers.tp-compact .tp-group-head > button.tp-group-toggle { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 1rem; row-gap: 0.1rem; justify-items: start; text-align: left; } #docker_containers.tp-compact .tp-group-toggle > .fa { grid-row: 1 / span 2; } #docker_containers.tp-compact .tp-group-toggle > .tp-group-name, #docker_containers.tp-compact .tp-group-toggle > .tp-group-count { grid-column: 2; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #docker_containers.tp-compact .tp-group-head > .tp-group-apps { grid-column: 2 / 5; } #docker_containers.tp-compact .tp-group-head > .tp-group-load { display: contents; } #docker_containers.tp-compact .tp-group-head .tp-group-cpu { grid-column: 5; } #docker_containers.tp-compact .tp-group-head .tp-group-mem { grid-column: 6; } #docker_containers.tp-compact .tp-group-head > .tp-actions { grid-column: 7 / -1; justify-self: end; } #docker_containers.tp-compact .tp-group-load > span::before { content: none; } } /* Phones: the icons and the sums get a line of their own under the group's name and count. */ @media (max-width: 767px) { .tp-group-head { flex-wrap: wrap; row-gap: 0.4rem; } /* The icons keep their width; the sums move to the next line when both do not fit. */ .tp-group-head .tp-group-apps { order: 3; flex: 0 0 auto; max-width: 100%; padding-left: 2.4rem; box-sizing: border-box; } .tp-group-head .tp-group-load { order: 4; margin-left: auto; } .tp-group-head::after { order: 2; flex-basis: 100%; content: ""; } } .tp-app-icon { position: relative; flex: none; width: 2.2rem; height: 2.2rem; } .tp-app-icon img { display: block; width: 100%; height: 100%; border-radius: 6px; object-fit: contain; } .tp-app-icon::after { position: absolute; right: -0.2rem; bottom: -0.2rem; width: 0.8rem; height: 0.8rem; border: 2px solid var(--tp-elevated, var(--background-color)); border-radius: 50%; background-color: #22c55e; content: ""; } .tp-app-icon.tp-app-paused::after { background-color: #f59e0b; } /* ---- Update ready: an orange dot at the icon's top right (the state dot sits at the bottom right), whether the container runs or not. On the group icons above, the Dashboard's Docker tile and the Docker tab's compact list. */ .tp-app-icon.tp-app-update::before, table.dashboard span.outer.solid.tp-update > span.hand::before, #docker_containers.tp-compact tr.tp-update td.ct-name span.hand::before { position: absolute; z-index: 1; top: -0.3rem; right: -0.3rem; width: 1.1rem; height: 1.1rem; box-sizing: border-box; border: 2px solid var(--tp-elevated, var(--background-color)); border-radius: 50%; background-color: #f97316; box-shadow: 0 0 8px rgba(249, 115, 22, 0.7); content: ""; } .tp-app-icon.tp-app-update::before { top: 0; right: 0; width: 0.8rem; height: 0.8rem; } table.dashboard span.outer.solid.tp-update > span.hand { position: relative; } /* The Docker tile's "started only" switch hides stopped containers (inline display: none); one with an update stays, so its dot is seen. Not in a closed FolderView folder's store, and not with FolderView Plus, which decides what its tile shows. */ :where(:root:not(.tp-fvplus)) #docker_view span.outer.solid.apps.stopped.tp-update[style*="display: none"]:not(.folder-storage *) { display: flex !important; } table.dashboard span.outer.solid.tp-update > span.hand::before { border-color: var(--tp-subtle); } #docker_containers.tp-compact td.ct-name span.hand { position: relative; flex: none; } #docker_containers.tp-compact tr.tp-update td.ct-name span.hand::before { top: -0.2rem; right: -0.2rem; width: 1.3rem; height: 1.3rem; } /* Stopped: greyed out, without a dot. */ .tp-app-icon.tp-app-stopped img { opacity: 0.45; filter: grayscale(1); } .tp-app-icon.tp-app-stopped::after { content: none; } /* An icon opens the app's menu. */ .tp-app-icon[role="button"] { cursor: pointer; transition: transform 0.2s; } .tp-app-icon[role="button"]:hover { transform: translateY(-1px); } .tp-app-icon[role="button"]:hover img { opacity: 1; filter: none; } .tp-app-icon[role="button"]:focus-visible { border-radius: 6px; outline: 2px solid var(--tp-primary, var(--link-text-color)); outline-offset: 2px; } .tp-app-more { flex: none; padding: 0.1rem 0.5rem; border-radius: 999px; color: var(--tp-muted, var(--alt-text-color)); font: 600 1rem/1.6 var(--tp-font-heading, inherit); background: var(--tp-subtle, rgba(255, 255, 255, 0.06)); } /* On a Dashboard chip the icons are a line under the count, a little smaller. */ table.dashboard span.outer.solid .inner > .tp-group-apps { flex-wrap: nowrap; margin-top: 0.4rem; overflow: hidden; } table.dashboard span.outer.solid .inner > .tp-group-apps .tp-app-icon { width: 1.8rem; height: 1.8rem; } /* With FolderView installed, the switch above the list between Aurora's view and FolderView's; in Aurora's view FolderView's "Add Folder" has nothing to add to. */ .tp-view-switch button.tp-tab[disabled] { opacity: 0.6; } /* Beside the "Docker Containers" title. */ div.title > .tp-segment.tp-title-switch { display: inline-flex; margin-right: auto; margin-left: 1.6rem; vertical-align: middle; } /* VMs tab: in the tab bar, after the "Virtual Machines" tab. */ nav.tabs > .tp-segment.tp-title-switch { align-self: center; margin-right: auto; margin-left: 1.6rem; } :is(div.title, nav.tabs) > .tp-title-switch button.tp-tab { padding: 0.4rem 1.2rem; font-size: 1.15rem; } .tp-aurora-view .fv3-add-folder-btn { display: none !important; } /* FolderView in the Docker tab: the whole folder row opens and closes it (not FolderView Plus, which handles clicks on its folders itself). */ :where(:root:not(.tp-fvplus)) #docker_list > tr.folder { cursor: pointer; } .tp-group-head button.tp-group-toggle { display: inline-flex; align-items: center; gap: 1rem; min-width: 0; margin: 0; padding: 0.3rem 0.6rem 0.3rem 0; border: 0; color: var(--text-color); font: 600 1.3rem var(--tp-font-heading, inherit); letter-spacing: -0.01em; text-transform: none; background: transparent; box-shadow: none; transform: none; } .tp-group-head button.tp-group-toggle:hover { border: 0; color: var(--tp-primary, var(--link-text-color)); background: transparent; box-shadow: none; transform: none; } .tp-group-toggle .fa { width: 1.4rem; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.1rem; transition: transform 0.35s; } .tp-group.tp-collapsed .tp-group-toggle .fa { transform: rotate(-90deg); } /* A group's grip, in front of its name: drag it to move the group. Every section's name leaves room for it, so the names line up ("Ungrouped" has none). The grip and the up / down buttons are there only while Unraid's lock button has the lists unlocked (html.tp-unlocked). */ :root:not(.tp-unlocked) :is(.tp-group-grip, .tp-actions button.tp-group-move) { display: none !important; } :root:not(.tp-unlocked) .tp-group-head > button.tp-group-toggle, :root:not(.tp-unlocked) .tp-group-head > button.tp-group-toggle:hover { padding-left: 0; } .tp-group-head { position: relative; } .tp-group-head > button.tp-group-toggle, .tp-group-head > button.tp-group-toggle:hover { padding-left: 2.8rem; } .tp-group-grip { position: absolute; top: 50%; left: -0.4rem; z-index: 1; display: grid; place-items: center; width: 2.4rem; height: 2.8rem; margin-top: -1.4rem; border-radius: 6px; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.2rem; cursor: grab; opacity: 0.6; touch-action: none; transition: opacity 0.2s, color 0.2s, background-color 0.2s; } .tp-group-grip:hover, .tp-group-dragging .tp-group-grip { color: var(--tp-primary, var(--link-text-color)); background: var(--tp-elevated, var(--background-color)); opacity: 1; } .tp-group-dragging .tp-group-grip { cursor: grabbing; } :is(#docker_list, #kvm_list) > tr.tp-group.tp-group-dragging > td { background-color: var(--tp-primary-soft, rgba(79, 139, 255, 0.15)); } .tp-group-drop-line { position: fixed; z-index: 10002; height: 3px; border-radius: 2px; background: var(--tp-primary, var(--link-text-color)); box-shadow: 0 0 0 3px var(--tp-primary-soft, rgba(79, 139, 255, 0.2)); pointer-events: none; } /* A long group name ends in "…" (the whole name is the tooltip); the count stays beside it. */ .tp-group-head > button.tp-group-toggle { flex: 0 1 auto; max-width: 100%; min-width: 0; } .tp-group-toggle > .tp-group-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .tp-group-toggle > .tp-group-count { flex: none; } .tp-group-count { color: var(--tp-muted, var(--alt-text-color)); font: 400 1.1rem var(--tp-font-body, inherit); letter-spacing: 0; } .tp-group-head .tp-actions { margin-left: auto; } .tp-group-menu .fa { margin-right: 0.8rem; } /* ---- Dialogs opened by enhance.js (new group, rename, confirmations) */ .tp-modal-backdrop { position: fixed; inset: 0; z-index: 100000; display: grid; place-items: center; padding: 2rem; background-color: rgba(8, 12, 20, 0.6); backdrop-filter: blur(3px); animation: tp-modal-fade 0.2s ease-out; } .tp-modal { box-sizing: border-box; width: min(44rem, 100%); padding: 2.6rem 2.6rem 2.2rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 18px; color: var(--text-color); background-color: var(--tp-elevated, var(--background-color)); box-shadow: var(--tp-shadow-lg, 0 24px 56px -22px rgba(0, 0, 0, 0.6)); animation: tp-modal-rise 0.3s cubic-bezier(0.16, 1, 0.3, 1); } /* A long message (many names) scrolls between the title and the buttons, so the buttons stay on screen, also on a phone (dvh: the height left by the browser's own bars). */ .tp-modal:not(.tp-ct-modal) { display: flex; flex-direction: column; max-height: calc(100vh - 4rem); max-height: calc(100dvh - 4rem); } .tp-modal:not(.tp-ct-modal) > .tp-modal-message { min-height: 0; overflow-y: auto; overflow-wrap: anywhere; } .tp-modal-title { margin: 0; font: 600 2rem/1.25 var(--tp-font-heading, inherit); letter-spacing: -0.01em; } .tp-modal-message { margin: 0.8rem 0 0; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.3rem; line-height: 1.5; text-align: left; } .tp-modal input.tp-modal-input { display: block; width: 100%; margin: 1.8rem 0 0; padding: 1rem 1.2rem; box-sizing: border-box; font-size: 1.4rem; } .tp-modal-problem { min-height: 1.8rem; margin: 0.6rem 0 0; color: var(--tp-danger, var(--red-600)); font-size: 1.2rem; text-align: left; } .tp-modal-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 1rem; margin-top: 2rem; } /* Small phones: long button labels ("Switch to FolderView3") stack full width. */ @media (max-width: 479px) { .tp-modal { padding: 2.2rem 2rem 2rem; } .tp-modal-actions > .tp-modal-button { flex: 1 1 100%; } } .tp-modal-problem + .tp-modal-actions { margin-top: 0.8rem; } .tp-modal button.tp-modal-button { min-width: 9rem; margin: 0; padding: 0.9rem 1.8rem; border: 1px solid var(--tp-input-border, var(--border-color)); border-radius: 999px; color: var(--text-color); font: 600 1.3rem var(--tp-font-heading, inherit); letter-spacing: 0; text-transform: none; background: var(--tp-elevated, transparent); transition: transform 0.35s, box-shadow 0.35s, border-color 0.35s, color 0.35s, opacity 0.35s; } .tp-modal button.tp-modal-button:hover:not(:disabled) { border-color: var(--tp-primary, var(--brand-orange)); color: var(--tp-primary, var(--brand-orange)); transform: translateY(-1px); } .tp-modal button.tp-modal-button.tp-modal-primary { border-color: transparent; color: var(--tp-primary-text, #ffffff); background: var(--tp-gradient, var(--brand-orange)); box-shadow: 0 10px 22px -12px var(--tp-primary, transparent); } .tp-modal button.tp-modal-button.tp-modal-primary:hover:not(:disabled) { border-color: transparent; color: var(--tp-primary-text, #ffffff); } .tp-modal button.tp-modal-button.tp-modal-danger { border-color: transparent; color: #ffffff; background: var(--tp-danger, var(--red-600)); } .tp-modal button.tp-modal-button.tp-modal-danger:hover:not(:disabled) { border-color: transparent; color: #ffffff; } .tp-modal button.tp-modal-button:disabled { opacity: 0.45; cursor: default; transform: none; } @keyframes tp-modal-fade { from { opacity: 0; } } @keyframes tp-modal-rise { from { opacity: 0; transform: translateY(12px) scale(0.98); } } @media (prefers-reduced-motion: reduce) { .tp-modal-backdrop, .tp-modal { animation: none; } } /* ---- Dashboard: System tile, the four usage rings as small cards */ :root.Theme--black { --tp-ring-track: #262c39; } :root.Theme--white { --tp-ring-track: #e3e8f0; } /* Unraid aligns the cards to the top, so a label on two lines ("Log filesystem") made its card taller than the others. All cards in a row now share a height and keep their rings level. */ .tile-system-memory-charts.tp-rings { grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr)); align-items: stretch; gap: 1rem; } .tile-system-memory-charts.tp-rings > span { display: flex; flex-direction: column; align-items: center; gap: 1.2rem; padding: 1.4rem 1rem 1.6rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 16px; background-color: var(--tp-subtle, transparent); transition: transform 0.3s; } .tile-system-memory-charts.tp-rings > span:hover { border-color: var(--tp-input-border, var(--border-color)); transform: translateY(-1px); } .tile-system-memory-charts.tp-rings > span > a.info { color: var(--tp-muted, var(--alt-text-color)); font: 600 1.1rem var(--tp-font-heading, inherit); letter-spacing: 0.08em; text-transform: uppercase; text-wrap: balance; } .tile-system-memory-charts.tp-rings div.pie { --tp-ring: var(--tp-primary, #4f8bff); position: relative; flex-direction: column; flex: none; gap: 0.2rem; width: 10.4rem; height: 10.4rem; margin: auto 0 0; border-radius: 50%; transition: background 0.6s; } /* Boot device, log and Docker vdisk: one value each, drawn here in the level's colour. */ .tile-system-memory-charts.tp-rings div.pie:not(#sys0) { background: conic-gradient(var(--tp-ring) calc(var(--tp-p, 0) * 1%), var(--tp-ring-track) 0) !important; } .tile-system-memory-charts.tp-rings div.pie[data-level="warning"] { --tp-ring: #f59e0b; } .tile-system-memory-charts.tp-rings div.pie[data-level="critical"] { --tp-ring: var(--tp-danger, #ef4444); } .tile-system-memory-charts.tp-rings div.pie::after { inset: 0.9rem; width: auto; height: auto; background-color: var(--tp-subtle, var(--dashstats-pie-after-bg-color)); box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.18); } .tile-system-memory-charts.tp-rings div.pie > span[class^="sys"] { color: var(--tp-text, var(--text-color)) !important; font: 600 2rem/1 var(--tp-font-heading, inherit); letter-spacing: -0.02em; } .tile-system-memory-charts.tp-rings div.pie[data-level="warning"] > span[class^="sys"] { color: #f59e0b !important; } .tile-system-memory-charts.tp-rings div.pie[data-level="critical"] > span[class^="sys"] { color: var(--tp-danger, #ef4444) !important; } .tile-system-memory-charts.tp-rings div.pie > span[class^="var"] { display: block; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.1rem !important; font-weight: 500; } /* Labels and legend above the rings */ .tp-enhance table.dashboard .tile-system-memory-labels > div { padding: 1.2rem 1.4rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 14px; line-height: 1.9; } .tp-enhance table.dashboard .tile-system-memory-labels i.ups { width: 1.6rem; margin-right: 0.6rem; text-align: center; } .tp-enhance table.dashboard .tile-system-memory-labels #dynamic i.fa-circle { font-size: 1rem; vertical-align: 0.1rem; } .tp-enhance table.dashboard .tile-system-memory-labels legend { margin-bottom: 0.4rem; color: var(--tp-muted, var(--alt-text-color)); font: 600 1.05rem var(--tp-font-heading, inherit); font-variant: normal; letter-spacing: 0.1em; text-transform: uppercase; } /* ---- Dashboard: Virtual Machines Usage tile (dashboardVmUsage() in enhance.js) One card per running VM, as wide as the tile allows: icon and name on top, the readings in a grid of two columns below (one on narrow screens), CPU readings with a slim bar. */ table.dashboard #vm_view_usage > tr > td { grid-template-columns: repeat(auto-fill, minmax(min(100%, 34rem), 1fr)); } table.dashboard #vm_view_usage span.outer.solid.tp-vmuse { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 1rem 1rem; padding: 1rem 1.2rem; } table.dashboard #vm_view_usage span.outer.solid.tp-vmuse:hover { transform: none; } .tp-vmuse-readings { display: grid; grid-column: 1 / -1; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 0.8rem 1.6rem; padding-top: 1rem; border-top: 1px solid var(--tp-border, var(--border-color)); } .tp-vmuse-readings > span { display: block; min-width: 0; color: var(--tp-text, var(--text-color)); font-size: 1.2rem; font-variant-numeric: tabular-nums; line-height: 1.45; overflow-wrap: anywhere; } /* "Guest CPU: 3.1%": the reading and its bar stack in the cell. */ .tp-vmuse-readings > span > span { display: block; } .tp-vmuse-readings .usage-disk.mm { position: relative; width: 100%; height: 0.5rem; margin-top: 0.4rem; overflow: hidden; border-radius: 999px; background: var(--tp-border, var(--usage-disk-background-color)); } .tp-vmuse-readings .usage-disk.mm > span:first-child { position: absolute; inset: 0 auto 0 0; max-width: 100%; font-size: 0; border-radius: 999px; background: var(--tp-gradient, var(--brand-orange)); } /* ---- Dashboard: GPU Statistics plugin tile (dashboardGpu() in enhance.js) Heading, device and a short readings line in the header; below, one row per reading: icon and name on the left, the value on the right, and for readings with a bar a slim bar across the tile under them. Rows the plugin hides (another vendor's readings, a GPU passed to a VM) stay hidden: it sets display on the rows, the layout here is on their cells. */ tbody.tp-gpu > tr.tp-gpu-moved { display: none !important; } tbody.tp-gpu .tile-header-left .section { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; } tbody.tp-gpu .tile-header-left .tile-header-main { margin: 0; } .tp-gpu-device { color: var(--tp-text, var(--text-color)); font-size: 1.25rem; } /* The plugin gives these Unraid's fixed column widths; here they are plain text. */ :is(.tp-gpu-device, .tp-gpu-driver) > span { display: inline; float: none; width: auto !important; min-width: 0 !important; max-width: none !important; } .tp-gpu-driver > span + span { margin-left: 0.4rem; } .tp-gpu-meta { display: flex; flex-wrap: wrap; gap: 0.2rem 1.4rem; color: var(--tp-muted, var(--alt-text-color)); } .tp-gpu-meta > i.ups { display: none; } .tp-gpu-meta :is([class^="gpu-util"], [class^="gpu-temp"], [class^="gpu-sessions"]) { color: var(--tp-text, var(--text-color)); font-weight: 600; } /* The name keeps at least half the row; a long value (bus receive and transmit) wraps on the right instead of pushing the name out. */ tbody.tp-gpu > tr > td.tp-gpu-w36 { display: grid; grid-template-columns: minmax(50%, 1fr) minmax(0, auto); align-items: center; gap: 0.45rem 1.2rem; padding-top: 0.55rem; padding-bottom: 0.55rem; } tbody.tp-gpu > tr > td > span.w36 { display: flex; align-items: center; gap: 0.8rem; float: none; width: auto !important; min-width: 0 !important; max-width: none !important; overflow: hidden; color: var(--tp-muted, var(--alt-text-color)); white-space: nowrap; text-overflow: ellipsis; } tbody.tp-gpu > tr > td > span.w36 > i.ups { flex: none; width: 1.6rem; margin: 0; color: var(--tp-primary, var(--brand-orange)); text-align: center; } /* Values bold; their captions ("Receive:", "State:") quiet. */ tbody.tp-gpu > tr > td > span:is(.w26, .w44) { float: none; width: auto !important; min-width: 0 !important; max-width: none !important; color: var(--tp-muted, var(--alt-text-color)); font-variant-numeric: tabular-nums; white-space: normal; text-align: right; } tbody.tp-gpu > tr > td > span:is(.w26, .w44) span { color: var(--tp-text, var(--text-color)); font-weight: 600; } /* One value with a bar: the value beside the name, the bar under both. */ tbody.tp-gpu > tr > td > span.w44.tp-gpu-bar { display: contents; } tbody.tp-gpu > tr > td > span.w44 > span.load { grid-column: 2; grid-row: 1; min-width: 4.4rem; color: var(--tp-text, var(--text-color)); font-weight: 600; text-align: right; } tbody.tp-gpu > tr > td > span:is(.w26, .w44) .usage-disk.sys { width: auto; height: 5px; margin: 0 !important; } tbody.tp-gpu > tr > td > span.w44 > .usage-disk.sys { grid-column: 1 / -1; grid-row: 2; } tbody.tp-gpu > tr > td > span:is(.w26, .w44) .usage-disk.sys > span:first-child { height: 5px; } /* Two values (NVIDIA and AMD rows such as "Load - Memory"): the name on top, the two values, each with its bar, side by side under it. */ tbody.tp-gpu > tr > td.tp-gpu-w26 { grid-template-columns: repeat(2, minmax(0, 1fr)); } tbody.tp-gpu > tr > td.tp-gpu-w26 > span.w36 { grid-column: 1 / -1; } tbody.tp-gpu > tr > td > span.w26 { display: grid; grid-template-columns: 4.4rem minmax(0, 1fr); align-items: center; gap: 0.8rem; text-align: left; } /* PCI bus (NVIDIA, AMD): "PCIe Gen (Max)" and "Lanes (Max)" are plain spans; both go on the right, one under the other, styled like the other values. */ tbody.tp-gpu > tr > td.tp-gpu-w36 > span:not(.w36, .w26, .w44) { grid-column: 2; color: var(--tp-muted, var(--alt-text-color)); font-variant-numeric: tabular-nums; text-align: right; } tbody.tp-gpu > tr > td.tp-gpu-w36 > span:not(.w36, .w26, .w44) span { color: var(--tp-text, var(--text-color)); font-weight: 600; } /* "State:P2": the plugin leaves out the space. */ tbody.tp-gpu [class^="gpu-perfstate"] { margin-left: 0.3rem; } /* A clock of 0 (an idle GPU) reads "idle"; dashboardGpu() sets the mark. */ tbody.tp-gpu [data-tp-idle] { font-size: 0 !important; } tbody.tp-gpu [data-tp-idle]::after { color: var(--tp-muted, var(--alt-text-color)); font-size: 1.3rem; font-weight: 500; content: "idle"; } /* Active apps: only while one is using the GPU (the plugin adds its icon then). */ table.dashboard tbody.tp-gpu > tr[class*="gpu-active-apps"]:not(:has(img)) { display: none !important; } /* Active apps: just their icons, side by side in the tile's bottom row and wrapping when many containers use the GPU (they were stacked one per line). Each icon's tooltip names the app, its process count and memory. */ table.dashboard tbody.tp-gpu > tr[class*="gpu-active-apps"] > td { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-top: 0.4rem; padding-top: 0.9rem; border-top: 1px solid var(--tp-border, var(--border-color)); } table.dashboard tbody.tp-gpu > tr[class*="gpu-active-apps"] > td > span.w36 { display: none; } /* A title on a line of its own above the icons. */ table.dashboard tbody.tp-gpu > tr[class*="gpu-active-apps"] > td::before { flex: 1 1 100%; margin-bottom: 0.2rem; color: var(--tp-muted, var(--alt-text-color)); font: 600 1rem var(--tp-font-heading, inherit); letter-spacing: 0.08em; text-transform: uppercase; content: "Apps using the GPU"; } table.dashboard tbody.tp-gpu > tr[class*="gpu-active-apps"] > td > span.gpu-img-span { flex: none; } table.dashboard tbody.tp-gpu > tr[class*="gpu-active-apps"] img.gpu-image { width: 2.6rem; height: 2.6rem; border-radius: 8px; object-fit: contain; } /* ---- Dashboard: fans as one row of small cards, wrapping only when needed */ .tp-fans { display: grid; grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); gap: 0.6rem; padding: 0.2rem 0 0.4rem; } /* Name with a small spinning fan on top, speed underneath: narrow enough for five or six in a row on a dashboard tile. */ .tp-fan { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "icon name" "speed speed"; align-items: center; gap: 0.4rem 0.6rem; min-width: 0; padding: 0.8rem 0.9rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 12px; background-color: var(--tp-subtle, var(--mild-background-color)); } .tp-fan-icon { grid-area: icon; color: var(--tp-primary, var(--brand-orange)); font-size: 1.2rem; animation: tp-fan-spin 2.4s linear infinite; } .tp-fan-text { display: contents; } .tp-fan-name { grid-area: name; overflow: hidden; text-overflow: ellipsis; color: var(--tp-muted, var(--alt-text-color)); font: 600 1rem var(--tp-font-heading, inherit); letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; } .tp-fan-speed { grid-area: speed; font: 600 1.4rem var(--tp-font-heading, inherit); font-variant-numeric: tabular-nums; white-space: nowrap; } .tp-fan.tp-fan-idle .tp-fan-icon { color: var(--tp-muted, var(--alt-text-color)); animation: none; } .tp-fan.tp-fan-idle .tp-fan-speed { color: var(--tp-muted, var(--alt-text-color)); } @keyframes tp-fan-spin { to { transform: rotate(360deg); } } @media (prefers-reduced-motion: reduce) { .tp-fan-icon { animation: none; } } /* ---- Settings and Tools: search above the panels */ .tp-panel-search { position: relative; display: flex; align-items: center; max-width: 52rem; margin: 0 0 2.4rem; } .tp-panel-search:before { position: absolute; left: 1.6rem; color: var(--tp-muted, var(--alt-text-color)); font: normal 1.4rem/1 FontAwesome; pointer-events: none; content: "\f002"; } #displaybox .tp-panel-search input.tp-panel-search-input { width: 100%; min-height: 4.4rem; margin: 0; padding: 1rem 9rem 1rem 4.4rem; color: var(--text-color); border: 1px solid var(--tp-input-border, var(--input-border-color)); border-radius: 999px; font-size: 1.4rem; background-color: var(--tp-elevated, var(--background-color)); box-shadow: var(--tp-shadow-sm, none); } #displaybox .tp-panel-search input.tp-panel-search-input:focus { border-color: var(--tp-primary, var(--brand-orange)); box-shadow: 0 0 0 3px var(--tp-primary-soft, transparent); } .tp-panel-search-count { position: absolute; right: 1.8rem; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.15rem; font-variant-numeric: tabular-nums; pointer-events: none; } .tp-panel-search-empty { margin: 0 0 2rem; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.3rem; } /* Settings and Tools: the search sits on the right of the page heading, in the same row. */ .tp-heading:has(.tp-panel-search) { display: flex; align-items: center; justify-content: space-between; gap: 2rem; } .tp-heading .tp-panel-search { flex: 0 1 38rem; max-width: 38rem; margin: 0; } /* ---- Users tab */ #displaybox div.title.tp-hidden-title { display: none; } #tp-users-summary { margin-bottom: 2.4rem; } .tp-users-tools { display: flex; flex: 0 1 52rem; align-items: center; gap: 1rem; } .tp-users-tools .tp-panel-search { flex: 1 1 auto; max-width: none; margin: 0; } .tp-heading button.tp-users-add { flex: none; min-height: 4.4rem; margin: 0; padding: 0 2rem; border: 0; border-radius: 999px; color: var(--tp-primary-text, #ffffff); font: 600 1.35rem var(--tp-font-heading, inherit); letter-spacing: 0; text-transform: none; white-space: nowrap; background: var(--tp-gradient, var(--brand-orange)); box-shadow: 0 10px 22px -14px var(--tp-primary, transparent); } #displaybox div.title.tp-users-title .left { display: inline-flex; align-items: center; gap: 0.6rem; } .tp-users-count { margin-left: 0.4rem; padding: 0.15rem 0.9rem; border-radius: 999px; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.15rem; font-weight: 600; background-color: var(--tp-subtle, transparent); } #displaybox .Panels.tp-users { display: grid; grid-template-columns: repeat(auto-fill, minmax(28rem, 1fr)); gap: 1.2rem; margin-top: 1.4rem; } /* Unraid's UserList.css makes each user a narrow centred column (max 15rem). */ #displaybox .Panels.tp-users > .user-list.tp-user { display: block; width: auto; min-width: 0; max-width: none; margin: 0; padding: 0; text-align: left; word-break: normal; } #displaybox .Panels.tp-users > .user-list.tp-user > a.tp-user-card { display: flex; flex-direction: row; align-items: center; justify-content: flex-start; gap: 1.4rem; min-height: 0; height: 100%; padding: 1.6rem 1.6rem 1.6rem 1.8rem; text-align: left; } .tp-user-avatar { position: relative; display: grid; flex: none; place-items: center; width: 5.2rem; height: 5.2rem; overflow: hidden; border-radius: 50%; color: #ffffff; font: 700 1.7rem/1 var(--tp-font-heading, inherit); letter-spacing: 0.02em; background-color: var(--tp-subtle, transparent); box-shadow: 0 0 0 3px var(--tp-elevated, transparent), 0 0 0 4px var(--tp-border, transparent); } #displaybox .tp-user-avatar img { width: 100%; max-width: none; height: 100%; margin: 0; border: 0; border-radius: 0; object-fit: cover; } .tp-user-text { display: flex; flex: 1 1 auto; flex-direction: column; gap: 0.5rem; min-width: 0; } .tp-user-top { display: flex; flex-wrap: wrap; align-items: center; gap: 0.8rem; } .tp-user-name { overflow: hidden; color: var(--tp-text, var(--text-color)); font: 600 1.55rem/1.2 var(--tp-font-heading, inherit); text-overflow: ellipsis; white-space: nowrap; } .tp-user-role { padding: 0.2rem 0.9rem; border-radius: 999px; color: var(--tp-success-text, #15803d); font: 600 1.05rem/1.5 var(--tp-font-heading, inherit); background-color: var(--tp-success-bg, #dcfce7); } .tp-user-role.tp-admin { color: var(--tp-primary, var(--brand-orange)); background-color: var(--tp-primary-soft, transparent); } .tp-user-desc { overflow: hidden; color: var(--tp-muted, var(--alt-text-color)); font: 400 1.25rem/1.4 var(--tp-font-body, inherit); text-overflow: ellipsis; white-space: nowrap; } .tp-user-desc.tp-user-desc-empty { opacity: 0.6; } .tp-user-go { display: grid; flex: none; place-items: center; width: 3.2rem; height: 3.2rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 999px; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.1rem; transition: color 0.3s, border-color 0.3s, background-color 0.3s; } a.tp-user-card:hover .tp-user-go, a.tp-user-card:focus-visible .tp-user-go { border-color: transparent; color: var(--tp-primary-text, #ffffff); background: var(--tp-gradient, var(--brand-orange)); } /* "Add user" as the last card of the share users. */ #displaybox .Panels.tp-users > button.tp-user-add { display: flex; align-items: center; justify-content: center; gap: 1rem; min-height: 8.6rem; margin: 0; border: 1.5px dashed var(--tp-input-border, var(--border-color)); border-radius: var(--tp-r-md, 14px); color: var(--tp-muted, var(--alt-text-color)); font: 600 1.35rem var(--tp-font-heading, inherit); letter-spacing: 0; text-transform: none; background: transparent; box-shadow: none; transition: border-color 0.3s, color 0.3s, background-color 0.3s; } #displaybox .Panels.tp-users > button.tp-user-add:hover { border-color: var(--tp-primary, var(--brand-orange)); color: var(--tp-primary, var(--brand-orange)); background-color: var(--tp-primary-soft, transparent); } .tp-user-add-icon { display: grid; place-items: center; width: 3.2rem; height: 3.2rem; border-radius: 999px; font-size: 2rem; line-height: 1; background-color: var(--tp-subtle, transparent); } /* Unraid's own Add User / Done buttons: replaced by the buttons above. */ form.tp-users-form { display: none; } @media (max-width: 767px) { .tp-users-tools { flex-basis: auto; flex-wrap: wrap; } #displaybox .Panels.tp-users { grid-template-columns: minmax(0, 1fr); } } /* ---- Settings and Tools: favourites */ .Panels .Panel { position: relative; } .Panel:has(.tp-fav-toggle) i.favo { display: none !important; } .Panel button.tp-fav-toggle { position: absolute; top: 0.8rem; right: 0.8rem; z-index: 1; display: grid; place-items: center; width: 3rem; min-width: 0; height: 3rem; min-height: 0; margin: 0; padding: 0; border: 0; border-radius: 999px; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.4rem; background: transparent; box-shadow: none; opacity: 0; cursor: pointer; transition: opacity 0.25s, color 0.25s, background-color 0.25s, transform 0.25s; } .Panel:hover button.tp-fav-toggle, .Panel:focus-within button.tp-fav-toggle, .Panel button.tp-fav-toggle.tp-on { opacity: 1; } .Panel button.tp-fav-toggle:hover, .Panel button.tp-fav-toggle:focus-visible { color: #f5b301; background-color: rgba(245, 179, 1, 0.14); transform: scale(1.08); } .Panel button.tp-fav-toggle.tp-on { color: #f5b301; } .Panel button.tp-fav-toggle.tp-on .fa { filter: drop-shadow(0 2px 6px rgba(245, 179, 1, 0.45)); } #displaybox div.title.tp-fav-title i.fa-star { color: #f5b301; } .tp-fav-hint { margin: 1.2rem 0 0; padding: 1.6rem 1.8rem; border: 1px dashed var(--tp-input-border, var(--border-color)); border-radius: 14px; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.25rem; text-align: left; } .tp-fav-hint:before { margin-right: 0.8rem; color: #f5b301; font: normal 1.3rem/1 FontAwesome; content: "\f005"; } #displaybox .tp-fav-panels:empty { display: none; } #displaybox .tp-fav-panels.tp-filtered-out + .tp-fav-hint { display: none; } #displaybox .tp-empty-section { display: none !important; } /* Phones and touch screens have no hover, so the star is always there. */ @media (hover: none) { .Panel button.tp-fav-toggle { opacity: 1; } } /* ---- Drop-down lists: the themed list shown instead of the browser's own */ select.tp-select-open { border-color: var(--tp-primary, var(--brand-orange)) !important; box-shadow: 0 0 0 3px var(--tp-primary-soft, transparent) !important; } .tp-select-panel { position: fixed; z-index: 100001; box-sizing: border-box; max-width: min(48rem, calc(100vw - 1.6rem)); padding: 0.6rem; overflow-y: auto; overscroll-behavior: contain; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 14px; color: var(--tp-text, var(--text-color)); font: 400 1.3rem/1.35 var(--tp-font-body, inherit); background-color: var(--tp-elevated, var(--background-color)); box-shadow: var(--tp-shadow-lg, 0 24px 56px -22px rgba(0, 0, 0, 0.6)), 0 0 0 1px rgba(0, 0, 0, 0.04); scrollbar-width: thin; animation: tp-select-down 0.16s cubic-bezier(0.16, 1, 0.3, 1); } .tp-select-panel.tp-upwards { animation-name: tp-select-up; } .tp-select-option { position: relative; padding: 0.85rem 3.4rem 0.85rem 1.2rem; border-radius: 9px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; user-select: none; } .tp-select-option.tp-active { color: var(--tp-text, var(--text-color)); background-color: var(--tp-primary-soft, rgba(0, 0, 0, 0.06)); } .tp-select-option.tp-selected { color: var(--tp-primary, var(--link-text-color)); font-weight: 600; } .tp-select-option.tp-selected:after { position: absolute; top: 50%; right: 1.2rem; font: normal 1.2rem/1 FontAwesome; transform: translateY(-50%); content: "\f00c"; } .tp-select-option.tp-disabled { color: var(--tp-muted, var(--alt-text-color)); opacity: 0.55; cursor: default; } .tp-select-group { padding: 1rem 1.2rem 0.4rem; color: var(--tp-muted, var(--alt-text-color)); font: 600 1rem var(--tp-font-heading, inherit); letter-spacing: 0.1em; text-transform: uppercase; } .tp-select-group:not(:first-child) { margin-top: 0.4rem; border-top: 1px solid var(--tp-border, var(--border-color)); } @keyframes tp-select-down { from { opacity: 0; transform: translateY(-6px); } } @keyframes tp-select-up { from { opacity: 0; transform: translateY(6px); } } /* ---- Dashboard: container dialog */ #docker_view span.outer.apps { cursor: pointer; } .tp-modal.tp-ct-modal { width: min(52rem, 100%); max-height: calc(100vh - 4rem); max-height: calc(100dvh - 4rem); padding: 2.4rem; overflow-y: auto; box-sizing: border-box; } .tp-ct-head { display: flex; align-items: center; gap: 1.6rem; } .tp-ct-icon { position: relative; display: grid; flex: 0 0 auto; place-items: center; width: 6.4rem; height: 6.4rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 18px; background-color: var(--tp-subtle, transparent); } .tp-ct-icon img, .tp-ct-icon img.img { width: 4.4rem !important; height: 4.4rem !important; margin: 0 !important; border-radius: 10px; object-fit: contain; } .tp-ct-icon i { margin: 0 !important; font-size: 3.6rem !important; } .tp-ct-icon:after { position: absolute; right: -0.4rem; bottom: -0.4rem; width: 1.4rem; height: 1.4rem; border: 3px solid var(--tp-elevated, var(--background-color)); border-radius: 999px; background-color: var(--tp-muted, #8b93a5); content: ""; } .tp-ct-running .tp-ct-icon:after { background-color: #22c55e; } .tp-ct-paused .tp-ct-icon:after { background-color: #f59e0b; } .tp-ct-text { flex: 1 1 auto; min-width: 0; } .tp-ct-text .tp-modal-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .tp-ct-meta { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.7rem; } .tp-ct-status, .tp-ct-tag { display: inline-flex; align-items: center; gap: 0.6rem; padding: 0.3rem 1rem; border-radius: 999px; font: 600 1.1rem/1.6 var(--tp-font-heading, inherit); color: var(--tp-neutral-text, var(--alt-text-color)); background-color: var(--tp-neutral-bg, rgba(0, 0, 0, 0.06)); } .tp-ct-status:before { width: 0.7rem; height: 0.7rem; border-radius: 999px; background-color: currentColor; content: ""; } .tp-ct-running .tp-ct-status { color: var(--tp-success-text, #15803d); background-color: var(--tp-success-bg, #dcfce7); } .tp-ct-paused .tp-ct-status, .tp-ct-tag.tp-ct-update { color: var(--tp-warn-text, #b45309); background-color: var(--tp-warn-bg, #fef3c7); } .tp-ct-modal button.tp-ct-close { display: grid; flex: 0 0 auto; align-self: flex-start; place-items: center; width: 3.6rem; min-width: 0; height: 3.6rem; min-height: 0; margin: 0; padding: 0; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 999px; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.5rem; background: transparent; box-shadow: none; transition: color 0.25s, border-color 0.25s, transform 0.25s; } .tp-ct-modal button.tp-ct-close:hover, .tp-ct-modal button.tp-ct-close:focus-visible { border-color: var(--tp-primary, var(--brand-orange)); color: var(--tp-primary, var(--brand-orange)); transform: rotate(90deg); } .tp-ct-primary { display: flex; flex-wrap: wrap; gap: 0.8rem; margin-top: 2.2rem; } .tp-ct-primary > .tp-ct-action { flex: 1 1 0; justify-content: center; min-width: 10rem; } /* The main action (Open WebUI, Start, Resume) gets its own row; the others share the next. */ .tp-ct-primary > .tp-ct-action.tp-ct-main { flex-basis: 100%; } .tp-ct-section { margin: 2.2rem 0 0.9rem; color: var(--tp-muted, var(--alt-text-color)); font: 600 1rem var(--tp-font-heading, inherit); letter-spacing: 0.115em; text-transform: uppercase; } .tp-ct-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: 0.8rem; } .tp-ct-modal .tp-ct-action { display: inline-flex; align-items: center; gap: 1rem; box-sizing: border-box; min-height: 4.4rem; margin: 0; padding: 0.9rem 1.4rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 12px; color: var(--tp-text, var(--text-color)); font: 600 1.3rem/1.2 var(--tp-font-heading, inherit); letter-spacing: 0; text-align: left; text-decoration: none; text-transform: none; background: var(--tp-subtle, transparent); box-shadow: none; cursor: pointer; transition: transform 0.25s, border-color 0.25s, color 0.25s, background-color 0.25s, box-shadow 0.25s; } .tp-ct-modal .tp-ct-action > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .tp-ct-modal .tp-ct-action:focus:not(:focus-visible) { outline: none; } .tp-ct-modal .tp-ct-action .fa { width: 1.6rem; color: var(--tp-primary, var(--brand-orange)); font-size: 1.4rem; text-align: center; } .tp-ct-modal .tp-ct-action:hover, .tp-ct-modal .tp-ct-action:focus-visible { border-color: var(--tp-primary, var(--brand-orange)); color: var(--tp-text, var(--text-color)); background-color: var(--tp-primary-soft, transparent); transform: translateY(-1px); } .tp-ct-modal .tp-ct-action.tp-ct-main, .tp-ct-modal .tp-ct-action.tp-ct-main:hover, .tp-ct-modal .tp-ct-action.tp-ct-main:focus-visible { border-color: transparent; color: var(--tp-primary-text, #ffffff); background: var(--tp-gradient, var(--brand-orange)); box-shadow: 0 12px 24px -14px var(--tp-primary, transparent); } .tp-ct-modal .tp-ct-action.tp-ct-main .fa { color: inherit; } .tp-ct-modal .tp-ct-action.tp-ct-danger-soft:not(.tp-ct-main) .fa, .tp-ct-modal .tp-ct-action.tp-ct-danger .fa { color: var(--tp-danger, #dc2626); } .tp-ct-modal .tp-ct-action.tp-ct-danger:hover, .tp-ct-modal .tp-ct-action.tp-ct-danger:focus-visible, .tp-ct-modal .tp-ct-action.tp-ct-danger-soft:not(.tp-ct-main):hover, .tp-ct-modal .tp-ct-action.tp-ct-danger-soft:not(.tp-ct-main):focus-visible { border-color: var(--tp-danger, #dc2626); background-color: var(--tp-danger-soft, transparent); } .tp-ct-modal .tp-ct-action.tp-ct-accent .fa { color: var(--tp-warn-text, #b45309); } .tp-ct-modal .tp-ct-link:after { margin-left: auto; color: var(--tp-muted, var(--alt-text-color)); font: normal 1.1rem/1 FontAwesome; content: "\f08e"; } .tp-ct-modal .tp-ct-main.tp-ct-link:after { margin-left: 0; color: inherit; } /* ---- VMs: expand button and the details area under an open VM */ #kvm_list td.vm-name { white-space: nowrap; } /* The VM list's actions column (start / stop, group, menu), as on the Docker tab. */ #kvm_table th.tp-vm-actions, #kvm_list > tr > td.tp-vm-actions { width: 1%; padding-right: 1.9rem; white-space: nowrap; } /* "+ Group" above the VM list, on the right. */ .tp-vm-toolbar { justify-content: flex-end; } #kvm_list button.tp-vm-expand { display: inline-grid; place-items: center; width: 2.8rem; min-width: 0; height: 2.8rem; min-height: 0; margin: 0 1rem 0 0; padding: 0; vertical-align: middle; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 999px; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.1rem; background: var(--tp-elevated, transparent); box-shadow: none; transition: border-color 0.3s, color 0.3s, background-color 0.3s; } #kvm_list button.tp-vm-expand .fa { transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); } #kvm_list button.tp-vm-expand:hover, #kvm_list button.tp-vm-expand:focus-visible { border-color: var(--tp-primary, var(--brand-orange)); color: var(--tp-primary, var(--brand-orange)); } #kvm_list button.tp-vm-expand.tp-open { border-color: transparent; color: var(--tp-primary-text, #ffffff); background: var(--tp-gradient, var(--brand-orange)); } #kvm_list button.tp-vm-expand.tp-open .fa { transform: rotate(90deg); } /* The open VM and its details read as one block, marked by an accent edge. */ #kvm_list > tr.tp-vm-open > td { background-color: var(--tp-primary-soft, transparent); } #kvm_list > tr.tp-vm-open > td:first-child { box-shadow: inset 3px 0 0 var(--tp-primary, var(--brand-orange)); } /* The details are one cell holding inner tables, which would cover a shadow; the edge is painted as the cell's background and the tables start 3px further in. */ #kvm_list > tr.tp-vm-details > td:first-child { padding-left: 3px !important; background: linear-gradient(var(--tp-primary, var(--brand-orange)), var(--tp-primary, var(--brand-orange))) left top / 3px 100% no-repeat, var(--tp-subtle, transparent); } #kvm_list > tr.tp-vm-details > td { background-color: var(--tp-subtle, transparent); } #kvm_list > tr.tp-vm-details table.snapshot thead td, #kvm_list > tr.tp-vm-details table.snapshot tr:first-child td { color: var(--tp-muted, var(--alt-text-color)); font: 600 1.05rem var(--tp-font-heading, inherit); letter-spacing: 0.08em; text-transform: uppercase; } /* ---- Main, Array Operation: one grid of cards The forms and tables stay in place but take no room (display: contents), so their rows become the grid's cards. Array spans the full width as the status card. */ .tp-ops { /* Wrapping rows that always fill the full width: no gaps at the end of a row, whatever the window width and however many cards there are. */ display: flex; flex-wrap: wrap; gap: 1.4rem; margin: 1.6rem 0 2.4rem; } .tp-ops > form, .tp-ops table.ArrayOperation-Table, .tp-ops table.ArrayOperation-Table > tbody { display: contents; } .tp-ops > p, .tp-ops > form > p, .tp-ops tr.hidden, .tp-ops tr[hidden], .tp-ops td.tp-op-stats[hidden], .tp-ops .tp-stat[hidden], .tp-ops .tp-progress[hidden], .tp-ops tr.tp-op-skip, .tp-ops td.tp-op-blank { display: none !important; } .tp-ops tr.tp-op { display: flex; flex: 1 1 26rem; flex-direction: column; gap: 1.2rem; min-width: 0; padding: 2rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 16px; background-color: var(--tp-elevated, var(--background-color)); box-shadow: var(--tp-shadow-sm, none); transition: border-color 0.3s, transform 0.3s; } .tp-ops tr.tp-op:hover { border-color: var(--tp-input-border, var(--border-color)); } .tp-ops tr.tp-op > td { display: block; width: auto !important; height: auto; padding: 0 !important; border: 0; color: var(--tp-text, var(--text-color)); text-align: left; white-space: normal; background: none; } /* Card order: status first, then parity, last check, disks, statistics, mover, power. */ .tp-ops tr.tp-op-array { order: 0; } .tp-ops tr.tp-op-running { order: 1; flex-basis: 100%; } .tp-ops tr.tp-op-other { order: 1; } .tp-ops tr.tp-op-parity { order: 2; } .tp-ops tr.tp-op-history { order: 3; } .tp-ops tr.tp-op-disks { order: 4; } .tp-ops tr.tp-op-stats { order: 5; } .tp-ops tr.tp-op-mover { order: 6; } .tp-ops tr.tp-op-power { order: 7; } .tp-ops td.tp-op-head { display: flex !important; align-items: center; gap: 1.2rem; } .tp-op-icon { display: grid; flex: none; place-items: center; width: 3.8rem; height: 3.8rem; border-radius: 12px; color: var(--tp-primary, var(--brand-orange)); font-size: 1.6rem; background-color: var(--tp-primary-soft, transparent); } .tp-op-title { font: 600 1.55rem/1.2 var(--tp-font-heading, inherit); letter-spacing: -0.01em; } .tp-ops td.tp-op-state { order: 1; font-weight: 600; } .tp-ops td.tp-op-desc { order: 2; color: var(--tp-muted, var(--alt-text-color)) !important; font-size: 1.3rem; line-height: 1.75; } .tp-ops td.tp-op-desc :is(strong, b) { color: var(--tp-text, var(--text-color)); } .tp-ops td.tp-op-desc i.fa { width: 1.6rem; margin-right: 0.4rem; color: var(--tp-primary, var(--brand-orange)); } .tp-ops td.tp-op-desc label, .tp-ops td.tp-op-desc small { display: inline-flex; align-items: center; gap: 0.8rem; margin-top: 0.8rem; color: var(--tp-text, var(--text-color)); font-size: 1.25rem; } .tp-ops td.tp-op-desc input[type="checkbox"] { margin: 0 0.8rem 0 0; vertical-align: middle; } .tp-ops td.tp-op-desc label input[type="checkbox"] { margin: 0; } .tp-ops td.tp-op-actions { order: 3; display: flex !important; flex-wrap: wrap; gap: 0.8rem; margin-top: auto; padding-top: 0.4rem !important; } .tp-ops td.tp-op-actions .buttons-spaced { display: contents; } .tp-ops td.tp-op-actions :is(input[type="button"], input[type="submit"], button) { flex: 1 1 0; min-width: 10rem; margin: 0 !important; padding: 1rem 1.8rem; font-size: 1.3rem; } .tp-ops :is(input, button).tp-op-primary:not(:disabled) { border-color: transparent; color: var(--tp-primary-text, #ffffff); background: var(--tp-gradient, var(--brand-orange)); box-shadow: 0 10px 22px -14px var(--tp-primary, transparent); } .tp-ops :is(input, button).tp-op-danger:not(:disabled) { border-color: color-mix(in srgb, var(--tp-danger, #ef4444) 40%, transparent); color: var(--tp-danger, #ef4444); background: var(--tp-danger-soft, transparent); } .tp-ops :is(input, button).tp-op-danger:not(:disabled):hover { border-color: var(--tp-danger, #ef4444); color: #ffffff; background: var(--tp-danger, #ef4444); } /* Array: the status card across the full width; status, text and the button in one row. */ .tp-ops tr.tp-op-array { flex-basis: 100%; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 1.2rem 2.4rem; background: radial-gradient(120% 140% at 0% 0%, var(--tp-primary-soft, transparent), transparent 55%), var(--tp-elevated, var(--background-color)); } /* Not for an empty status cell: the blank rule above hides those, but this one is more specific. */ .tp-ops tr.tp-op-array td.tp-op-state:not(.tp-op-blank) { order: 1; display: inline-flex !important; align-items: center; padding: 0.6rem 1.4rem 0.6rem 1.1rem !important; border-radius: 999px; font: 600 1.5rem var(--tp-font-heading, inherit); background-color: var(--tp-subtle, transparent); } .tp-ops tr.tp-op-array td.tp-op-state i.orb { margin-right: 0.8rem; font-size: 1rem; } .tp-ops tr.tp-op-array td.tp-op-state i.green-orb { color: #22c55e; filter: drop-shadow(0 0 5px rgba(34, 197, 94, 0.8)); } .tp-ops tr.tp-op-array td.tp-op-desc { flex: 1 1 24rem; } .tp-ops tr.tp-op-array td.tp-op-actions { order: 3; flex: 0 0 auto; margin: 0; padding: 0 !important; } .tp-ops tr.tp-op-array td.tp-op-actions :is(input, button) { min-width: 14rem; } /* Parity: the state as a pill, green when valid. */ .tp-ops td.tp-op-state.tp-op-good, .tp-ops td.tp-op-state.tp-op-warn { align-self: flex-start; padding: 0.4rem 1.2rem !important; border-radius: 999px; font-size: 1.25rem; } .tp-ops td.tp-op-state.tp-op-good { color: var(--tp-success-text, #15803d); background-color: var(--tp-success-bg, #dcfce7); } .tp-ops td.tp-op-state.tp-op-warn { color: var(--tp-warn-text, #b45309); background-color: var(--tp-warn-bg, #fef3c7); } .tp-ops td.tp-op-state.tp-op-good:before, .tp-ops td.tp-op-state.tp-op-warn:before { margin-right: 0.6rem; font: normal 1.2rem/1 FontAwesome; content: "\f058"; } .tp-ops td.tp-op-state.tp-op-warn:before { content: "\f071"; } /* Running operation: state pill, progress bar and the figures as tiles. */ .tp-ops tr.tp-op-running td.tp-op-state { align-self: flex-start; padding: 0.4rem 1.2rem !important; border-radius: 999px; color: var(--tp-primary, var(--brand-orange)); font-size: 1.25rem; background-color: var(--tp-primary-soft, transparent); } .tp-ops tr.tp-op-running td.tp-op-state:before { display: inline-block; margin-right: 0.6rem; font: normal 1.2rem/1 FontAwesome; content: "\f110"; animation: fa-spin 2s linear infinite; } .tp-op-running .tp-op-icon { color: var(--tp-primary-text, #ffffff); background: var(--tp-gradient, var(--brand-orange)); } .tp-ops td.tp-op-stats { order: 3; display: grid !important; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 1rem 1.4rem; } /* Mover: full width while it runs, the status tiles above its button. */ .tp-ops tr.tp-op-mover.tp-op-busy { flex-basis: 100%; } .tp-ops tr.tp-op-mover td.tp-op-actions { order: 4; } .tp-ops .tp-stat.tp-stat-wide { grid-column: 1 / -1; } .tp-ops .tp-stat > td { display: block; padding: 0 !important; border: 0; text-align: left; background: none; } .tp-ops .tp-stat > td.tp-stat-label span { font: inherit; } .tp-ops tr.tp-op-running td.tp-op-actions { order: 4; flex: 0 0 auto; max-width: 32rem; } .tp-progress { grid-column: 1; grid-row: 1; height: 1rem; overflow: hidden; border-radius: 999px; background-color: var(--tp-border, var(--border-color)); } .tp-progress > span { display: block; width: 0; height: 100%; border-radius: 999px; background: var(--tp-gradient, var(--brand-orange)); transition: width 0.8s cubic-bezier(0.16, 1, 0.3, 1); } .tp-progress-label { grid-column: 2; grid-row: 1; color: var(--tp-text, var(--text-color)); font: 600 1.5rem var(--tp-font-heading, inherit); font-variant-numeric: tabular-nums; } .tp-stats { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 0.8rem; } .tp-stat { display: flex; flex-direction: column; gap: 0.4rem; padding: 1.2rem 1.4rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 12px; background-color: var(--tp-subtle, transparent); } .tp-stat-label { color: var(--tp-muted, var(--alt-text-color)); font: 600 1.05rem var(--tp-font-heading, inherit); letter-spacing: 0.07em; text-transform: uppercase; } .tp-ops td.tp-stat-value { display: block; padding: 0 !important; border: 0; color: var(--tp-text, var(--text-color)); font: 600 1.45rem/1.3 var(--tp-font-heading, inherit); font-variant-numeric: tabular-nums; white-space: normal !important; overflow-wrap: anywhere; background: none; } .tp-op-power .tp-op-icon { color: var(--tp-danger, #ef4444); background-color: var(--tp-danger-soft, transparent); } /* Unraid's ArrayOperation.css has its own phone and tablet layout for these rows (flex-basis per cell, by position); in the cards that turned into tall blobs and squeezed text. */ @media (max-width: 769px) { .tp-ops tr.tp-op > td { flex: 0 0 auto !important; flex-basis: auto !important; width: auto !important; max-width: 100%; } .tp-ops tr.tp-op > td.tp-op-actions { width: 100% !important; } .tp-ops tr.tp-op-array { flex-direction: column; align-items: stretch; } .tp-ops tr.tp-op-array td.tp-op-state { align-self: flex-start; } } @media (max-width: 767px) { .tp-ops { grid-template-columns: minmax(0, 1fr); } .tp-ops tr.tp-op-array td.tp-op-actions { flex: 1 1 100%; } } /* ---- VMs: snapshot buttons in the details */ #kvm_list button.tp-snap-create { display: inline-flex; align-items: center; gap: 0.6rem; min-height: 0; margin: 0 0 0 1.4rem; padding: 0.45rem 1.2rem; border: 0; border-radius: 999px; color: var(--tp-primary-text, #ffffff); font: 600 1.1rem var(--tp-font-heading, inherit); letter-spacing: 0; text-transform: none; vertical-align: middle; background: var(--tp-gradient, var(--brand-orange)); box-shadow: 0 8px 18px -12px var(--tp-primary, transparent); } #kvm_list button.tp-snap-create:hover { transform: translateY(-1px); } #kvm_list .tp-snap-actions { display: inline-flex; gap: 0.4rem; margin-left: 1.2rem; vertical-align: middle; } #kvm_list button.tp-snap-button { display: inline-grid; place-items: center; width: 2.6rem; min-width: 0; height: 2.6rem; min-height: 0; margin: 0; padding: 0; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 999px; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.1rem; background: var(--tp-elevated, transparent); box-shadow: none; transition: border-color 0.25s, color 0.25s, background-color 0.25s; } #kvm_list button.tp-snap-button:hover, #kvm_list button.tp-snap-button:focus-visible { border-color: var(--tp-primary, var(--brand-orange)); color: var(--tp-primary, var(--brand-orange)); } #kvm_list button.tp-snap-button.tp-snap-danger:hover, #kvm_list button.tp-snap-button.tp-snap-danger:focus-visible { border-color: var(--tp-danger, #ef4444); color: #ffffff; background-color: var(--tp-danger, #ef4444); } #kvm_list td.tp-snap-empty { padding: 1.2rem 1.6rem 1.2rem 5.6rem !important; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.25rem; } /* ---- Docker editor: live port check, a tick or cross inside the right edge of the field */ .tp-port-field { position: relative; display: inline-flex; align-items: center; max-width: 100%; } .tp-port-field > input { padding-right: 4rem !important; } .tp-port-mark { position: absolute; right: 1rem; display: grid; place-items: center; width: 2.2rem; height: 2.2rem; border-radius: 999px; font: normal 1.2rem/1 FontAwesome; opacity: 0; transform: scale(0.6); pointer-events: none; transition: opacity 0.2s, transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background-color 0.2s, color 0.2s; } .tp-port-ok > .tp-port-mark, .tp-port-bad > .tp-port-mark { opacity: 1; transform: scale(1); } .tp-port-ok > .tp-port-mark { color: #ffffff; background-color: #22c55e; box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.18); } .tp-port-ok > .tp-port-mark:before { content: "\f00c"; } .tp-port-bad > .tp-port-mark { color: #ffffff; background-color: var(--tp-danger, #ef4444); box-shadow: 0 0 0 4px color-mix(in srgb, var(--tp-danger, #ef4444) 22%, transparent); } .tp-port-bad > .tp-port-mark:before { content: "\f00d"; } .tp-port-ok > input { border-color: rgba(34, 197, 94, 0.55) !important; } .tp-port-bad > input { border-color: var(--tp-danger, #ef4444) !important; box-shadow: 0 0 0 3px color-mix(in srgb, var(--tp-danger, #ef4444) 16%, transparent) !important; } .tp-port-message { display: flex; align-items: center; gap: 0.6rem; color: var(--tp-danger, #ef4444); font-size: 1.2rem; font-weight: 500; } .tp-port-message:before { font: normal 1.2rem/1 FontAwesome; content: "\f071"; } .tp-port-message[hidden] { display: none; } /* ---- Docker editor: "Show docker allocations" view */ #dockerAllocations > dl[hidden] { display: none !important; } .tp-alloc { margin: 1.2rem 0 2rem; overflow: hidden; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 16px; background-color: var(--tp-elevated, var(--background-color)); box-shadow: var(--tp-shadow-sm, none); } .tp-alloc-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem 2rem; padding: 1.4rem 1.6rem; border-bottom: 1px solid var(--tp-border, var(--border-color)); } #dockerAllocations .tp-alloc-head input.tp-alloc-search { flex: 0 1 36rem; min-height: 3.8rem; margin: 0; padding: 0.8rem 1.4rem 0.8rem 3.8rem; border-radius: 999px; background: var(--tp-subtle, transparent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b93a5' stroke-width='2.2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M20 20l-3.5-3.5'/%3E%3C/svg%3E") 1.3rem center / 1.5rem no-repeat; } .tp-alloc-count { color: var(--tp-muted, var(--alt-text-color)); font-size: 1.2rem; font-variant-numeric: tabular-nums; } .tp-alloc-row { display: grid; grid-template-columns: minmax(14rem, 1.1fr) 11rem 13rem minmax(0, 3fr); align-items: center; gap: 1.2rem; padding: 1rem 1.6rem; border-bottom: 1px solid var(--tp-border, var(--border-color)); } .tp-alloc-list { max-height: 48rem; overflow-y: auto; scrollbar-width: thin; } .tp-alloc-list > .tp-alloc-row:last-child { border-bottom: 0; } .tp-alloc-list > .tp-alloc-row:hover { background-color: var(--tp-primary-soft, transparent); } .tp-alloc-row.tp-alloc-columns { padding-top: 0.9rem; padding-bottom: 0.9rem; color: var(--tp-muted, var(--alt-text-color)); font: 600 1.05rem var(--tp-font-heading, inherit); letter-spacing: 0.07em; text-transform: uppercase; background-color: var(--tp-subtle, transparent); } .tp-alloc-name { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; min-width: 0; color: var(--tp-text, var(--text-color)); font: 600 1.3rem var(--tp-font-heading, inherit); overflow-wrap: anywhere; } .tp-alloc-badge { padding: 0.2rem 0.8rem; border-radius: 999px; color: var(--tp-primary-text, #ffffff); font-size: 1rem; background: var(--tp-gradient, var(--brand-orange)); } .tp-alloc-row.tp-alloc-self { background-color: var(--tp-primary-soft, transparent); box-shadow: inset 3px 0 0 var(--tp-primary, var(--brand-orange)); } .tp-alloc-pill { display: inline-block; padding: 0.25rem 1rem; border-radius: 999px; font-size: 1.15rem; font-weight: 600; } .tp-alloc-pill.tp-net-bridge { color: var(--tp-primary, #4f8bff); background-color: var(--tp-primary-soft, transparent); } .tp-alloc-pill.tp-net-host { color: #a78bfa; background-color: rgba(167, 139, 250, 0.14); } .tp-alloc-pill.tp-net-custom { color: #2dd4bf; background-color: rgba(45, 212, 191, 0.14); } .tp-alloc-ip { color: var(--tp-text, var(--text-color)); font-size: 1.25rem; font-variant-numeric: tabular-nums; } .tp-alloc-ports { display: flex; flex-wrap: wrap; gap: 0.5rem; } .tp-alloc-port { padding: 0.2rem 0.8rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 7px; color: var(--tp-text, var(--text-color)); font-size: 1.2rem; font-variant-numeric: tabular-nums; background-color: var(--tp-subtle, transparent); } .tp-alloc-port.tp-alloc-range { border-style: dashed; } .tp-alloc-port.tp-alloc-clash { border-color: var(--tp-danger, #ef4444); color: var(--tp-danger, #ef4444); background-color: var(--tp-danger-soft, transparent); } .tp-alloc-none, .tp-alloc-empty { color: var(--tp-muted, var(--alt-text-color)); font-size: 1.2rem; } .tp-alloc-empty { margin: 0; padding: 2rem 1.6rem; text-align: center; } @media (max-width: 767px) { .tp-alloc-row { grid-template-columns: minmax(0, 1fr) auto; gap: 0.6rem 1rem; } .tp-alloc-row.tp-alloc-columns { display: none; } .tp-alloc-ip { grid-column: 1; } .tp-alloc-ports { grid-column: 1 / -1; } } /* ---- Phones: each container becomes a small card row 1: icon and name; row 2: status, CPU, memory, web UI; row 3: autostart and actions; row 4: its addresses; row 5: its mounts (issue #27). The version is left out here; "All columns" still shows Unraid's full table. */ @media (max-width: 767px) { .TableContainer:has(> #docker_containers.tp-compact) { overflow-x: visible; } #docker_containers.tp-compact { min-width: 0 !important; } #docker_containers.tp-compact > thead { display: none; } #docker_containers.tp-compact > tbody > tr.sortable { grid-template-columns: minmax(0, 1fr) auto auto auto; row-gap: 1rem; column-gap: 1.2rem; padding: 1.4rem 1.4rem 1.2rem; } #docker_containers.tp-compact .tp-c-name { grid-row: 1; grid-column: 1 / -1; } #docker_containers.tp-compact .tp-c-status { grid-row: 2; grid-column: 1; } #docker_containers.tp-compact td.tp-c-load > [class^="cpu-"] { grid-row: 2; grid-column: 2; } #docker_containers.tp-compact td.tp-c-load > [class^="mem-"] { grid-row: 2; grid-column: 3; } #docker_containers.tp-compact .tp-c-webui { grid-row: 2; grid-column: 4; } #docker_containers.tp-compact .tp-c-auto { grid-row: 3; grid-column: 1 / 3; } #docker_containers.tp-compact .tp-c-actions { grid-row: 3; grid-column: 3 / -1; } #docker_containers.tp-compact .tp-c-version { display: none !important; } /* Addresses and mounts, each with a small icon; Unraid's "show more" arrow still folds a long list of mounts. A container without either has no such line. */ #docker_containers.tp-compact > tbody > tr.sortable > :is(td.tp-c-ports, td.tp-c-volumes) { display: flex !important; align-items: flex-start; gap: 0.8rem; grid-column: 1 / -1; min-width: 0; padding: 0 !important; color: var(--tp-muted, var(--alt-text-color)); font: 1.15rem/1.5 var(--tp-font-body, inherit); overflow-wrap: break-word; } #docker_containers.tp-compact > tbody > tr.sortable > td.tp-c-ports { grid-row: 4; } #docker_containers.tp-compact > tbody > tr.sortable > td.tp-c-volumes { grid-row: 5; margin-top: -0.4rem; } #docker_containers.tp-compact > tbody > tr.sortable > :is(td.tp-c-ports, td.tp-c-volumes).tp-c-empty { display: none !important; } #docker_containers.tp-compact > tbody > tr.sortable > :is(td.tp-c-ports, td.tp-c-volumes)::before { flex: none; width: 1.6rem; padding-top: 0.1rem; font: 1.2rem FontAwesome; text-align: center; opacity: 0.8; } #docker_containers.tp-compact > tbody > tr.sortable > td.tp-c-ports::before { content: "\f0ac"; } #docker_containers.tp-compact > tbody > tr.sortable > td.tp-c-volumes::before { content: "\f07c"; } /* Unraid breaks paths at any letter; here only after a "/" (enhance.js marks the places). */ #docker_containers.tp-compact > tbody > tr.sortable > :is(td.tp-c-ports, td.tp-c-volumes) .docker_readmore { flex: 1; min-width: 0; word-break: normal; } #docker_containers.tp-compact > tbody > tr.sortable > td.tp-c-volumes .readmore-js-toggle { flex: none; width: auto !important; margin-left: 0.4rem; } #docker_containers.tp-compact td.tp-c-load > [class^="mem-"] { overflow: hidden; max-width: 14rem; text-overflow: ellipsis; white-space: nowrap; } :is(#docker_list, #kvm_list) > tr.tp-group > td { padding: 0.7rem 1.4rem; } .tp-group-head .tp-actions { gap: 0; } /* The VM table scrolls sideways; a section's header stays in view with its buttons. */ #kvm_list > tr.tp-group .tp-group-head { position: sticky; left: 1.4rem; box-sizing: border-box; max-width: calc(100vw - 6rem); } .tp-group-head .tp-actions .tp-icon-button { width: 2.8rem; } /* The header wraps; the grip stays beside the name on its first line. */ .tp-group-grip { top: 0; margin-top: 0; } .tp-toolbar { align-items: stretch; flex-direction: column; } .tp-tools { flex-wrap: wrap; } .tp-toolbar input.tp-search { flex: 1 1 100%; width: 100%; } .tp-summary-value { font-size: 2rem; } .tp-heading h1 { font-size: 2.3rem; } .tp-heading:has(.tp-panel-search) { align-items: stretch; flex-direction: column; gap: 1.4rem; } .tp-heading .tp-panel-search { flex-basis: auto; max-width: none; } .tp-modal-backdrop:has(.tp-ct-modal) { place-items: end center; padding: 0; } .tp-modal.tp-ct-modal { width: 100%; max-height: 88vh; max-height: 88dvh; border-radius: 22px 22px 0 0; padding: 2rem 1.6rem calc(2rem + env(safe-area-inset-bottom, 0px)); } .tp-ct-primary > .tp-ct-action { min-width: calc(50% - 0.4rem); } .tp-ct-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } /* Dashboard, Processor tile (marked by enhance.js): chips for power and temperature, the cores as a grid of small tiles. */ .tp-cpu-tile .tp-cpu-source { display: none !important; } .tp-cpu-stats { display: inline-flex; flex-wrap: wrap; gap: 0.6rem; } .tp-cpu-chip { display: inline-flex; align-items: center; gap: 0.6rem; padding: 0.4rem 1.1rem 0.4rem 0.9rem; border: 1px solid var(--tp-border); border-radius: 999px; color: var(--tp-text); font: 600 1.2rem/1.2 var(--tp-font-heading); font-variant-numeric: tabular-nums; white-space: nowrap; background: var(--tp-subtle); } .tp-cpu-chip[hidden] { display: none; } .tp-cpu-chip > i { color: var(--tp-primary); font-size: 1.15rem; } .tp-cpu-chip > i.fa-thermometer-half { color: #f59e0b; } /* Package lines: "CPU 0" with its chips; hidden when nothing is known. */ .tp-cpu-tile tr.tp-cpu-gap, .tp-cpu-tile tr.tp-cpu-pkg.tp-cpu-pkg-empty { display: none !important; } .tp-cpu-tile tr.tp-cpu-pkg span.cpu_open { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; width: auto; margin-top: 0.4rem; } .tp-cpu-pkg-name { color: var(--tp-muted); font: 600 1.1rem/1 var(--tp-font-heading); letter-spacing: 0.06em; text-transform: uppercase; } /* The core rows flow into a grid: the tile's rows become blocks, the core rows share a line. */ table.dashboard > tbody.tp-cpu-tile { display: grid !important; grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr)); column-gap: 0.6rem; } /* The grid is inset like the tile's other rows (their cells have 2rem side padding); those rows reach back out to the edges so their own padding stays as it was. */ table.dashboard > tbody.tp-cpu-tile { box-sizing: border-box; padding-right: 2rem !important; padding-left: 2rem !important; } tbody.tp-cpu-tile > tr { display: block; grid-column: 1 / -1; margin: 0 -2rem; } tbody.tp-cpu-tile > tr.tp-core { margin: 0; } tbody.tp-cpu-tile > tr > td { display: block; } tbody.tp-cpu-tile > tr.tp-core { grid-column: auto; padding: 0.3rem 0; } tbody.tp-cpu-tile > tr.tp-core > td { display: grid; grid-template-columns: 1fr auto; grid-template-areas: "label value" "bar bar"; align-items: center; gap: 0.5rem 0.6rem; box-sizing: border-box; height: 100%; padding: 0.8rem 1rem !important; border: 1px solid var(--tp-border); border-radius: 10px; background: var(--tp-subtle); } tbody.tp-cpu-tile > tr.tp-core span.w26 { grid-area: label; display: flex; align-items: center; gap: 0.4rem; width: auto !important; min-width: 0; max-width: none !important; /* Unraid limits it to 26% on phones */ overflow: hidden !important; text-overflow: ellipsis !important; margin: 0; color: var(--tp-muted); font: 600 1.15rem/1.2 var(--tp-font-heading); white-space: nowrap; } /* Intel hybrid CPUs: "P" or "E" after the core numbers. */ .tp-core-type { flex: none; padding: 0.1rem 0.4rem; border-radius: 4px; color: var(--tp-primary, var(--brand-orange)); font: 700 0.9rem/1.2 var(--tp-font-heading, inherit); background: color-mix(in srgb, var(--tp-primary, var(--brand-orange)) 14%, transparent); } tbody.tp-cpu-tile > tr.tp-core span.w72 { display: contents; } tbody.tp-cpu-tile > tr.tp-core .load { grid-area: value; width: auto !important; margin: 0 !important; font: 600 1.25rem/1.2 var(--tp-font-heading); font-variant-numeric: tabular-nums; text-align: right; } tbody.tp-cpu-tile > tr.tp-core .usage-disk { grid-area: bar; width: 100% !important; height: 0.5rem !important; margin: 0 !important; } /* Core with hyper-threading: one tile, both loads at the top right, two thin bars below. */ tbody.tp-cpu-tile > tr.tp-core-pair > td { grid-template-columns: minmax(0, max-content) 1fr auto; column-gap: 0.5rem; grid-template-areas: "label v1 v2" "b1 b1 b1" "b2 b2 b2"; row-gap: 0.4rem; } tbody.tp-cpu-tile > tr.tp-core-pair span.w36 { display: contents; } tbody.tp-cpu-tile > tr.tp-core-pair span.w36:nth-of-type(2) .load { grid-area: v1; text-align: right; } tbody.tp-cpu-tile > tr.tp-core-pair span.w36:nth-of-type(2) .usage-disk { grid-area: b1; } tbody.tp-cpu-tile > tr.tp-core-pair span.w36:nth-of-type(3) .load { grid-area: v2; } tbody.tp-cpu-tile > tr.tp-core-pair span.w36:nth-of-type(3) .usage-disk { grid-area: b2; } tbody.tp-cpu-tile > tr.tp-core-pair .load { font-size: 1.15rem; } tbody.tp-cpu-tile > tr.tp-core-pair .usage-disk { height: 0.35rem !important; } /* ---- Docker (Aurora view): "Orphans", Unraid's "(orphan image)" rows in a section of their own. Unraid shows them in its advanced view only; in the section they are always listed. */ #docker_list > tr.tp-orphan:not(.tp-filtered-out) { display: table-row !important; } #docker_containers.tp-compact > tbody > tr.tp-orphan:not(.tp-filtered-out) { display: grid !important; grid-template-columns: minmax(22rem, 2.4fr) minmax(0, 5fr) 18rem; align-items: center; column-gap: 1.4rem; min-height: 6.4rem; padding: 0 1.9rem; } #docker_containers.tp-compact > tbody > tr.tp-orphan > td { width: auto !important; padding: 0.8rem 0 !important; border: 0; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.2rem; overflow-wrap: anywhere; } #docker_containers.tp-compact > tbody > tr.tp-orphan > td:last-child { text-align: right; white-space: nowrap; } /* "Remove all" in the Orphans header. */ .tp-actions button.tp-group-remove-all { display: inline-flex; align-items: center; gap: 0.6rem; min-width: 0; height: 3rem; margin: 0; padding: 0 1.2rem; border: 1px solid color-mix(in srgb, var(--red-600, #dc2626) 45%, transparent); border-radius: 999px; color: var(--red-600, #dc2626); font-size: 1.2rem; font-weight: 600; letter-spacing: 0; background: color-mix(in srgb, var(--red-600, #dc2626) 10%, transparent); box-shadow: none; transition: background-color 0.35s, transform 0.35s; } .tp-actions button.tp-group-remove-all:hover { border-color: var(--red-600, #dc2626); color: var(--red-600, #dc2626); background: color-mix(in srgb, var(--red-600, #dc2626) 18%, transparent); transform: translateY(-1px); } /* Phones: icon and date on the first line, the image ID and tags under them. */ @media (max-width: 767px) { #docker_containers.tp-compact > tbody > tr.tp-orphan:not(.tp-filtered-out) { grid-template-columns: minmax(0, 1fr) auto; row-gap: 0; padding: 0.4rem 1.6rem 0.8rem; } #docker_containers.tp-compact > tbody > tr.tp-orphan > td:nth-child(2) { grid-column: 1 / -1; grid-row: 2; padding-top: 0 !important; } } AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/styles/layout-sidebar.css <<'AURORA_THEME_EOF' /* * Sidebar layout for Unraid's top-navigation themes (Black, White). * Moves #menu to the left edge on wide screens; phones and tablets keep the stock top bar. * Active when has the tp-sidebar class, which the plugin sets from its settings. */ @media (min-width: 1024px) and (max-width: 1359px) { :root.tp-sidebar.Theme--nav-top #UserProfile > div:first-of-type { display: none; } } /* The sidebar toggle only exists where there is a sidebar. */ #header .tp-sidebar-toggle { display: none; } @media (min-width: 1024px) { :root.tp-sidebar.Theme--nav-top { --tp-sidebar-width: 22rem; --tp-sidebar-line: rgba(255, 255, 255, 0.08); /* The Apps tab keeps its search bar below a top menu; with the menu in the sidebar it belongs at the very top. */ --search-area-top: 0; /* The slim top bar sits on the page surface, so its text uses the normal text colours. */ --header-text-primary: var(--tp-text, var(--text-color)); --header-text-secondary: var(--tp-muted, var(--alt-text-color)); body { padding-left: var(--tp-sidebar-width); } #header { min-height: 6.4rem; padding-left: 2.4rem; border-bottom: 1px solid var(--tp-border, var(--border-color)); color: var(--text-color); background-color: var(--tp-elevated, var(--background-color)); background-image: none; } /* Display Settings, "Show banner": the banner fills the top bar, whose text then uses the header colours (custom ones included) instead of the page's. With "Show banner background color fade" it fades into the header colour behind the buttons. */ #header.image { --header-text-primary: var(--tp-header-text, var(--header-text-color)); --header-text-secondary: var(--tp-header-muted, var(--alt-text-color)); color: var(--tp-header-text, var(--header-text-color)); background: var(--customer-header-background-image) center / cover no-repeat var(--tp-header-bg, var(--header-background-color)); } &.has-banner-gradient #header.image { background: linear-gradient(90deg, var(--tp-header-bg, var(--header-background-color)) 0, transparent 30%, transparent 48%, var(--tp-header-bg, var(--header-background-color)) 85%), var(--customer-header-background-image) center / cover no-repeat var(--tp-header-bg, var(--header-background-color)); } #header.image::before { content: none; } #header.image #UserProfile > div:first-of-type { background: color-mix(in srgb, var(--tp-header-bg, var(--header-background-color)) 72%, transparent); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); } /* Unraid's logo, version and update badge become the sidebar's brand block. They stay where they are in the page and are only positioned over the top of the sidebar. */ #header unraid-header-os-version { --color-header-text-secondary: var(--tp-header-muted, #a7b0c0); position: fixed; top: 0; left: 0; width: var(--tp-sidebar-width); padding: 1.6rem 1.4rem 0; box-sizing: border-box; } #header unraid-header-os-version > div { max-width: none; margin: 0; gap: 1.2rem; } /* Version and update notice share one row under the logo */ #header unraid-header-os-version > div > div { flex-direction: row; flex-wrap: nowrap; align-items: center; gap: 1.2rem; margin: 0; } /* Version: just the number, prominent, without the info icon. The element still opens Unraid's version menu when clicked. */ #header unraid-header-os-version span[role="button"][aria-haspopup] { justify-content: flex-start; gap: 0.8rem; color: var(--tp-header-text, #ffffff); font: 700 2rem/1.2 var(--tp-font-heading, inherit); letter-spacing: -0.01em; text-decoration: none; } #header unraid-header-os-version span[role="button"][aria-haspopup] > svg { display: none; } #header unraid-header-os-version span[role="button"][aria-haspopup]:hover { color: var(--tp-primary-ii, var(--brand-orange)); } #menu { position: fixed; top: 0; bottom: 0; left: 0; display: flex; flex-direction: column; gap: 1.2rem; width: var(--tp-sidebar-width); padding: 12rem 1.2rem 4rem; box-sizing: border-box; overflow-y: auto; border-right: 1px solid var(--tp-sidebar-line); border-bottom: 0; background-color: var(--header-background-color); scrollbar-width: thin; } #footer, .navigationSection { left: var(--tp-sidebar-width); width: calc(100% - var(--tp-sidebar-width)); } /* ---- Sidebar toggle at the left of the top bar (added in AuroraTheme.page). Collapsed, the sidebar and the logo above it slide out and the page takes the full width. */ #header .tp-sidebar-toggle { display: grid; flex: none; order: -1; place-items: center; width: 3.6rem; height: 3.6rem; min-width: 0; margin: 0 1.6rem 0 0; padding: 0; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 10px; color: var(--tp-muted, var(--alt-text-color)); background: var(--tp-elevated, transparent); box-shadow: none; cursor: pointer; transition: border-color 0.35s, color 0.35s; } #header .tp-sidebar-toggle:hover, #header .tp-sidebar-toggle:focus-visible { border-color: var(--tp-primary, var(--brand-orange)); color: var(--tp-primary, var(--brand-orange)); background: var(--tp-elevated, transparent); transform: none; } #header .tp-sidebar-toggle svg { width: 1.8rem; height: 1.8rem; } &.tp-sidebar-collapsed { body { padding-left: 0; } /* The page lines up with the toggle in the top bar. */ #displaybox { padding-left: 2.4rem; } #menu, #header unraid-header-os-version { visibility: hidden; transform: translateX(-100%); } #footer, .navigationSection { left: 0; width: 100%; } } /* Only after a click, so a page opened with the sidebar hidden does not animate. */ &.tp-sidebar-animate { body, #footer, .navigationSection { transition: padding-left 0.3s ease, left 0.3s ease, width 0.3s ease; } #menu, #header unraid-header-os-version { transition: transform 0.3s ease, visibility 0.3s; } } /* ---- Top right: uptime chip, server identity, notification and menu buttons. This is Unraid's profile component; only its own elements are restyled. Its second div is "div ~ div", not :last-of-type, which makes Chrome restyle whole tables. */ #UserProfile { flex-direction: row; align-items: center; gap: 1.4rem; padding: 0 2.4rem 0 0; } /* Uptime and licence line becomes a quiet chip */ #UserProfile > div:first-of-type { flex-direction: row; align-items: center; gap: 0.8rem; padding: 0.5rem 0.6rem 0.5rem 1.2rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 999px; color: var(--tp-muted, var(--alt-text-color)); font-family: var(--tp-font-heading, inherit); font-weight: 500; white-space: nowrap; background-color: var(--tp-subtle, var(--mild-background-color)); } #UserProfile > div:first-of-type em { margin-left: 0.2rem; padding: 0.2rem 0.8rem; border-radius: 999px; color: var(--tp-primary-text, #ffffff); font-style: normal; background: var(--tp-gradient, var(--brand-orange)); } #UserProfile > div ~ div { gap: 0.8rem; } /* Server identity: icon tile, name on top, description underneath */ #UserProfile > div ~ div > div:first-child { display: grid; grid-template-columns: auto auto; align-items: center; column-gap: 1rem; margin-right: 0.6rem; } #UserProfile > div ~ div > div:first-child:before { display: grid; grid-row: 1 / span 2; place-items: center; width: 3.4rem; height: 3.4rem; border-radius: 50%; color: #ffffff; font: normal 1.4rem/1 FontAwesome; background: var(--tp-gradient, var(--brand-orange)); box-shadow: 0 4px 10px -4px var(--tp-primary, transparent); content: "\f233"; } #UserProfile > div ~ div > div:first-child > span:nth-child(1) { grid-row: 2; grid-column: 2; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.1rem; line-height: 1.3; text-align: left; } #UserProfile > div ~ div > div:first-child > span:nth-child(2) { display: none !important; } #UserProfile > div ~ div > div:first-child > span:nth-child(3) { grid-row: 1; grid-column: 2; font: 600 1.4rem/1.3 var(--tp-font-heading, inherit); letter-spacing: -0.01em; } /* Notification bell and menu as round buttons */ #UserProfile > div ~ div > span[role="button"] { justify-content: center; width: 3.6rem; height: 3.6rem; min-height: 0; padding: 0; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 50%; color: var(--tp-muted, var(--alt-text-color)); background-color: var(--tp-elevated, transparent); transition: border-color 0.35s, color 0.35s, transform 0.35s; } #UserProfile > div ~ div > span[role="button"]:hover, #UserProfile > div ~ div > span[role="button"][data-state="open"] { border-color: var(--tp-primary, var(--brand-orange)); color: var(--tp-primary, var(--brand-orange)); transform: translateY(-1px); } #UserProfile > div ~ div > span[role="button"] svg[data-slot="icon"]:first-child { width: 1.8rem; height: 1.8rem; color: inherit; } /* "Connected server" card, added by scripts/enhance.js */ .tp-server { display: block; } .nav-tile { /* Keep full height on short screens; the sidebar scrolls instead of squeezing. */ flex: none; flex-direction: column; flex-wrap: nowrap; gap: 2px; height: auto; min-height: 0; overflow: visible; line-height: normal; white-space: normal; background-color: transparent; } /* ---- Main menu entries */ .nav-tile:not(.right) .nav-item { display: block; text-align: left; } .nav-tile:not(.right) .nav-item a { display: flex; align-items: center; gap: 1.1rem; min-height: 3.6rem; padding: 0 1.1rem; border-radius: 8px; } .nav-tile:not(.right) .nav-item:hover a { background-color: rgba(255, 255, 255, 0.06); } .nav-tile:not(.right) .nav-item.active a { background-color: rgba(255, 255, 255, 0.1); } /* The active marker becomes a bar on the left edge instead of an underline. */ .nav-tile:not(.right) .nav-item:after { top: 0.8rem; bottom: 0.8rem; left: 0; width: 3px; height: auto; border-radius: 2px; background: linear-gradient(180deg, var(--tp-primary, var(--brand-orange)), var(--tp-primary-ii, var(--brand-red))); transform: scaleY(0); transform-origin: center; } .nav-tile:not(.right) .nav-item:hover:after, .nav-tile:not(.right) .nav-item:focus:after { transform: scaleY(0); } .nav-tile:not(.right) .nav-item.active:after { transform: scaleY(1); } /* Icons for the stock tabs; tabs added by other plugins get a neutral dot. */ .nav-tile:not(.right) .nav-item a:before { flex: none; width: 1.8rem; font: normal 1.5rem/1 FontAwesome; text-align: center; opacity: 0.7; content: "\f111"; } .nav-tile:not(.right) .nav-item.active a:before { color: var(--tp-primary-ii, var(--brand-orange)); opacity: 1; } .nav-tile:not(.right) .nav-item a[href$="/Dashboard"]:before { content: "\f0e4"; } .nav-tile:not(.right) .nav-item a[href$="/Main"]:before { content: "\f0a0"; } .nav-tile:not(.right) .nav-item a[href$="/Shares"]:before { content: "\f07c"; } .nav-tile:not(.right) .nav-item a[href$="/Users"]:before { content: "\f0c0"; } .nav-tile:not(.right) .nav-item a[href$="/Settings"]:before { content: "\f013"; } .nav-tile:not(.right) .nav-item a[href$="/Plugins"]:before { content: "\f12e"; } .nav-tile:not(.right) .nav-item a[href$="/Docker"]:before { content: "\f1b3"; } .nav-tile:not(.right) .nav-item a[href$="/VMs"]:before { content: "\f108"; } .nav-tile:not(.right) .nav-item a[href$="/Apps"]:before { content: "\f009"; } .nav-tile:not(.right) .nav-item a[href$="/Stats"]:before { content: "\f080"; } .nav-tile:not(.right) .nav-item a[href$="/Tools"]:before { content: "\f0ad"; } /* ---- Utility buttons (lock, search, terminal, log, ...): a "Quick actions" card of icon tiles at the bottom of the sidebar */ .nav-tile.right { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 0.6rem; margin-top: auto; padding: 1.2rem; border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 12px; background-color: rgba(255, 255, 255, 0.05); } .nav-tile.right:before { grid-column: 1 / -1; margin-bottom: 0.2rem; color: var(--tp-header-muted, #a7b0c0); font: 600 1rem var(--tp-font-heading, inherit); letter-spacing: 0.11em; text-transform: uppercase; opacity: 0.75; content: "Quick actions"; } /* Array usage indicator (Display Settings; the theme's widget when page enhancements are on): a full-width row above the tiles. Unraid places it absolutely, which laid it over the first row of buttons. */ #menu .nav-tile.right > :is(.usage-bar, .tp-usage) { grid-column: 1 / -1; width: auto; margin: 0 0 0.4rem; } /* Unraid leaves empty placeholders between the buttons; they would take up tiles. */ .nav-tile.right .nav-user:empty { display: none; } .nav-tile.right .nav-item, .nav-tile.right .nav-user { display: block; } .nav-tile.right .nav-item a, .nav-tile.right .nav-user a { display: grid; place-items: center; aspect-ratio: 1; padding: 0; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 10px; color: var(--tp-header-muted, #a7b0c0); font-size: 1.5rem; line-height: 1; background-color: rgba(255, 255, 255, 0.04); transition: transform 0.35s, color 0.35s, border-color 0.35s, background-color 0.35s, box-shadow 0.35s; } .nav-tile.right .nav-item a .system, .nav-tile.right .nav-user a .system { padding: 0; } .nav-tile.right .nav-item:after, .nav-tile.right .nav-user:after { display: none; } .nav-tile.right .nav-item:hover a, .nav-tile.right .nav-item a:focus-visible, .nav-tile.right .nav-user:hover a { border-color: transparent; color: #ffffff; background: var(--tp-gradient, var(--brand-orange)); box-shadow: 0 6px 14px -6px var(--tp-primary, transparent); transform: translateY(-2px); } /* Help switched on: the button stays lit while the page's help boxes are shown. */ .nav-tile.right .nav-item.HelpButton.active a { border-color: color-mix(in srgb, var(--tp-primary, #4f8bff) 45%, transparent); color: var(--tp-primary, #4f8bff); background-color: var(--tp-primary-soft, rgba(79, 139, 255, 0.18)); } /* A page without help boxes: the help button is dimmed and does nothing. */ .nav-tile.right .nav-item.HelpButton.tp-help-none a { opacity: 0.35; cursor: default; } .nav-tile.right .nav-item.HelpButton.tp-help-none:hover a { border-color: rgba(255, 255, 255, 0.06); color: var(--tp-header-muted, #a7b0c0); background: rgba(255, 255, 255, 0.04); box-shadow: none; transform: none; } /* Coloured state icons (the lock) turn white with the rest on hover. */ .nav-tile.right .nav-item:hover a b, .nav-tile.right .nav-item a:focus-visible b { color: inherit !important; } } } /* Share / disk settings: from a 1330px window Unraid moves "Read settings from / Write settings to" to the top right of the form, measured on the window. With the sidebar the content area is narrower and the controls lay over the fields, so they stay above the form until there is room. */ @media (min-width: 1330px) and (max-width: 1699px) { :root.tp-sidebar .clone-settings { position: static; width: auto; margin: 0 0 1.4rem; } } AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/styles/login.css <<'AURORA_THEME_EOF' /* Aurora Theme: login page. include/login-head.php writes this into Unraid's login page (files below /plugins are not served before login), with the two font placeholders below replaced by the fonts as data URLs. Unraid's own login styles come first in the page; these override them. Sizes are in px: the login page keeps the browser's 16px rem, unlike the rest of the web interface. */ @font-face { font-family: "Inter"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("@@INTER@@") format("woff2"); } @font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 300 700; font-display: swap; src: url("@@GROTESK@@") format("woff2"); } :root { --tp-bg: #07090e; --tp-card: rgba(18, 21, 29, 0.78); --tp-field: rgba(255, 255, 255, 0.04); --tp-text: #e7eaf0; --tp-muted: #8b93a5; --tp-border: rgba(255, 255, 255, 0.09); --tp-input-border: rgba(255, 255, 255, 0.14); --tp-primary: #4f8bff; --tp-primary-ii: #22d3ee; --tp-primary-soft: rgba(79, 139, 255, 0.18); --tp-gradient: linear-gradient(135deg, var(--tp-primary), var(--tp-primary-ii)); --tp-danger: #f87171; --tp-danger-soft: rgba(248, 113, 113, 0.12); --tp-success: #86efac; --tp-success-soft: rgba(34, 197, 94, 0.13); --tp-aurora-a: rgba(79, 139, 255, 0.35); --tp-aurora-b: rgba(34, 211, 238, 0.22); --tp-aurora-c: rgba(167, 139, 250, 0.22); --tp-user-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b93a5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='8' r='4'/%3E%3Cpath d='M4 21c0-4 3.6-7 8-7s8 3 8 7'/%3E%3C/svg%3E"); --tp-lock-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b93a5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='11' width='16' height='10' rx='2.5'/%3E%3Cpath d='M8 11V8a4 4 0 0 1 8 0v3'/%3E%3C/svg%3E"); color-scheme: dark; } :root.Theme--white, :root.Theme--azure { --tp-bg: #eef2f8; --tp-card: rgba(255, 255, 255, 0.82); --tp-field: #ffffff; --tp-text: #0f172a; --tp-muted: #64748b; --tp-border: rgba(15, 23, 42, 0.08); --tp-input-border: rgba(15, 23, 42, 0.14); --tp-primary: #2563eb; --tp-primary-ii: #06b6d4; --tp-primary-soft: rgba(37, 99, 235, 0.12); --tp-danger: #dc2626; --tp-danger-soft: rgba(220, 38, 38, 0.08); --tp-success: #15803d; --tp-success-soft: rgba(34, 197, 94, 0.12); --tp-aurora-a: rgba(37, 99, 235, 0.22); --tp-aurora-b: rgba(6, 182, 212, 0.18); --tp-aurora-c: rgba(139, 92, 246, 0.14); --tp-user-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='8' r='4'/%3E%3Cpath d='M4 21c0-4 3.6-7 8-7s8 3 8 7'/%3E%3C/svg%3E"); --tp-lock-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='11' width='16' height='10' rx='2.5'/%3E%3Cpath d='M8 11V8a4 4 0 0 1 8 0v3'/%3E%3C/svg%3E"); color-scheme: light; } /* ---- Page: an aurora glow behind a centred glass card */ html, body { min-height: 100%; } body { display: grid; place-items: center; min-height: 100vh; margin: 0; padding: 24px 16px 50px; box-sizing: border-box; color: var(--tp-text); font: 400 15px/1.5 "Inter", system-ui, sans-serif; background: radial-gradient(60vmax 45vmax at 12% 8%, var(--tp-aurora-a), transparent 60%), radial-gradient(55vmax 40vmax at 88% 18%, var(--tp-aurora-c), transparent 62%), radial-gradient(70vmax 50vmax at 60% 110%, var(--tp-aurora-b), transparent 60%), var(--tp-bg); background-attachment: fixed; } /* Theme credit, quietly at the bottom. */ body::after { position: fixed; right: 0; bottom: 16px; left: 0; color: var(--tp-muted); font: 500 12px "Inter", system-ui, sans-serif; letter-spacing: 0.02em; text-align: center; opacity: 0.7; content: "Aurora Theme"; pointer-events: none; } #login { position: relative; width: min(420px, 100%); margin: 0; overflow: hidden; border: 1px solid var(--tp-border); border-radius: 24px; background: var(--tp-card); box-shadow: 0 40px 80px -30px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.02) inset; backdrop-filter: blur(22px) saturate(1.3); -webkit-backdrop-filter: blur(22px) saturate(1.3); animation: tp-login-rise 0.6s cubic-bezier(0.16, 1, 0.3, 1); } /* A thin gradient line along the top edge of the card. */ #login::before { position: absolute; top: 0; right: 0; left: 0; height: 3px; background: var(--tp-gradient); content: ""; } #login.shadow { box-shadow: 0 40px 80px -30px rgba(0, 0, 0, 0.55); } /* ---- Logo: Unraid's wordmark, small, instead of the red angled banner */ #login .logo { height: auto; overflow: visible; border-radius: 0; } #login .angle::after { display: none; } #login .wordmark { display: flex; justify-content: center; padding: 32px 20px 0; } #login .wordmark svg { width: 96px; height: auto; opacity: 0.85; } #login .wordmark svg path { fill: var(--tp-text); } /* ---- Server: the case icon in a gradient tile above the name */ #login .content { display: flex; flex-direction: column; align-items: stretch; padding: 24px 32px 28px; } #login .case { order: -2; display: grid; float: none; place-items: center; width: 72px; height: 72px; margin: 0 auto 16px; border-radius: 20px; color: #ffffff; font-size: 44px; line-height: 1; background: var(--tp-gradient); box-shadow: 0 16px 32px -16px var(--tp-primary); } #login .case:empty::before { font: 600 30px/1 "Space Grotesk", "Inter", sans-serif; content: "U"; } #login .case img { max-width: 48px; max-height: 48px; filter: brightness(0) invert(1); } #login h1 { order: -1; margin: 0; font: 600 26px/1.2 "Space Grotesk", "Inter", sans-serif; letter-spacing: -0.02em; text-align: center; } #login h2 { order: -1; margin: 4px 0 24px; color: var(--tp-muted); font: 400 14px/1.4 "Inter", sans-serif; text-align: center; } /* ---- Form */ #login .form { width: 100%; } #login .form form p { margin: 0; } #login [type=text], #login [type=password], #login [type=email] { display: block; width: 100%; height: 50px; margin: 0 0 12px; padding: 0 16px 0 46px; box-sizing: border-box; border: 1px solid var(--tp-input-border); border-radius: 14px; color: var(--tp-text); font: 400 15px "Inter", sans-serif; background: var(--tp-field) no-repeat 16px center / 18px 18px; transition: border-color 0.25s, box-shadow 0.25s, background-color 0.25s; } #login [type=text] { background-image: var(--tp-user-icon); } #login [type=password] { background-image: var(--tp-lock-icon); } #login [type=text]::placeholder, #login [type=password]::placeholder { color: var(--tp-muted); opacity: 1; } #login [type=text]:focus, #login [type=password]:focus, #login [type=email]:focus { border-color: var(--tp-primary); outline: none; box-shadow: 0 0 0 4px var(--tp-primary-soft); } #login .button, #login .button--small, #login .form button { display: block; width: 100%; height: 50px; margin: 8px 0 0; padding: 0 20px; border: 0; border-radius: 14px; color: #ffffff; font: 600 15px "Space Grotesk", "Inter", sans-serif; letter-spacing: 0.01em; text-transform: none; background: var(--tp-gradient); background-size: auto; box-shadow: 0 14px 28px -16px var(--tp-primary); cursor: pointer; transition: box-shadow 0.3s, filter 0.3s; } #login .button:hover, #login .form button:hover { color: #ffffff; background: var(--tp-gradient); filter: brightness(1.08); box-shadow: 0 18px 32px -16px var(--tp-primary); } #login .button:focus-visible, #login .form button:focus-visible { outline: 2px solid var(--tp-primary); outline-offset: 3px; } /* Messages: real errors in red, "Successfully logged out" in green (marked by login-head.php). */ #login .error { display: flex; align-items: center; gap: 8px; margin: 16px 0 0; padding: 10px 14px; border-radius: 12px; color: var(--tp-danger); font-size: 13.5px; background-color: var(--tp-danger-soft); } #login .error:empty { display: none; } #login .error.tp-ok { color: var(--tp-success); background-color: var(--tp-success-soft); } /* ---- Password recovery: a quiet link under the form */ #login .content > a { align-self: center; margin-top: 20px; color: var(--tp-muted); font: 500 13px "Inter", sans-serif; letter-spacing: 0; text-transform: none; text-decoration: none; transition: color 0.25s; } #login .content > a:hover, #login .content > a:focus-visible { color: var(--tp-primary); } @keyframes tp-login-rise { from { opacity: 0; transform: translateY(14px) scale(0.98); } } @media (prefers-reduced-motion: reduce) { #login { animation: none; } } /* ---- Phones: the card fills the screen width */ @media (max-width: 500px) { body { padding: 12px 12px 50px; background: radial-gradient(90vmax 60vmax at 0% 0%, var(--tp-aurora-a), transparent 60%), radial-gradient(90vmax 60vmax at 100% 100%, var(--tp-aurora-b), transparent 60%), var(--tp-bg); } #login { width: 100%; border-radius: 22px; } #login .content { padding: 20px 20px 24px; } #login [type=text], #login [type=password] { font-size: 16px; } } AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/styles/settings.css <<'AURORA_THEME_EOF' /* Settings page of the Aurora Theme: update card and the GitHub issue form. Falls back to Unraid's own colours when the theme's colours are switched off. */ .tft-update-card { display: flex; align-items: center; gap: 1.6rem; max-width: 90rem; margin: 1.4rem 0 2.4rem; padding: 1.8rem 2rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 18px; background-color: var(--tp-elevated, var(--mild-background-color)); } .tft-update-icon { display: grid; flex: none; place-items: center; width: 4.8rem; height: 4.8rem; border-radius: 50%; color: var(--tp-muted, var(--alt-text-color)); font-size: 2.4rem; background-color: var(--tp-subtle, var(--background-color)); } .tft-update-text { display: flex; flex: 1; flex-direction: column; gap: 0.3rem; min-width: 0; } .tft-update-text strong { font: 600 1.7rem/1.3 var(--tp-font-heading, inherit); letter-spacing: -0.01em; } .tft-update-text span { color: var(--tp-muted, var(--alt-text-color)); font-size: 1.3rem; } .tft-update-text code { overflow: hidden; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.1rem; text-overflow: ellipsis; white-space: nowrap; opacity: 0.8; } /* An update waiting: accent border with a glow, gradient icon, gentle pulse */ .tft-update-card.tft-state-available { border-color: var(--tp-primary, var(--brand-orange)); background: linear-gradient(120deg, var(--tp-primary-soft, rgba(255, 140, 47, 0.12)), transparent 60%), var(--tp-elevated, var(--mild-background-color)); box-shadow: 0 0 0 4px var(--tp-primary-soft, transparent), 0 18px 40px -24px var(--tp-primary, transparent); } .tft-update-card.tft-state-available .tft-update-icon { color: #ffffff; background: var(--tp-gradient, var(--brand-orange)); box-shadow: 0 8px 20px -8px var(--tp-primary, transparent); animation: tft-update-pulse 2.6s ease-in-out infinite; } .tft-update-card.tft-state-current .tft-update-icon { color: var(--tp-success-text, var(--green-500)); background-color: var(--tp-success-bg, var(--background-color)); } .tft-update-card.tft-state-missing .tft-update-icon, .tft-update-card.tft-state-invalid .tft-update-icon { color: var(--tp-warn-text, var(--orange-300)); background-color: var(--tp-warn-bg, var(--background-color)); } @keyframes tft-update-pulse { 0%, 100% { box-shadow: 0 8px 20px -8px var(--tp-primary, #ff8c2f), 0 0 0 0 var(--tp-primary-soft, rgba(255, 140, 47, 0.3)); } 50% { box-shadow: 0 8px 20px -8px var(--tp-primary, #ff8c2f), 0 0 0 8px transparent; } } /* Buttons are styled here in full, so Unraid's global button look does not leak in. */ button.tft-button { display: inline-flex; flex: none; align-items: center; gap: 0.8rem; min-width: 0; margin: 0; padding: 1rem 2rem; border: 1px solid var(--tp-input-border, var(--border-color)); border-radius: 999px; color: var(--text-color); font: 600 1.3rem var(--tp-font-heading, inherit); letter-spacing: 0; text-transform: none; white-space: nowrap; background: var(--tp-elevated, transparent); transition: transform 0.35s, box-shadow 0.35s, filter 0.35s, opacity 0.35s; } button.tft-button.tft-button-primary { border-color: transparent; color: var(--tp-primary-text, #ffffff); background: var(--tp-gradient, var(--brand-orange)); box-shadow: 0 10px 22px -12px var(--tp-primary, transparent); } button.tft-button:hover:not(:disabled) { border-color: transparent; color: var(--tp-primary-text, #ffffff); background: var(--tp-gradient, var(--brand-orange)); filter: brightness(1.08); transform: translateY(-1px); } button.tft-button:disabled { opacity: 0.45; cursor: default; } /* ---- GitHub issue form */ .tft-issue { display: flex; flex-direction: column; gap: 1.4rem; max-width: 90rem; margin: 1.4rem 0 2.4rem; padding: 2rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 18px; background-color: var(--tp-elevated, var(--mild-background-color)); } .tft-issue-intro { margin: 0; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.3rem; line-height: 1.5; text-align: left; } .tft-segment { display: inline-flex; flex-wrap: wrap; align-self: flex-start; gap: 0.2rem; padding: 0.3rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 999px; background-color: var(--tp-subtle, var(--background-color)); } button.tft-segment-option { min-width: 0; margin: 0; padding: 0.6rem 1.4rem; border: 0; border-radius: 999px; color: var(--tp-muted, var(--alt-text-color)); font: 500 1.25rem var(--tp-font-heading, inherit); letter-spacing: 0; text-transform: none; background: transparent; box-shadow: none; transform: none; } button.tft-segment-option:hover { color: var(--text-color); background: transparent; transform: none; } button.tft-segment-option[aria-checked="true"] { color: var(--tp-primary, var(--brand-orange)); font-weight: 600; background: var(--tp-elevated, var(--mild-background-color)); box-shadow: var(--tp-shadow-sm, none); } .tft-field { display: flex; flex-direction: column; gap: 0.6rem; } .tft-field > span { color: var(--tp-muted, var(--alt-text-color)); font: 600 1.1rem var(--tp-font-heading, inherit); letter-spacing: 0.08em; text-transform: uppercase; } .tft-field input, .tft-field textarea { width: 100%; box-sizing: border-box; padding: 1rem 1.2rem; font-size: 1.35rem; resize: vertical; } .tft-check { display: flex; align-items: center; gap: 0.8rem; color: var(--tp-muted, var(--alt-text-color)); font-size: 1.2rem; } .tft-issue-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1.6rem; } .tft-link { color: var(--tp-primary, var(--link-text-color)); font: 600 1.25rem var(--tp-font-heading, inherit); } @media (prefers-reduced-motion: reduce) { .tft-update-card .tft-update-icon { animation: none !important; } } @media (max-width: 640px) { .tft-update-card { flex-wrap: wrap; } /* The four issue kinds no longer fit on one line; two even rows instead of a pill that wraps unevenly. */ .tft-segment { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-self: stretch; border-radius: 1.4rem; } button.tft-segment-option { border-radius: 1rem; } } /* ---- Changing the update location */ button.tft-link-button { align-self: flex-start; min-width: 0; margin: 0.4rem 0 0; padding: 0; border: 0; color: var(--tp-primary, var(--link-text-color)); font: 600 1.2rem var(--tp-font-heading, inherit); letter-spacing: 0; text-transform: none; background: none; box-shadow: none; transform: none; } button.tft-link-button:hover { border: 0; color: var(--tp-primary-ii, var(--link-text-color)); background: none; transform: none; } .tft-source-form { display: flex; flex-direction: column; gap: 1rem; max-width: 90rem; margin: -1.2rem 0 2.4rem; padding: 1.8rem 2rem; border: 1px solid var(--tp-border, var(--border-color)); border-radius: 18px; background-color: var(--tp-elevated, var(--mild-background-color)); } .tft-source-form[hidden] { display: none; } .tft-source-problem { min-height: 1.6rem; margin: 0; color: var(--tp-danger, var(--red-600)); font-size: 1.2rem; text-align: left; } /* "Automatic updates" checkbox in the settings form */ .tft-check.tft-check-inline { display: inline-flex; color: var(--tp-text, var(--text-color)); font-size: 1.3rem; cursor: pointer; } .tft-check input[type="checkbox"] { width: 1.8rem; height: 1.8rem; margin: 0; accent-color: var(--tp-primary, var(--brand-orange)); cursor: pointer; } /* Last automatic check, under the update card's text */ .tft-update-text span.tft-auto-status { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.4rem; font-size: 1.15rem; } .tft-update-text span.tft-auto-status.tft-auto-failed { color: var(--tp-danger, var(--red-600)); } /* Update card buttons: "Check for updates" and the update itself, side by side. */ .tft-update-actions { display: flex; flex: none; flex-wrap: wrap; justify-content: flex-end; gap: 0.8rem; } .tft-update-actions button.tft-button[hidden] { display: none; } @media (max-width: 640px) { .tft-update-actions { flex: 1 1 100%; justify-content: stretch; } .tft-update-actions button.tft-button { flex: 1 1 0; } } /* ---- Accent colour: picker, code and "Default" in one row */ .tft-accent { display: flex; align-items: center; gap: 0.8rem; width: 100%; max-width: 40rem; } .tft-accent input[type="color"] { flex: none; width: 4rem; height: 3.4rem; margin: 0; padding: 0.3rem; border: 1px solid var(--tp-input-border, var(--border-color)); border-radius: 8px; background: transparent; cursor: pointer; } .tft-accent input[type="text"] { flex: 1 1 auto; width: auto !important; min-width: 0; margin: 0; font-variant-numeric: tabular-nums; } .tft-accent input[type="button"] { flex: none; margin: 0; } /* ---- Automatic update schedule: frequency, weekday and time in one row */ .tft-schedule { display: flex; gap: 0.8rem; width: 100%; max-width: 40rem; } /* Each select would otherwise take the full field width and the three would stack. */ .tft-schedule select { flex: 1 1 0; width: auto !important; min-width: 0; margin: 0; } .tft-schedule select[hidden] { display: none; } .tft-schedule select#tft-when { flex-grow: 1.4; } /* "Dev build" next to the installed version while a dev build runs. */ .tft-update-text strong .tft-dev-chip { display: inline-block; margin-left: 0.6rem; padding: 0.1rem 0.8rem; border-radius: 999px; background: var(--tp-gradient, var(--tp-primary)); color: #fff; font: 600 1.1rem/1.6 var(--tp-font-body, inherit); letter-spacing: 0.02em; vertical-align: middle; } AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/styles/stats.css <<'AURORA_THEME_EOF' /* Stats page. Chart colours: series 1 follows the theme's primary colour, series 2 is an amber that stays distinguishable from it for colour-blind readers on both the light and dark surface. */ #tft-stats { --tfs-series-1: var(--tp-primary, #2563eb); --tfs-series-2: #d97706; --tfs-border: var(--tp-border, var(--table-border-color)); --tfs-muted: var(--tp-muted, var(--alt-text-color)); --tfs-surface: var(--tp-elevated, var(--table-background-color)); } .tfs-note { margin: 0 0 2rem; color: var(--tfs-muted); font-size: 1.2rem; text-align: left; } .tfs-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.4rem; } .tfs-card { min-width: 0; padding: 1.7rem 2rem 1.2rem; border: 1px solid var(--tfs-border); border-radius: 18px; background-color: var(--tfs-surface); box-shadow: var(--tp-shadow-sm, none); } .tfs-card-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 0.7rem; } .tfs-card-title { margin: 0; font: 600 1.4rem var(--tp-font-heading, inherit); letter-spacing: -0.01em; } .tfs-card-title small, .tfs-legend { color: var(--tfs-muted); font: 400 1.1rem var(--tp-font-body, inherit); letter-spacing: 0; } .tfs-legend { display: flex; flex-wrap: wrap; gap: 1.2rem; } .tfs-legend span { display: inline-flex; align-items: center; gap: 0.6rem; } .tfs-swatch { flex: none; width: 8px; height: 8px; border-radius: 2px; background-color: var(--tfs-series); } .tfs-chart { position: relative; margin: 0.8rem 0 0.6rem; } .tfs-chart svg { display: block; width: 100%; height: 190px; overflow: visible; } .tfs-gridline { stroke: var(--tfs-border); stroke-width: 1; } .tfs-axis { fill: var(--tfs-muted); font: 10px var(--tp-font-body, sans-serif); } .tfs-line { fill: none; stroke: var(--tfs-series); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; } .tfs-area { fill: var(--tfs-series); opacity: 0.1; } .tfs-hair { stroke: var(--tfs-muted); stroke-width: 1; opacity: 0.5; } .tfs-dot { fill: var(--tfs-series); stroke: var(--tfs-surface); stroke-width: 2; } .tfs-hover[hidden] { display: none; } .tfs-empty { position: absolute; inset: 0 0 2rem; display: grid; place-items: center; color: var(--tfs-muted); font-size: 1.2rem; text-align: center; } .tfs-tip { position: absolute; top: 4px; z-index: 2; padding: 0.7rem 1rem; border: 1px solid var(--tfs-border); border-radius: 8px; font-size: 1.1rem; white-space: nowrap; pointer-events: none; background-color: var(--tfs-surface); box-shadow: var(--tp-shadow-lg, 0 8px 24px rgba(0, 0, 0, 0.3)); } .tfs-tip-time { margin-bottom: 0.3rem; color: var(--tfs-muted); } .tfs-tip-row { display: flex; align-items: center; gap: 0.6rem; } .tfs-tip-row strong { margin-left: auto; padding-left: 1.2rem; font: 600 1.1rem var(--tp-font-heading, inherit); font-variant-numeric: tabular-nums; } .tfs-section { margin: 2.7rem 0 1rem; font: 600 1.4rem var(--tp-font-heading, inherit); letter-spacing: -0.01em; } .tfs-disk { display: grid; grid-template-columns: 9rem minmax(4rem, 1fr) auto; align-items: center; gap: 1.4rem; min-height: 3.7rem; border-bottom: 1px solid var(--tfs-border); } .tfs-disk:last-child { border-bottom: 0; } .tfs-disk-name { overflow: hidden; font: 600 1.2rem var(--tp-font-heading, inherit); text-overflow: ellipsis; white-space: nowrap; } .tfs-track { height: 6px; overflow: hidden; border-radius: 999px; background-color: var(--tfs-border); } .tfs-fill { height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--tp-primary, #2563eb), var(--tp-primary-ii, #06b6d4)); } .tfs-disk-value { color: var(--tfs-muted); font: 1rem var(--tp-font-body, inherit); font-variant-numeric: tabular-nums; white-space: nowrap; } @media (max-width: 900px) { .tfs-grid { grid-template-columns: minmax(0, 1fr); } } AURORA_THEME_EOF cat > /usr/local/emhttp/plugins/Aurora-Theme/styles/theme.css <<'AURORA_THEME_EOF' /* * Fonts, bundled with the plugin (no external requests): Inter for text, Space Grotesk for * headings, both under the SIL Open Font License (see fonts/*-LICENSE.txt). * Unraid's own families, clear-sans and bitstream, are declared again here with Inter's files, * so every element that asks for them by name renders in Inter as well. Icon fonts are untouched. */ @font-face { font-family: "Inter"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-wght-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: "Inter"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-ext-wght-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; } @font-face { font-family: "Inter"; font-style: italic; font-weight: 100 900; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-wght-italic.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 300 700; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/space-grotesk-latin-wght-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 300 700; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/space-grotesk-latin-ext-wght-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; } @font-face { font-family: clear-sans; font-style: normal; font-weight: normal; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-wght-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: clear-sans; font-style: normal; font-weight: normal; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-ext-wght-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; } @font-face { font-family: clear-sans; font-style: italic; font-weight: normal; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-wght-italic.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: clear-sans; font-style: normal; font-weight: bold; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-wght-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: clear-sans; font-style: normal; font-weight: bold; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-ext-wght-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; } @font-face { font-family: clear-sans; font-style: italic; font-weight: bold; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-wght-italic.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: bitstream; font-style: normal; font-weight: normal; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-wght-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: bitstream; font-style: normal; font-weight: normal; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-ext-wght-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; } @font-face { font-family: bitstream; font-style: italic; font-weight: normal; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-wght-italic.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: bitstream; font-style: normal; font-weight: bold; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-wght-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: bitstream; font-style: normal; font-weight: bold; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-ext-wght-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; } @font-face { font-family: bitstream; font-style: italic; font-weight: bold; font-display: swap; src: url("/plugins/Aurora-Theme/fonts/inter-latin-wght-italic.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* * Tapeory look for the Unraid webGUI (written against 7.3.2). * Works on top of the built-in Black (dark) and White (light) themes: it overrides the * colour variables those themes set, then restyles fonts, navigation, title bars, tables, * buttons and inputs. Other themes are left untouched. */ /* ========================================================================== Tokens — Tapeory palette ========================================================================== */ :root.Theme--white { --tp-bg: #f5f6f8; --tp-elevated: #ffffff; --tp-subtle: #eef0f4; --tp-text: #14181f; --tp-muted: #62697a; --tp-border: #e4e7ec; --tp-input-border: #d7dbe3; --tp-primary: #2563eb; --tp-primary-ii: #06b6d4; --tp-primary-soft: rgba(37, 99, 235, 0.1); --tp-tab-active: #ffffff; --tp-primary-text: #ffffff; --tp-header-bg: var(--tp-custom-header-bg, #12161f); --tp-header-text: var(--tp-custom-header-text, #ffffff); --tp-header-muted: var(--tp-custom-header-muted, #a7b0c0); --tp-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06); --tp-shadow-lg: 0 24px 56px -22px rgba(15, 23, 42, 0.3); /* The same palette as "H S% L%" triplets, the form Unraid's newer components expect. */ --tp-success-bg: #dcfce7; --tp-success-text: #166534; --tp-danger: #dc2626; --tp-danger-soft: rgba(220, 38, 38, 0.08); --tp-warn-bg: #fef3c7; --tp-warn-text: #b45309; --tp-neutral-bg: #e5e7eb; --tp-neutral-text: #374151; --tp-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2362697a' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); --tp-hsl-surface: 0 0% 100%; --tp-hsl-text: 218 22% 10%; --tp-hsl-subtle: 220 21% 95%; --tp-hsl-muted: 223 11% 43%; --tp-hsl-border: 218 17% 91%; --tp-hsl-input: 220 18% 87%; --tp-hsl-primary: 221 83% 53%; --tp-hsl-primary-text: 0 0% 100%; } :root.Theme--black { --tp-bg: #0a0c11; --tp-elevated: #12151d; --tp-subtle: #161a23; --tp-text: #e7eaf0; --tp-muted: #8b93a5; --tp-border: #232833; --tp-input-border: #2c323f; --tp-primary: #4f8bff; --tp-primary-ii: #22d3ee; --tp-primary-soft: rgba(79, 139, 255, 0.16); --tp-tab-active: #232a39; --tp-primary-text: #06101f; --tp-header-bg: var(--tp-custom-header-bg, #05070a); --tp-header-text: var(--tp-custom-header-text, #f3f4f6); --tp-header-muted: var(--tp-custom-header-muted, #93a0b8); --tp-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.45); --tp-shadow-lg: 0 32px 70px -26px rgba(0, 0, 0, 0.72); --tp-success-bg: #12301f; --tp-success-text: #86efac; --tp-danger: #f87171; --tp-danger-soft: rgba(248, 113, 113, 0.12); --tp-warn-bg: #3a2a0c; --tp-warn-text: #fbbf24; --tp-neutral-bg: #232833; --tp-neutral-text: #c3c9d6; --tp-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b93a5' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); --tp-hsl-surface: 224 23% 9%; --tp-hsl-text: 220 23% 92%; --tp-hsl-subtle: 222 23% 11%; --tp-hsl-muted: 222 13% 60%; --tp-hsl-border: 221 19% 17%; --tp-hsl-input: 221 18% 21%; --tp-hsl-primary: 220 100% 65%; --tp-hsl-primary-text: 216 68% 7%; } :root:is(.Theme--black, .Theme--white) { --tp-font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif; --tp-font-heading: "Space Grotesk", var(--tp-font-body); --tp-gradient: linear-gradient(135deg, var(--tp-primary), var(--tp-primary-ii)); --tp-r-sm: 8px; --tp-r-md: 12px; --tp-r-full: 999px; --tp-ease: cubic-bezier(0.16, 1, 0.3, 1); /* ---- Unraid's own variables (see black.css / white.css), pointed at the tokens above */ --font-sans: var(--tp-font-body); --default-font-family: var(--tp-font-body); --text-color: var(--tp-text); --alt-text-color: var(--tp-muted); --disabled-text-color: var(--tp-muted); --inverse-text-color: var(--tp-bg); --link-text-color: var(--tp-primary); --background-color: var(--tp-bg); --opac-background-color: color-mix(in srgb, var(--tp-bg) 95%, transparent); --mild-background-color: var(--tp-subtle); --usage-bar-background-color: var(--tp-border); --usage-bar-used-background-color: var(--tp-muted); --usage-disk-background-color: var(--tp-border); --inverse-background-color: var(--tp-text); --radio-background-color: var(--tp-border); --dashboard-background-color: var(--tp-elevated); --dashboard-icon-color: var(--tp-subtle); --border-color: var(--tp-border); --disabled-border-color: var(--tp-border); --input-border-color: var(--tp-input-border); --disabled-input-border-color: var(--tp-border); --textarea-border-color: var(--tp-input-border); --table-border-color: var(--tp-border); --table-alt-border-color: var(--tp-border); --table-background-color: var(--tp-elevated); --table-header-background-color: var(--tp-subtle); --hover-table-row-background-color: var(--tp-primary-soft); --footer-text: var(--tp-muted); --footer-background-color: var(--tp-bg); --small-shadow: var(--tp-shadow-sm); --hr-color: var(--tp-border); --checkbox-color: var(--tp-input-border); --checkbox-hover-color: var(--tp-muted); --brand-orange: var(--tp-primary); --brand-red: var(--tp-primary); --header-text-color: var(--tp-header-muted); --header-background-color: var(--tp-header-bg); --header-text-primary: var(--tp-header-text); --header-text-secondary: var(--tp-header-muted); --title-header-background-color: transparent; --scrollbar-color: var(--tp-input-border); --scrollbar-hover-color: var(--tp-muted); --button-text-color: var(--tp-text); --button-border: 1px solid var(--tp-input-border); --button-background: var(--tp-elevated); --button-background-size: auto; --hover-button-text-color: var(--tp-primary); --hover-button-background: var(--tp-elevated); --input-bg-color: var(--tp-elevated); --focus-input-bg-color: var(--tp-elevated); --shade-bg-color: var(--tp-subtle); --dynamix-tablesorter-thead-th-text-color: var(--tp-muted); --dynamix-tablesorter-thead-th-bg-color: var(--tp-subtle); --dynamix-tablesorter-thead-row-border-color: var(--tp-border); --dynamix-tablesorter-tbody-row-bg-color: var(--tp-elevated); --dynamix-tablesorter-tbody-row-alt-bg-color: var(--tp-elevated); --dynamix-tablesorter-tbody-row-border-color: var(--tp-border); --dynamix-jquery-ui-button-text-color: var(--tp-primary); --dynamix-jquery-ui-button-background-start: var(--tp-primary); --dynamix-jquery-ui-button-background-end: var(--tp-primary-ii); --dynamix-jquery-ui-button-hover-color: var(--tp-primary-text); --dynamix-jquery-ui-border-color: var(--tp-border); --dynamix-jquery-ui-header-background: transparent; --dynamix-jquery-ui-header-color: var(--tp-text); --dynamix-jquery-ui-content-background: var(--tp-elevated); --dynamix-jquery-ui-content-color: var(--tp-text); --dynamix-jquery-ui-active-background: var(--tp-primary-soft); --dynamix-jquery-ui-dropcontainer-background: var(--tp-elevated); --dynamix-jquery-ui-disabled-color: var(--tp-muted); --dynamix-jquery-ui-disabled-border-color: var(--tp-border); --dynamix-jquery-ui-disabled-background: var(--tp-subtle); --dynamix-jquery-ui-text-color: var(--tp-text); --dynamix-select-box-shadow: 0 0 0 3px var(--tp-primary-soft); --dynamix-tooltipster-sidetip-bg-from: var(--tp-elevated); --dynamix-tooltipster-sidetip-bg-to: var(--tp-elevated); --dynamix-tooltipster-sidetip-content-text-color: var(--tp-text); --dynamix-tooltipster-sidetip-arrow-bg-color: var(--tp-elevated); --dynamix-tooltipster-sidetip-arrow-border-color: var(--tp-border); --dynamix-tooltipster-sidetip-box-border-color: var(--tp-border); --dynamix-filetree-bg-color: var(--tp-elevated); --dynamix-sweet-alert-text-color: var(--tp-text); --dynamix-sweet-alert-icon-bg-color: var(--tp-elevated); /* ---- Community Applications (Apps tab) variables */ --template-background: var(--tp-elevated); --template-hover-background: var(--tp-elevated); --template-favourite: var(--tp-subtle); --a-color: var(--tp-primary); --sidebar-background: var(--tp-bg); --sidebar-text: var(--tp-text); --support-popup-text: var(--tp-text); --support-popup-background: var(--tp-subtle); --unraid-66-color: var(--tp-primary); --ca-shadow-color: transparent; --ca-dark-purple-color: var(--tp-primary); --chart-grid: var(--tp-border); /* ====================================================================== Base ====================================================================== */ font-family: var(--tp-font-body); /* Unraid's newer components (notification panel, header menu, modals) set their own colour variables on every .unapi element, so they are overridden there. */ /* ":not(#tp-none)" only raises the weight of this rule: Unraid sets these variables again with matching weight in a stylesheet that loads after this one. */ &.dark, .unapi:not(#tp-none) { --background: var(--tp-hsl-surface); --foreground: var(--tp-hsl-text); --muted: var(--tp-hsl-subtle); --muted-foreground: var(--tp-hsl-muted); --popover: var(--tp-hsl-surface); --popover-foreground: var(--tp-hsl-text); --card: var(--tp-hsl-surface); --card-foreground: var(--tp-hsl-text); --border: var(--tp-hsl-border); --input: var(--tp-hsl-input); --primary: var(--tp-hsl-primary); --primary-foreground: var(--tp-hsl-primary-text); /* Secondary buttons sit on cards that use the subtle surface, so they take the border tone. */ --secondary: var(--tp-hsl-border); --secondary-foreground: var(--tp-hsl-text); --accent: var(--tp-hsl-border); --accent-foreground: var(--tp-hsl-text); --ring: var(--tp-hsl-primary); --destructive: 0 72% 51%; --destructive-foreground: 0 0% 100%; --radius: 0.75rem; /* Their second colour system (surfaces, borders and text of cards, menus and dialogs) */ --ui-bg: var(--tp-elevated); --ui-bg-muted: var(--tp-subtle); --ui-bg-elevated: var(--tp-subtle); --ui-bg-accented: var(--tp-border); --ui-border: var(--tp-border); --ui-border-muted: var(--tp-border); --ui-border-accented: var(--tp-input-border); --ui-text: var(--tp-text); --ui-text-toned: var(--tp-text); --ui-text-muted: var(--tp-muted); --ui-text-dimmed: var(--tp-muted); --ui-text-highlighted: var(--tp-text); --ui-primary: var(--tp-primary); --ui-radius: 0.75rem; /* Unraid's orange becomes the theme accent; its red stays a clear red for errors. */ --color-orange: var(--tp-primary); --color-orange-dark: var(--tp-primary); --color-unraid-red: #dc2626; /* Accent and neutral colour scales: blue and slate instead of orange / green and grey */ --color-primary-50: #eff6ff; --color-primary-100: #dbeafe; --color-primary-200: #bfdbfe; --color-primary-300: #93c5fd; --color-primary-400: #60a5fa; --color-primary-500: #3b82f6; --color-primary-600: #2563eb; --color-primary-700: #1d4ed8; --color-primary-800: #1e40af; --color-primary-900: #1e3a8a; --color-primary-950: #172554; --ui-color-primary-50: #eff6ff; --ui-color-primary-100: #dbeafe; --ui-color-primary-200: #bfdbfe; --ui-color-primary-300: #93c5fd; --ui-color-primary-400: #60a5fa; --ui-color-primary-500: #3b82f6; --ui-color-primary-600: #2563eb; --ui-color-primary-700: #1d4ed8; --ui-color-primary-800: #1e40af; --ui-color-primary-900: #1e3a8a; --ui-color-primary-950: #172554; --ui-color-neutral-50: #f6f7f9; --ui-color-neutral-100: #eef0f4; --ui-color-neutral-200: #e4e7ec; --ui-color-neutral-300: #d7dbe3; --ui-color-neutral-400: #a7b0c0; --ui-color-neutral-500: #8b93a5; --ui-color-neutral-600: #62697a; --ui-color-neutral-700: #2c323f; --ui-color-neutral-800: #1b202a; --ui-color-neutral-900: #12151d; --ui-color-neutral-950: #0a0c11; } /* Unraid's brand buttons draw a red-to-orange gradient border or fill; here it is the theme gradient. */ .unapi .from-unraid-red { --tw-gradient-from: var(--tp-primary); } .unapi .to-orange { --tw-gradient-to: var(--tp-primary-ii); } /* Unraid's newer components bring their own system-font stack. */ .unapi, .unapi :where(button, input, select, textarea) { font-family: var(--tp-font-body); } .unapi :is(h1, h2, h3) { font-family: var(--tp-font-heading); } /* Not matched inside the Docker and VM lists: there Chrome would compute a selection style for every cell each time FolderView rebuilds a row, which doubled its restyle work. The lists still get the colour, by highlight inheritance, in browsers that have it. */ &::selection, & :not(:is(#docker_list, #kvm_list) *)::selection { color: var(--tp-primary-text); background-color: var(--tp-primary); } h1, h2, h3, h4 { font-family: var(--tp-font-heading); letter-spacing: -0.01em; } /* ====================================================================== Header and navigation ====================================================================== */ #header { color: var(--tp-header-text); } #menu { border-bottom: 1px solid rgba(255, 255, 255, 0.08); } .nav-tile { font-family: var(--tp-font-heading); font-size: 1.35rem; letter-spacing: 0; } .nav-item a { font-weight: 500; text-transform: none; transition: color 0.35s var(--tp-ease); } .nav-user a { color: var(--tp-header-muted); } .nav-item:hover a, .nav-item.active a, .nav-user:hover a { color: var(--tp-header-text); } .nav-item.active a { font-weight: 600; } /* The underline grows in from the left, like Tapeory's top bar. */ .nav-item:after, .nav-user.show:after { left: 10px; width: calc(100% - 20px); border-radius: 2px; background: linear-gradient(90deg, var(--tp-primary), var(--tp-primary-ii)); transform: translateY(8px) scaleX(0); transform-origin: left; transition: transform 0.35s var(--tp-ease); } .nav-item:focus:after, .nav-item:hover:after, .nav-item.active:after, .nav-user.show:hover:after { background: linear-gradient(90deg, var(--tp-primary), var(--tp-primary-ii)); transform: translateY(8px) scaleX(1); } /* ====================================================================== Section title bars ====================================================================== */ div.title { padding: 8px 2px 10px; border-bottom: 1px solid var(--tp-border); font-family: var(--tp-font-heading); font-size: 1.6rem; font-weight: 600; letter-spacing: -0.01em; } div.title i.title, div.title i.fa, div.title i.icon { color: var(--tp-primary); } /* ====================================================================== Tables ====================================================================== */ .TableContainer, .TableContainer--no-min-width { border: 1px solid var(--tp-border); border-radius: var(--tp-r-md); background-color: var(--tp-elevated); box-shadow: var(--tp-shadow-sm); } .TableContainer table, .TableContainer--no-min-width table { margin: 0; } table.tablesorter, table.unraid { background-color: var(--tp-elevated); } table.tablesorter thead tr th, table.unraid thead tr:first-child > td { font-family: var(--tp-font-heading); font-weight: 600; letter-spacing: 0.05em; } /* A heading wider than its column ends in "…" instead of running into the next one: next to the sidebar FolderView3's narrower Docker columns had "Volume Mappings (App to Host)" over "Autostart". (Wrapping instead changed how FolderView3 sizes the columns.) */ #docker_containers > thead > tr > th { overflow: hidden; text-overflow: ellipsis; } table.unraid thead tr:first-child > td { color: var(--tp-muted); } table.tablesorter tbody tr, table.unraid tbody tr { border-bottom: 1px solid var(--tp-border); } table.tablesorter tbody tr:last-child, table.unraid tbody tr:last-child { border-bottom: 0; } table.tablesorter tbody tr:not(.tr_last):hover > td { background-color: var(--tp-primary-soft); } table.tablesorter tbody tr.tr_last { background-color: var(--tp-subtle); } /* Dashboard tiles */ table.dashboard tbody { box-shadow: var(--tp-shadow-sm); } table.dashboard tr:first-child > td { font-family: var(--tp-font-heading); font-weight: 600; } /* ====================================================================== Buttons ====================================================================== */ input[type="button"]:where(:not(.unapi *)), input[type="reset"]:where(:not(.unapi *)), input[type="submit"]:where(:not(.unapi *)), button:where(:not(.unapi *, [role="tab"], .ui-button, .swal-button)), a.button:where(:not(.unapi *)) { padding: 8px 16px; /* Colours set here as well, not only through Unraid's variables: some pages bring their own button styles (the Docker install output's log.htm), which would otherwise win. */ border: var(--button-border); border-radius: var(--tp-r-full); color: var(--button-text-color); background: var(--button-background); background-size: auto; font-family: var(--tp-font-heading); font-size: 1.2rem; font-weight: 600; letter-spacing: 0; text-transform: none; transition: transform 0.35s var(--tp-ease), box-shadow 0.35s var(--tp-ease), border-color 0.35s var(--tp-ease), color 0.35s var(--tp-ease); } input:hover[type="button"]:where(:not(.unapi *, [disabled])), input:hover[type="reset"]:where(:not(.unapi *, [disabled])), input:hover[type="submit"]:where(:not(.unapi *, [disabled])), button:hover:where(:not(.unapi *, [disabled], [role="tab"], .ui-button, .swal-button)), a.button:hover:where(:not(.unapi *, [disabled])) { border-color: var(--tp-primary); color: var(--hover-button-text-color); background: var(--hover-button-background); box-shadow: var(--tp-shadow-sm); transform: translateY(-1px); } input[type="button"][disabled]:where(:not(.unapi *)), input[type="reset"][disabled]:where(:not(.unapi *)), input[type="submit"][disabled]:where(:not(.unapi *)), button[disabled]:where(:not(.unapi *, [role="tab"], .ui-button)), a.button[disabled]:where(:not(.unapi *)) { border: 1px solid var(--tp-border); color: var(--tp-muted); background: var(--tp-subtle); opacity: 0.6; } /* Page tabs: a segmented control. The tabs sit in a rounded track; the active one is a raised pill with its icon in the accent colour. */ nav.tabs { margin-bottom: 0.4rem; } .tabs .tabs-container { display: inline-flex; width: auto; max-width: 100%; gap: 0.4rem; padding: 0.5rem; overflow-x: auto; border: 1px solid var(--tp-border); border-radius: 16px; background-color: var(--tp-subtle); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12); scrollbar-width: thin; } .tabs button[role="tab"] { position: relative; gap: 0.8rem; min-height: 4rem; padding: 0.9rem 1.8rem; border: 1px solid transparent; border-radius: 11px; color: var(--tp-muted); font: 500 1.35rem/1 var(--tp-font-heading); letter-spacing: 0; white-space: nowrap; background: transparent; opacity: 1; transition: color 0.3s var(--tp-ease), background-color 0.3s var(--tp-ease), border-color 0.3s var(--tp-ease), box-shadow 0.3s var(--tp-ease); } /* The label's span ends up display:block from a .left rule, which dropped the gap between icon and text (Display Settings, Page view: Tabbed). */ .tabs button[role="tab"] > span { display: inline-flex; align-items: center; gap: 0.8rem !important; } .tabs button[role="tab"] i, .tabs button[role="tab"] i.title { margin: 0; color: inherit; font-size: 1.4rem; transition: color 0.3s var(--tp-ease); } .tabs button[role="tab"] img.icon { margin: 0; opacity: 0.75; filter: grayscale(0.4); transition: opacity 0.3s var(--tp-ease), filter 0.3s var(--tp-ease); } .tabs button[role="tab"]:hover { border-color: transparent; color: var(--tp-text); background-color: color-mix(in srgb, var(--tp-elevated) 55%, transparent); } .tabs button[role="tab"][aria-selected="true"] { border-color: var(--tp-border); color: var(--tp-text); font-weight: 600; background-color: var(--tp-tab-active, var(--tp-elevated)); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18), 0 6px 16px -10px rgba(0, 0, 0, 0.45); } .tabs button[role="tab"][aria-selected="true"] i, .tabs button[role="tab"][aria-selected="true"] i.title { color: var(--tp-primary); } .tabs button[role="tab"][aria-selected="true"] img.icon, .tabs button[role="tab"]:hover img.icon { opacity: 1; filter: none; } /* A short accent line under the active tab's label. */ .tabs button[role="tab"][aria-selected="true"]:after { position: absolute; right: 35%; bottom: 0.35rem; left: 35%; height: 2px; border-radius: 999px; background: var(--tp-gradient); content: ""; } .tabs button[role="tab"]:focus { border-color: transparent; opacity: 1; } .tabs button[role="tab"][aria-selected="true"]:focus { border-color: var(--tp-border); } .tabs button[role="tab"]:focus-visible { outline: 2px solid var(--tp-primary); outline-offset: 1px; } @media (max-width: 767px) { .tabs .tabs-container { display: flex; width: 100%; } .tabs button[role="tab"] { padding: 0.8rem 1.3rem; } } /* ====================================================================== Form fields — boxed with a focus ring instead of an underline ====================================================================== */ input[type="text"]:where(:not(.unapi *)), input[type="password"]:where(:not(.unapi *)), input[type="number"]:where(:not(.unapi *)), input[type="url"]:where(:not(.unapi *)), input[type="email"]:where(:not(.unapi *)), input[type="date"]:where(:not(.unapi *)), input[type="search"]:where(:not(.unapi *)), textarea:where(:not(.unapi *)), select:where(:not(.unapi *)) { box-sizing: border-box; border: 1px solid var(--tp-input-border); border-radius: 10px; color: var(--tp-text); font-family: var(--tp-font-body); background-color: var(--tp-elevated); transition: border-color 0.35s var(--tp-ease), box-shadow 0.35s var(--tp-ease); } /* Unraid colours placeholders like links; here they are quiet hints. */ input::placeholder, textarea::placeholder { color: var(--tp-muted); opacity: 0.75; } /* Single-line fields share the dropdowns' height so form rows line up. */ input[type="text"]:where(:not(.unapi *)), input[type="password"]:where(:not(.unapi *)), input[type="number"]:where(:not(.unapi *)), input[type="url"]:where(:not(.unapi *)), input[type="email"]:where(:not(.unapi *)), input[type="date"]:where(:not(.unapi *)), input[type="search"]:where(:not(.unapi *)) { min-height: 3.6rem; padding: 0.7rem 1.2rem; font-size: 1.3rem; } textarea:where(:not(.unapi *)) { padding: 0.9rem 1.2rem; font-size: 1.3rem; line-height: 1.5; } /* Docker start delay ("wait"): Unraid makes it 2.6rem wide for a bare field, which leaves no room for the number once it is boxed. Aurora's compact list sizes it itself; this is for the other views, such as FolderView3's (issue #21). */ #docker_containers input.wait { width: 4.6rem; margin: 0 0 0 0.6rem; padding: 0.3rem 0.6rem; text-align: center; } /* ====================================================================== Dropdowns (every