Added disks selection
This commit is contained in:
+141
-27
@@ -385,6 +385,100 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 12: Disks -->
|
||||||
|
<div x-show="step === 12" class="space-y-8">
|
||||||
|
<div class="border-b border-slate-700 pb-4">
|
||||||
|
<h2 class="text-4xl font-bold text-sky-400">Disks</h2>
|
||||||
|
<p class="text-slate-400 mt-2 text-lg">Select where NixOS will be installed and where your data will live.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Critical Warning -->
|
||||||
|
<div class="bg-red-500/10 border border-red-500/50 p-6 rounded-2xl flex gap-6 items-start">
|
||||||
|
<div class="bg-red-500 rounded-full p-2 mt-1 shrink-0">
|
||||||
|
<i class="mdi mdi-alert-octagon text-white text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 class="text-red-400 font-bold text-lg">Data Wipe Warning</h4>
|
||||||
|
<p class="text-sm text-red-200/80 leading-relaxed">Selecting a disk for Numbus will <strong>completely erase all existing data</strong> on that drive. Ensure you have backed up any important information before proceeding.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||||
|
<!-- Boot Selection -->
|
||||||
|
<div class="space-y-6">
|
||||||
|
<h3 class="text-xl font-bold text-white flex items-center gap-2">
|
||||||
|
<i class="mdi mdi-boot text-sky-400"></i> Boot Disks
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-slate-400">Choose 1 (Stripe) or 2 (Mirror) disks for the operating system.</p>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<template x-for="disk in hardwareData.disks" :key="disk.id">
|
||||||
|
<button @click="toggleDisk(disk.id, 'boot')"
|
||||||
|
x-show="!formData.disks.data.includes(disk.id)"
|
||||||
|
:class="formData.disks.boot.includes(disk.id) ? 'bg-fuchsia-600/20 border-fuchsia-500' : 'bg-slate-900 border-slate-700 opacity-60 hover:opacity-100'"
|
||||||
|
class="w-full p-4 border rounded-xl text-left transition-all group">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<i class="mdi mdi-harddisk text-2xl" :class="formData.disks.boot.includes(disk.id) ? 'text-white' : 'text-slate-500'"></i>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-sm" x-text="disk.name"></div>
|
||||||
|
<div class="text-[10px] uppercase text-slate-500 font-bold" x-text="disk.size + ' • ' + disk.type"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<div class="text-[10px] font-bold" :class="disk.health === 'PASSED' ? 'text-green-500' : 'text-amber-500'" x-text="'Health: ' + disk.health"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Boot Safety Warning -->
|
||||||
|
<div x-show="formData.disks.boot.length === 1" class="bg-amber-500/5 border border-amber-500/20 p-4 rounded-xl">
|
||||||
|
<p class="text-xs text-amber-200/70"><i class="mdi mdi-shield-alert mr-1"></i> <strong>Striped Config:</strong> Using only one boot disk provides no hardware redundancy. If this drive fails, the system will not boot.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Data Selection -->
|
||||||
|
<div class="space-y-6">
|
||||||
|
<h3 class="text-xl font-bold text-white flex items-center gap-2">
|
||||||
|
<i class="mdi mdi-database text-sky-400"></i> Data Disks
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-slate-400">Select disks for storage. SnapRAID parity is used if >1 disk is chosen.</p>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<template x-for="disk in hardwareData.disks" :key="disk.id">
|
||||||
|
<button @click="toggleDisk(disk.id, 'data')"
|
||||||
|
x-show="!formData.disks.boot.includes(disk.id)"
|
||||||
|
:class="formData.disks.data.includes(disk.id) ? 'bg-fuchsia-600/20 border-fuchsia-500' : 'bg-slate-900 border-slate-700 opacity-60 hover:opacity-100'"
|
||||||
|
class="w-full p-4 border rounded-xl text-left transition-all group">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<i class="mdi mdi-server text-2xl" :class="formData.disks.data.includes(disk.id) ? 'text-white' : 'text-slate-500'"></i>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-sm" x-text="disk.name"></div>
|
||||||
|
<div class="text-[10px] uppercase text-slate-500 font-bold" x-text="disk.size + ' • ' + disk.type"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<div class="text-[10px] font-bold" :class="disk.health === 'PASSED' ? 'text-green-500' : 'text-amber-500'" x-text="'Health: ' + disk.health"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Data Safety Warning -->
|
||||||
|
<div x-show="formData.disks.data.length === 1" class="bg-amber-500/5 border border-amber-500/20 p-4 rounded-xl">
|
||||||
|
<p class="text-xs text-amber-200/70"><i class="mdi mdi-shield-alert mr-1"></i> <strong>Striped Config:</strong> One data disk provides no parity. Data loss will occur if this drive fails.</p>
|
||||||
|
</div>
|
||||||
|
<div x-show="formData.disks.data.length > 1" class="bg-green-500/5 border border-green-500/20 p-4 rounded-xl">
|
||||||
|
<p class="text-xs text-green-400/70"><i class="mdi mdi-shield-check mr-1"></i> <strong>SnapRAID Active:</strong> Your data is protected by parity drives.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Step 6: Network -->
|
<!-- Step 6: Network -->
|
||||||
<div x-show="step === 6" class="space-y-8">
|
<div x-show="step === 6" class="space-y-8">
|
||||||
<div class="border-b border-slate-700 pb-4">
|
<div class="border-b border-slate-700 pb-4">
|
||||||
@@ -672,8 +766,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 12: Non-Interactive Git Config -->
|
<!-- Step 13: Non-Interactive Git Config -->
|
||||||
<div x-show="step === 12" class="space-y-8">
|
<div x-show="step === 13" class="space-y-8">
|
||||||
<div class="border-b border-slate-700 pb-4">
|
<div class="border-b border-slate-700 pb-4">
|
||||||
<h2 class="text-4xl font-bold text-sky-400">Configuration Source</h2>
|
<h2 class="text-4xl font-bold text-sky-400">Configuration Source</h2>
|
||||||
<p class="text-slate-400 mt-2 text-lg">Link your Git repository containing the numbus.yaml file.</p>
|
<p class="text-slate-400 mt-2 text-lg">Link your Git repository containing the numbus.yaml file.</p>
|
||||||
@@ -696,8 +790,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 13: Setup Type & Restore -->
|
<!-- Step 14: Setup Type & Restore -->
|
||||||
<div x-show="step === 13" class="space-y-8">
|
<div x-show="step === 14" class="space-y-8">
|
||||||
<div class="border-b border-slate-700 pb-4">
|
<div class="border-b border-slate-700 pb-4">
|
||||||
<h2 class="text-4xl font-bold text-sky-400">Setup Type</h2>
|
<h2 class="text-4xl font-bold text-sky-400">Setup Type</h2>
|
||||||
<p class="text-slate-400 mt-2 text-lg">Are we creating a new server instance or restoring an existing one?</p>
|
<p class="text-slate-400 mt-2 text-lg">Are we creating a new server instance or restoring an existing one?</p>
|
||||||
@@ -728,8 +822,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 14: Tweaks -->
|
<!-- Step 15: Tweaks -->
|
||||||
<div x-show="step === 14" class="space-y-8">
|
<div x-show="step === 15" class="space-y-8">
|
||||||
<div class="border-b border-slate-700 pb-4">
|
<div class="border-b border-slate-700 pb-4">
|
||||||
<h2 class="text-4xl font-bold text-sky-400">Environment Overrides</h2>
|
<h2 class="text-4xl font-bold text-sky-400">Environment Overrides</h2>
|
||||||
<p class="text-slate-400 mt-2 text-lg">Apply specific tweaks to this local deployment.</p>
|
<p class="text-slate-400 mt-2 text-lg">Apply specific tweaks to this local deployment.</p>
|
||||||
@@ -831,8 +925,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 15: Installation Progress -->
|
<!-- Step 16: Installation Progress -->
|
||||||
<div x-show="step === 15" class="flex flex-col items-center justify-center py-20 text-center space-y-8">
|
<div x-show="step === 16" class="flex flex-col items-center justify-center py-20 text-center space-y-8">
|
||||||
<div class="w-full max-w-md bg-slate-900 rounded-full h-4 overflow-hidden border border-slate-700">
|
<div class="w-full max-w-md bg-slate-900 rounded-full h-4 overflow-hidden border border-slate-700">
|
||||||
<div class="bg-fuchsia-600 h-full animate-pulse" style="width: 35%"></div>
|
<div class="bg-fuchsia-600 h-full animate-pulse" style="width: 35%"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -851,8 +945,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Final Review (Step 16) -->
|
<!-- Final Review (Step 17) -->
|
||||||
<div x-show="step === 16" class="space-y-8 text-center py-10">
|
<div x-show="step === 17" class="space-y-8 text-center py-10">
|
||||||
<div class="w-20 h-20 bg-green-500/20 text-green-500 rounded-full flex items-center justify-center mx-auto mb-6">
|
<div class="w-20 h-20 bg-green-500/20 text-green-500 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
<svg class="w-10 h-10" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>
|
<svg class="w-10 h-10" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -874,7 +968,7 @@
|
|||||||
>Back</button>
|
>Back</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
x-show="step > 0 && step < maxSteps && ![3,4,15].includes(step)"
|
x-show="step > 0 && step < maxSteps && ![3,4,16].includes(step)"
|
||||||
@click="if(canGoNext()) step++"
|
@click="if(canGoNext()) step++"
|
||||||
:disabled="!canGoNext()"
|
:disabled="!canGoNext()"
|
||||||
:class="!canGoNext() ? 'opacity-30 cursor-not-allowed' : ''"
|
:class="!canGoNext() ? 'opacity-30 cursor-not-allowed' : ''"
|
||||||
@@ -913,12 +1007,13 @@
|
|||||||
function configurator() {
|
function configurator() {
|
||||||
return {
|
return {
|
||||||
step: 0,
|
step: 0,
|
||||||
maxSteps: 15,
|
maxSteps: 16,
|
||||||
|
hardwareData: { disks: [] },
|
||||||
init() {
|
init() {
|
||||||
// Watch the step and reset scroll position of the main content area
|
// Watch the step and reset scroll position of the main content area
|
||||||
this.updateNavigation();
|
this.updateNavigation();
|
||||||
this.$watch('step', () => {
|
this.$watch('step', () => {
|
||||||
const container = this.$el.querySelector('.flex-grow.overflow-y-auto');
|
const container = this.$el.querySelector('.flex-grow');
|
||||||
if (container) {
|
if (container) {
|
||||||
container.scrollTop = 0;
|
container.scrollTop = 0;
|
||||||
}
|
}
|
||||||
@@ -935,10 +1030,10 @@
|
|||||||
{ label: 'Live Setup', step: 3 },
|
{ label: 'Live Setup', step: 3 },
|
||||||
{ label: 'Discovery', step: 4 }
|
{ label: 'Discovery', step: 4 }
|
||||||
]},
|
]},
|
||||||
{ title: 'Server Configuration', steps: [5,6,7,8,9,10,11,12,13,14], minors: [
|
{ title: 'Server Configuration', steps: [5,6,7,8,9,10,11,12,13,14,15], minors: [
|
||||||
{ label: 'Configuration', step: 12 }
|
{ label: 'Configuration', step: 13 }
|
||||||
]},
|
]},
|
||||||
{ title: 'Installation', steps: [15], minors: [
|
{ title: 'Installation', steps: [16], minors: [
|
||||||
{ label: 'Progress', step: 15 }
|
{ label: 'Progress', step: 15 }
|
||||||
]}
|
]}
|
||||||
],
|
],
|
||||||
@@ -960,11 +1055,14 @@
|
|||||||
if (isServer) {
|
if (isServer) {
|
||||||
configMinors.push({ label: 'Alerts', step: 11 });
|
configMinors.push({ label: 'Alerts', step: 11 });
|
||||||
}
|
}
|
||||||
|
if (isServer) {
|
||||||
|
configMinors.push({ label: 'Disks', step: 12 });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
configMinors = [
|
configMinors = [
|
||||||
{ label: 'Source Repo', step: 12 },
|
{ label: 'Source Repo', step: 13 },
|
||||||
{ label: 'Setup Type', step: 13 },
|
{ label: 'Setup Type', step: 14 },
|
||||||
{ label: 'Environment Tweaks', step: 14 }
|
{ label: 'Environment Tweaks', step: 15 }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -981,13 +1079,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Configuration',
|
title: 'Configuration',
|
||||||
steps: isInteractive ? [5, 6, 7, 8, 9, 10, 11] : [12, 13, 14],
|
steps: isInteractive ? [5, 6, 7, 8, 9, 10, 11, 12] : [13, 14, 15],
|
||||||
minors: configMinors
|
minors: configMinors
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Finalize',
|
title: 'Finalize',
|
||||||
steps: [15, 16],
|
steps: [16, 17],
|
||||||
minors: [{ label: 'Deployment Status', step: 15 }]
|
minors: [{ label: 'Deployment Status', step: 16 }]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
@@ -1064,6 +1162,10 @@
|
|||||||
ssl: {
|
ssl: {
|
||||||
domain: '',
|
domain: '',
|
||||||
cloudflare_token: ''
|
cloudflare_token: ''
|
||||||
|
},
|
||||||
|
disks: {
|
||||||
|
boot: [],
|
||||||
|
data: []
|
||||||
},
|
},
|
||||||
gitConfig: {
|
gitConfig: {
|
||||||
url: '',
|
url: '',
|
||||||
@@ -1090,6 +1192,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
toggleDisk(id, category) {
|
||||||
|
const list = this.formData.disks[category];
|
||||||
|
const index = list.indexOf(id);
|
||||||
|
if (index > -1) {
|
||||||
|
list.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
if (category === 'boot' && list.length >= 2) return;
|
||||||
|
if (category === 'data' && list.length >= 9) return;
|
||||||
|
list.push(id);
|
||||||
|
}
|
||||||
|
},
|
||||||
addGroup() {
|
addGroup() {
|
||||||
const name = this.newGroupName.trim().toLowerCase().replaceAll(' ', '_');
|
const name = this.newGroupName.trim().toLowerCase().replaceAll(' ', '_');
|
||||||
if (name && !this.formData.groups[name]) {
|
if (name && !this.formData.groups[name]) {
|
||||||
@@ -1165,7 +1278,7 @@
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.hardwareData = await res.json();
|
this.hardwareData = await res.json();
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
this.step = (this.formData.deploymentMode === 'interactive') ? 5 : 12;
|
this.step = (this.formData.deploymentMode === 'interactive') ? 5 : 13;
|
||||||
}
|
}
|
||||||
} catch (e) { /* Bridge might not have written file yet */ }
|
} catch (e) { /* Bridge might not have written file yet */ }
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@@ -1184,10 +1297,11 @@
|
|||||||
9: () => this.formData.security.ssh_keys.trim().length > 10 && this.formData.ssl.cloudflare_token.length > 10,
|
9: () => this.formData.security.ssh_keys.trim().length > 10 && this.formData.ssl.cloudflare_token.length > 10,
|
||||||
10: () => !!(this.formData.users[0].username && this.formData.users[0].name && this.validators.email(this.formData.users[0].email)),
|
10: () => !!(this.formData.users[0].username && this.formData.users[0].name && this.validators.email(this.formData.users[0].email)),
|
||||||
11: () => this.validators.domain(this.formData.mail.smtp_host) && this.validators.port(this.formData.mail.smtp_port),
|
11: () => this.validators.domain(this.formData.mail.smtp_host) && this.validators.port(this.formData.mail.smtp_port),
|
||||||
12: () => this.formData.gitConfig.url.length > 5,
|
12: () => this.formData.disks.boot.length >= 1,
|
||||||
13: () => !this.formData.restoreConfig.restore_data || (this.formData.restoreConfig.backup_host && this.formData.restoreConfig.setup_key),
|
13: () => this.formData.gitConfig.url.length > 5,
|
||||||
14: () => true,
|
14: () => !this.formData.restoreConfig.restore_data || (this.formData.restoreConfig.backup_host && this.formData.restoreConfig.setup_key),
|
||||||
15: () => false
|
15: () => true,
|
||||||
|
16: () => false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (rules[this.step]) {
|
if (rules[this.step]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user