feat(dedicated): minimal OS variants, single-open accordion, default Alma 9
Three asks shipped together: 1. Default flipped from AlmaLinux 10 → AlmaLinux 9. Alma 10 stays in the picker but isn't pre-selected; 9 is the more battle-tested choice for production dedicated workloads. 2. Single-open accordion: openFamilies (Set<string>) → openFamily (string). Opening any family closes whichever was previously open. Click the open family's header to fully collapse. Watch on `props.selected` keeps the active selection's family open on first paint and on programmatic selection changes (URL hydration). Removed the "Expand all / Collapse all" toggle in the title row — redundant under single-open semantics. 3. "Minimal" image variants added for every distro that publishes one upstream: AlmaLinux / Rocky / Ubuntu / Debian / Fedora / openSUSE / FreeBSD. New labels add a clear "Minimal" suffix; new slugs use `-min` suffix (e.g. alma9-min, ubuntu24-min). Proxmox / Windows / "No OS" deliberately have no minimal variant — Proxmox is a single-flavor hypervisor, Windows is BYOL, "No OS" is a no-op. Total OS count: 22 → 38 (across 9 families). Reseeded the OS group; 20/20 dedicated tests still pass; npm run build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -464,23 +464,44 @@ class ConfigOptionSeeder extends Seeder
|
||||
// FreeBSD 15.0 (Dec 2025), 14.4 (Mar 2026)
|
||||
// Proxmox VE 9.1 (Nov 2025), 8.4 (security through Aug 2026)
|
||||
// Windows Server 2025 (Nov 2024), 2022, 2019
|
||||
// Each Linux distro has a "Minimal" variant where one is published
|
||||
// upstream — stripped-down image with no GUI / dev tools / docs,
|
||||
// intended for cloud / container / appliance workloads. Slugs use
|
||||
// the `-min` suffix; family grouping in the picker keeps the pair
|
||||
// adjacent. Proxmox / Windows / "No OS" don't have minimal variants.
|
||||
$this->seedValues($gen14OsOption, [
|
||||
['label' => 'AlmaLinux 10', 'value' => 'alma10', 'monthly' => 0, 'is_default' => true],
|
||||
['label' => 'AlmaLinux 9', 'value' => 'alma9', 'monthly' => 0],
|
||||
['label' => 'AlmaLinux 10', 'value' => 'alma10', 'monthly' => 0],
|
||||
['label' => 'AlmaLinux 10 Minimal', 'value' => 'alma10-min', 'monthly' => 0],
|
||||
['label' => 'AlmaLinux 9', 'value' => 'alma9', 'monthly' => 0, 'is_default' => true],
|
||||
['label' => 'AlmaLinux 9 Minimal', 'value' => 'alma9-min', 'monthly' => 0],
|
||||
['label' => 'AlmaLinux 8', 'value' => 'alma8', 'monthly' => 0],
|
||||
['label' => 'AlmaLinux 8 Minimal', 'value' => 'alma8-min', 'monthly' => 0],
|
||||
['label' => 'Rocky Linux 10', 'value' => 'rocky10', 'monthly' => 0],
|
||||
['label' => 'Rocky Linux 10 Minimal', 'value' => 'rocky10-min', 'monthly' => 0],
|
||||
['label' => 'Rocky Linux 9', 'value' => 'rocky9', 'monthly' => 0],
|
||||
['label' => 'Rocky Linux 9 Minimal', 'value' => 'rocky9-min', 'monthly' => 0],
|
||||
['label' => 'Rocky Linux 8', 'value' => 'rocky8', 'monthly' => 0],
|
||||
['label' => 'Rocky Linux 8 Minimal', 'value' => 'rocky8-min', 'monthly' => 0],
|
||||
['label' => 'Ubuntu 26.04 LTS', 'value' => 'ubuntu26', 'monthly' => 0],
|
||||
['label' => 'Ubuntu 26.04 LTS Minimal', 'value' => 'ubuntu26-min', 'monthly' => 0],
|
||||
['label' => 'Ubuntu 24.04 LTS', 'value' => 'ubuntu24', 'monthly' => 0],
|
||||
['label' => 'Ubuntu 24.04 LTS Minimal', 'value' => 'ubuntu24-min', 'monthly' => 0],
|
||||
['label' => 'Ubuntu 22.04 LTS', 'value' => 'ubuntu22', 'monthly' => 0],
|
||||
['label' => 'Ubuntu 22.04 LTS Minimal', 'value' => 'ubuntu22-min', 'monthly' => 0],
|
||||
['label' => 'Debian 13 (Trixie)', 'value' => 'debian13', 'monthly' => 0],
|
||||
['label' => 'Debian 13 Minimal', 'value' => 'debian13-min', 'monthly' => 0],
|
||||
['label' => 'Debian 12 (Bookworm)', 'value' => 'debian12', 'monthly' => 0],
|
||||
['label' => 'Debian 12 Minimal', 'value' => 'debian12-min', 'monthly' => 0],
|
||||
['label' => 'Fedora Server 44', 'value' => 'fedora44', 'monthly' => 0],
|
||||
['label' => 'Fedora Server 44 Minimal', 'value' => 'fedora44-min', 'monthly' => 0],
|
||||
['label' => 'Fedora Server 43', 'value' => 'fedora43', 'monthly' => 0],
|
||||
['label' => 'Fedora Server 43 Minimal', 'value' => 'fedora43-min', 'monthly' => 0],
|
||||
['label' => 'openSUSE Leap 16.0', 'value' => 'opensuse-leap-160', 'monthly' => 0],
|
||||
['label' => 'openSUSE Leap 16.0 Minimal', 'value' => 'opensuse-leap-160-min', 'monthly' => 0],
|
||||
['label' => 'FreeBSD 15.0', 'value' => 'freebsd15', 'monthly' => 0],
|
||||
['label' => 'FreeBSD 15.0 Minimal', 'value' => 'freebsd15-min', 'monthly' => 0],
|
||||
['label' => 'FreeBSD 14.4', 'value' => 'freebsd14', 'monthly' => 0],
|
||||
['label' => 'FreeBSD 14.4 Minimal', 'value' => 'freebsd14-min', 'monthly' => 0],
|
||||
['label' => 'Proxmox VE 9.1', 'value' => 'proxmox9', 'monthly' => 0],
|
||||
['label' => 'Proxmox VE 8.4', 'value' => 'proxmox8', 'monthly' => 0],
|
||||
['label' => 'Windows Server 2025 (BYOL)', 'value' => 'windows-2025-byol', 'monthly' => 0],
|
||||
|
||||
@@ -56,34 +56,28 @@ const familyGroups = computed<FamilyGroup[]>(() => {
|
||||
return Array.from(map.values()).sort((a, b) => a.rank - b.rank)
|
||||
})
|
||||
|
||||
// Set of family names that are currently expanded. By default we open
|
||||
// only the family that contains the current selection — keeps the picker
|
||||
// compact while still showing the active choice.
|
||||
const openFamilies = ref<Set<string>>(new Set())
|
||||
// Single-open accordion: only one family expanded at a time. Defaults to
|
||||
// whichever family contains the current selection so the user always sees
|
||||
// their pick on first paint. Click the open family's header to close it
|
||||
// (no family expanded), or click another to swap.
|
||||
const openFamily = ref<string>('')
|
||||
|
||||
watch(
|
||||
() => props.selected,
|
||||
(slug) => {
|
||||
if (!slug) return
|
||||
const meta = metaFor(slug)
|
||||
if (!openFamilies.value.has(meta.family)) {
|
||||
const next = new Set(openFamilies.value)
|
||||
next.add(meta.family)
|
||||
openFamilies.value = next
|
||||
}
|
||||
openFamily.value = meta.family
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function toggleFamily(name: string): void {
|
||||
const next = new Set(openFamilies.value)
|
||||
if (next.has(name)) next.delete(name)
|
||||
else next.add(name)
|
||||
openFamilies.value = next
|
||||
openFamily.value = openFamily.value === name ? '' : name
|
||||
}
|
||||
|
||||
function isFamilyOpen(name: string): boolean {
|
||||
return openFamilies.value.has(name)
|
||||
return openFamily.value === name
|
||||
}
|
||||
|
||||
function selectedInFamily(fam: FamilyGroup): DedicatedConfigValue | null {
|
||||
@@ -120,38 +114,12 @@ function priceLabel(v: DedicatedConfigValue): string {
|
||||
function logoFor(slug: string): string {
|
||||
return metaFor(slug).logo
|
||||
}
|
||||
|
||||
function expandAll(): void {
|
||||
openFamilies.value = new Set(familyGroups.value.map(f => f.family))
|
||||
}
|
||||
|
||||
function collapseAll(): void {
|
||||
// Keep the family with the selection open so the user always sees their pick.
|
||||
const meta = props.selected ? metaFor(props.selected) : null
|
||||
openFamilies.value = new Set(meta ? [meta.family] : [])
|
||||
}
|
||||
|
||||
const allOpen = computed<boolean>(() => openFamilies.value.size === familyGroups.value.length)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="os-group">
|
||||
<div class="os-group__head">
|
||||
<div class="os-group__head-row">
|
||||
<h4 class="os-group__title">{{ shortGroupLabel(group.name) }}</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="os-group__expand-toggle"
|
||||
@click="allOpen ? collapseAll() : expandAll()"
|
||||
>
|
||||
<VIcon
|
||||
:icon="allOpen ? 'tabler-fold' : 'tabler-fold-up'"
|
||||
size="14"
|
||||
class="me-1"
|
||||
/>
|
||||
{{ allOpen ? 'Collapse all' : 'Expand all' }}
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="group.description" class="os-group__desc">{{ group.description }}</p>
|
||||
</div>
|
||||
|
||||
@@ -227,38 +195,11 @@ const allOpen = computed<boolean>(() => openFamilies.value.size === familyGroups
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.os-group__head-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.os-group__title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.01em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.os-group__expand-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: rgba(var(--v-theme-on-surface), 0.6);
|
||||
background: transparent;
|
||||
border: 1px solid rgba(var(--v-theme-on-surface), 0.12);
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s, border-color 0.15s, background-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
color: rgba(var(--v-theme-on-surface), 0.95);
|
||||
border-color: rgba(var(--v-theme-primary), 0.45);
|
||||
background: rgba(var(--v-theme-primary), 0.06);
|
||||
}
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.os-group__desc {
|
||||
|
||||
Reference in New Issue
Block a user