@@ -20,153 +20,49 @@ function ahtml(strings, ...values) {
2020 return strings . reduce ( ( acc , str , i ) => acc + str + ( values [ i ] ?? '' ) , '' ) ;
2121}
2222
23- // ─── Simple Icons Library ─────────────────────────────────
24- const SIMPLE_ICONS = [
25- { slug : 'docker' , label : 'Docker' } ,
26- { slug : 'kubernetes' , label : 'Kubernetes' } ,
27- { slug : 'amazoneks' , label : 'EKS' } ,
28- { slug : 'terraform' , label : 'Terraform' } ,
29- { slug : 'ansible' , label : 'Ansible' } ,
30- { slug : 'database' , label : 'Database' } ,
31- { slug : 'code' , label : 'Code' } ,
32- { slug : 'nginx' , label : 'Nginx' } ,
33- { slug : 'apache' , label : 'Apache' } ,
34- { slug : 'caddy' , label : 'Caddy' } ,
35- { slug : 'traefik' , label : 'Traefik' } ,
36- { slug : 'ubuntu' , label : 'Ubuntu' } ,
37- { slug : 'debian' , label : 'Debian' } ,
38- { slug : 'centos' , label : 'CentOS' } ,
39- { slug : 'fedora' , label : 'Fedora' } ,
40- { slug : 'alpinelinux' , label : 'Alpine' } ,
41- { slug : 'archlinux' , label : 'Arch' } ,
42- { slug : 'opensuse' , label : 'openSUSE' } ,
43- { slug : 'freebsd' , label : 'FreeBSD' } ,
44- { slug : 'linux' , label : 'Linux' } ,
45- { slug : 'windows' , label : 'Windows' } ,
46- { slug : 'python' , label : 'Python' } ,
47- { slug : 'javascript' , label : 'JavaScript' } ,
48- { slug : 'typescript' , label : 'TypeScript' } ,
49- { slug : 'java' , label : 'Java' } ,
50- { slug : 'go' , label : 'Go' } ,
51- { slug : 'rust' , label : 'Rust' } ,
52- { slug : 'php' , label : 'PHP' } ,
53- { slug : 'ruby' , label : 'Ruby' } ,
54- { slug : 'cplusplus' , label : 'C++' } ,
55- { slug : 'csharp' , label : 'C#' } ,
56- { slug : 'kotlin' , label : 'Kotlin' } ,
57- { slug : 'swift' , label : 'Swift' } ,
58- { slug : 'lua' , label : 'Lua' } ,
59- { slug : 'perl' , label : 'Perl' } ,
60- { slug : 'scala' , label : 'Scala' } ,
61- { slug : 'haskell' , label : 'Haskell' } ,
62- { slug : 'elixir' , label : 'Elixir' } ,
63- { slug : 'nodejs' , label : 'Node.js' } ,
64- { slug : 'nodedotjs' , label : 'Node.js' } ,
65- { slug : 'react' , label : 'React' } ,
66- { slug : 'vue-dot-js' , label : 'Vue.js' } ,
67- { slug : 'angular' , label : 'Angular' } ,
68- { slug : 'nextdotjs' , label : 'Next.js' } ,
69- { slug : 'svelte' , label : 'Svelte' } ,
70- { slug : 'express' , label : 'Express' } ,
71- { slug : 'flask' , label : 'Flask' } ,
72- { slug : 'django' , label : 'Django' } ,
73- { slug : 'laravel' , label : 'Laravel' } ,
74- { slug : 'spring' , label : 'Spring' } ,
75- { slug : 'rails' , label : 'Rails' } ,
76- { slug : 'fastapi' , label : 'FastAPI' } ,
77- { slug : 'mysql' , label : 'MySQL' } ,
78- { slug : 'postgresql' , label : 'PostgreSQL' } ,
79- { slug : 'mongodb' , label : 'MongoDB' } ,
80- { slug : 'redis' , label : 'Redis' } ,
81- { slug : 'sqlite' , label : 'SQLite' } ,
82- { slug : 'mariadb' , label : 'MariaDB' } ,
83- { slug : 'elasticsearch' , label : 'Elastic' } ,
84- { slug : 'cassandra' , label : 'Cassandra' } ,
85- { slug : 'neo4j' , label : 'Neo4j' } ,
86- { slug : 'influxdb' , label : 'InfluxDB' } ,
87- { slug : 'firebase' , label : 'Firebase' } ,
88- { slug : 'supabase' , label : 'Supabase' } ,
89- { slug : 'prisma' , label : 'Prisma' } ,
90- { slug : 'minecraft' , label : 'Minecraft' } ,
91- { slug : 'terraria' , label : 'Terraria' } ,
92- { slug : 'valheim' , label : 'Valheim' } ,
93- { slug : 'factorio' , label : 'Factorio' } ,
94- { slug : 'amongus' , label : 'Among Us' } ,
95- { slug : 'steam' , label : 'Steam' } ,
96- { slug : 'garrysmod' , label : 'Garry\'s Mod' } ,
97- { slug : 'teamfortress' , label : 'Team Fortress' } ,
98- { slug : 'left4dead' , label : 'L4D' } ,
99- { slug : 'halflife' , label : 'Half-Life' } ,
100- { slug : 'palworld' , label : 'Palworld' } ,
101- { slug : 'stardewvalley' , label : 'Stardew' } ,
102- { slug : 'dontstarve' , label : 'Don\'t Starve' } ,
103- { slug : 'callofduty' , label : 'Call of Duty' } ,
104- { slug : 'arma' , label : 'Arma' } ,
105- { slug : 'dayz' , label : 'DayZ' } ,
106- { slug : 'rustgame' , label : 'Rust (Game)' } ,
107- { slug : 'subnautica' , label : 'Subnautica' } ,
108- { slug : 'ark' , label : 'ARK' } ,
109- { slug : 'minecraftbedrock' , label : 'MC Bedrock' } ,
110- { slug : 'nodebb' , label : 'NodeBB' } ,
111- { slug : 'flarum' , label : 'Flarum' } ,
112- { slug : 'ghost' , label : 'Ghost' } ,
113- { slug : 'wordpress' , label : 'WordPress' } ,
114- { slug : 'drupal' , label : 'Drupal' } ,
115- { slug : 'joomla' , label : 'Joomla' } ,
116- { slug : 'grafana' , label : 'Grafana' } ,
117- { slug : 'prometheus' , label : 'Prometheus' } ,
118- { slug : 'zabbix' , label : 'Zabbix' } ,
119- { slug : 'portainer' , label : 'Portainer' } ,
120- { slug : 'jenkins' , label : 'Jenkins' } ,
121- { slug : 'gitlab' , label : 'GitLab' } ,
122- { slug : 'github' , label : 'GitHub' } ,
123- { slug : 'gitea' , label : 'Gitea' } ,
124- { slug : 'git' , label : 'Git' } ,
125- { slug : 'visualstudiocode' , label : 'VS Code' } ,
126- { slug : 'postman' , label : 'Postman' } ,
127- { slug : 'cloudflare' , label : 'Cloudflare' } ,
128- { slug : 'vercel' , label : 'Vercel' } ,
129- { slug : 'netlify' , label : 'Netlify' } ,
130- { slug : 'heroku' , label : 'Heroku' } ,
131- { slug : 'digitalocean' , label : 'DigitalOcean' } ,
132- { slug : 'vultr' , label : 'Vultr' } ,
133- { slug : 'hetzner' , label : 'Hetzner' } ,
134- { slug : 'oracle' , label : 'Oracle' } ,
135- { slug : 'linode' , label : 'Linode' } ,
136- { slug : 'amazonwebservices' , label : 'AWS' } ,
137- { slug : 'microsoftazure' , label : 'Azure' } ,
138- { slug : 'googlecloud' , label : 'GCP' } ,
139- { slug : 'openai' , label : 'OpenAI' } ,
140- { slug : 'discord' , label : 'Discord' } ,
141- { slug : 'telegram' , label : 'Telegram' } ,
142- { slug : 'slack' , label : 'Slack' } ,
143- { slug : 'plex' , label : 'Plex' } ,
144- { slug : 'jellyfin' , label : 'Jellyfin' } ,
145- { slug : 'emby' , label : 'Emby' } ,
146- { slug : 'nextcloud' , label : 'Nextcloud' } ,
147- { slug : 'vaultwarden' , label : 'Vaultwarden' } ,
148- { slug : 'wireguard' , label : 'WireGuard' } ,
149- { slug : 'openvpn' , label : 'OpenVPN' } ,
150- { slug : 'pihole' , label : 'Pi-hole' } ,
151- { slug : 'ubiquiti' , label : 'Ubiquiti' } ,
152- { slug : 'homeassistant' , label : 'Home Assistant' } ,
153- { slug : 'tautulli' , label : 'Tautulli' } ,
154- { slug : 'sonarqube' , label : 'SonarQube' } ,
155- { slug : 'verdaccio' , label : 'Verdaccio' } ,
156- { slug : 'minio' , label : 'MinIO' } ,
157- { slug : 'rabbitmq' , label : 'RabbitMQ' } ,
158- { slug : 'kafka' , label : 'Kafka' } ,
159- { slug : 'nats' , label : 'NATS' } ,
160- { slug : 'haproxy' , label : 'HAProxy' } ,
161- { slug : 'consul' , label : 'Consul' } ,
162- { slug : 'nomad' , label : 'Nomad' } ,
163- { slug : 'etcd' , label : 'etcd' } ,
164- { slug : 'podman' , label : 'Podman' } ,
165- { slug : 'lxc' , label : 'LXC' } ,
166- { slug : 'proxmox' , label : 'Proxmox' } ,
167- { slug : 'vmware' , label : 'VMware' } ,
168- { slug : 'vagrant' , label : 'Vagrant' } ,
169- ] ;
23+ // ─── Simple Icons Library (loaded dynamically) ────────────
24+ let _simpleIconsCache = null ;
25+ let _simpleIconsLoading = null ;
26+
27+ async function loadSimpleIcons ( ) {
28+ if ( _simpleIconsCache ) return _simpleIconsCache ;
29+ if ( _simpleIconsLoading ) return _simpleIconsLoading ;
30+
31+ _simpleIconsLoading = ( async ( ) => {
32+ try {
33+ const res = await fetch ( 'https://raw.githubusercontent.com/simple-icons/simple-icons/develop/slugs.md' ) ;
34+ const text = await res . text ( ) ;
35+ const icons = [ ] ;
36+ const re = / \| \s * ` ( [ ^ ` ] + ) ` \s * \| \s * ` ( [ ^ ` ] + ) ` \s * \| / g;
37+ let m ;
38+ while ( ( m = re . exec ( text ) ) !== null ) {
39+ const label = m [ 1 ] ;
40+ const slug = m [ 2 ] ;
41+ if ( slug && label ) icons . push ( { slug, label } ) ;
42+ }
43+ icons . sort ( ( a , b ) => a . label . localeCompare ( b . label ) ) ;
44+ _simpleIconsCache = icons ;
45+ } catch ( e ) {
46+ console . warn ( 'Failed to load Simple Icons list, using fallback' ) ;
47+ _simpleIconsCache = [
48+ { slug : 'docker' , label : 'Docker' } ,
49+ { slug : 'kubernetes' , label : 'Kubernetes' } ,
50+ { slug : 'minecraft' , label : 'Minecraft' } ,
51+ { slug : 'ubuntu' , label : 'Ubuntu' } ,
52+ { slug : 'debian' , label : 'Debian' } ,
53+ { slug : 'linux' , label : 'Linux' } ,
54+ { slug : 'github' , label : 'GitHub' } ,
55+ { slug : 'discord' , label : 'Discord' } ,
56+ { slug : 'database' , label : 'Database' } ,
57+ { slug : 'code' , label : 'Code' } ,
58+ ] ;
59+ }
60+ _simpleIconsLoading = null ;
61+ return _simpleIconsCache ;
62+ } ) ( ) ;
63+
64+ return _simpleIconsLoading ;
65+ }
17066
17167function siUrl ( slug ) {
17268 return `https://cdn.simpleicons.org/${ slug } ` ;
@@ -184,7 +80,12 @@ function renderLogoDisplay(logo, size, extraStyle) {
18480}
18581
18682function renderSvgPickerGrid ( selectedSlug ) {
187- return SIMPLE_ICONS . map ( icon => {
83+ return `<div style="text-align:center;padding:16px;color:var(--text-secondary)"><span class="spinner"></span> Loading icons...</div>` ;
84+ }
85+
86+ async function renderSvgPickerGridLoaded ( selectedSlug ) {
87+ const icons = await loadSimpleIcons ( ) ;
88+ return icons . map ( icon => {
18889 const isSelected = icon . slug === selectedSlug ;
18990 return `<div class="svg-picker-item ${ isSelected ? 'selected' : '' } " data-slug="${ icon . slug } " data-label="${ escapeHtml ( icon . label ) } " title="${ escapeHtml ( icon . label ) } ">
19091 <img src="${ siUrl ( icon . slug ) } " alt="${ escapeHtml ( icon . label ) } " loading="lazy" />
@@ -1965,16 +1866,6 @@ async function renderAdminEggSettings(nestId, eggId) {
19651866 } ) ;
19661867 } ) ;
19671868
1968- initSvgPickerListeners ( $a ( '#egg-svg-picker' ) , ( slug ) => {
1969- $a ( '#egg-svg-slug' ) . value = slug ;
1970- const prev = $a ( '#egg-svg-selected-preview' ) ;
1971- if ( prev ) {
1972- prev . style . display = 'flex' ;
1973- prev . querySelector ( 'img' ) . src = siUrl ( slug ) ;
1974- prev . querySelector ( '.logo-preview-label' ) . textContent = slug ;
1975- }
1976- } ) ;
1977-
19781869 $a ( '#egg-svg-clear' ) ?. addEventListener ( 'click' , ( ) => {
19791870 $a ( '#egg-svg-slug' ) . value = '' ;
19801871 const prev = $a ( '#egg-svg-selected-preview' ) ;
@@ -2054,6 +1945,22 @@ async function renderAdminEggSettings(nestId, eggId) {
20541945
20551946 initIcons ( ) ;
20561947
1948+ ( async ( ) => {
1949+ const grid = $a ( '#egg-svg-picker .svg-picker-grid' ) ;
1950+ if ( grid ) {
1951+ grid . innerHTML = await renderSvgPickerGridLoaded ( '' ) ;
1952+ initSvgPickerListeners ( $a ( '#egg-svg-picker' ) , ( slug ) => {
1953+ $a ( '#egg-svg-slug' ) . value = slug ;
1954+ const prev = $a ( '#egg-svg-selected-preview' ) ;
1955+ if ( prev ) {
1956+ prev . style . display = 'flex' ;
1957+ prev . querySelector ( 'img' ) . src = siUrl ( slug ) ;
1958+ prev . querySelector ( '.logo-preview-label' ) . textContent = slug ;
1959+ }
1960+ } ) ;
1961+ }
1962+ } ) ( ) ;
1963+
20571964 $a ( '#btn-save-egg-resources-all' ) ?. addEventListener ( 'click' , async ( ) => {
20581965 if ( ! confirm ( 'Apply these resources to ALL existing servers using this egg? This will update their limits on the panel.' ) ) return ;
20591966 const btn = $a ( '#btn-save-egg-resources-all' ) ;
@@ -2247,6 +2154,22 @@ function showRenameNestModal(nestId, currentName, currentLogo, currentDescriptio
22472154 overlay . style . display = 'flex' ;
22482155 initIcons ( ) ;
22492156
2157+ ( async ( ) => {
2158+ const grid = $a ( '#nest-svg-picker .svg-picker-grid' ) ;
2159+ if ( grid ) {
2160+ grid . innerHTML = await renderSvgPickerGridLoaded ( initialSvgSlug ) ;
2161+ initSvgPickerListeners ( $a ( '#nest-svg-picker' ) , ( slug ) => {
2162+ $a ( '#modal-edit-nest-svg-slug' ) . value = slug ;
2163+ const prev = $a ( '#nest-svg-selected-preview' ) ;
2164+ if ( prev ) {
2165+ prev . style . display = 'flex' ;
2166+ prev . querySelector ( 'img' ) . src = siUrl ( slug ) ;
2167+ prev . querySelector ( '.logo-preview-label' ) . textContent = slug ;
2168+ }
2169+ } ) ;
2170+ }
2171+ } ) ( ) ;
2172+
22502173 const toggleBtns = content . querySelectorAll ( '.logo-type-toggle button' ) ;
22512174 toggleBtns . forEach ( btn => {
22522175 btn . addEventListener ( 'click' , ( ) => {
@@ -2271,16 +2194,6 @@ function showRenameNestModal(nestId, currentName, currentLogo, currentDescriptio
22712194 }
22722195 } ) ;
22732196
2274- initSvgPickerListeners ( $a ( '#nest-svg-picker' ) , ( slug ) => {
2275- $a ( '#modal-edit-nest-svg-slug' ) . value = slug ;
2276- const prev = $a ( '#nest-svg-selected-preview' ) ;
2277- if ( prev ) {
2278- prev . style . display = 'flex' ;
2279- prev . querySelector ( 'img' ) . src = siUrl ( slug ) ;
2280- prev . querySelector ( '.logo-preview-label' ) . textContent = slug ;
2281- }
2282- } ) ;
2283-
22842197 $a ( '#nest-svg-clear' ) ?. addEventListener ( 'click' , ( ) => {
22852198 $a ( '#modal-edit-nest-svg-slug' ) . value = '' ;
22862199 const prev = $a ( '#nest-svg-selected-preview' ) ;
0 commit comments