fix(frontend): recipes page gutter, intro banner, filter chip tokens and memory units

This commit is contained in:
Juan Denis committed 2026-11-03 13:02:57 -05:00
1 parent 07ee00ae5d
commit f384d1851d
2 files changed
+61 -41

No files matched your search

+22 -9
View File
@@ -9,6 +9,7 @@ import {
import { Drawer } from '../components/ds';
import EmptyState from '../components/EmptyState';
import SectionIntro from '../components/SectionIntro';
import FormField from '../components/FormField';
import { runRecipe, useRecipeCatalog } from '../hooks/useRecipeCatalog';
import { useToast } from '../contexts/useToast.js';
@@ -36,7 +37,8 @@ function RecipeIcon({ icon }) {
return <Icon size={17} />;
}
const GB = 1024 * 1024 * 1024;
const MB = 1024 * 1024;
const GB = 1024 * MB;
function preflight(requirements, server) {
if (!server || !requirements) return [];
@@ -52,9 +54,9 @@ function preflight(requirements, server) {
if (requirements.memoryMB) {
rows.push({
key: 'mem',
need: formatBytes(requirements.memoryMB * GB, { decimals: 0 }),
need: formatBytes(requirements.memoryMB * MB, { decimals: 0 }),
have: formatBytes(server.total_memory, { decimals: 0 }),
ok: !!server.total_memory && server.total_memory >= requirements.memoryMB * GB,
ok: !!server.total_memory && server.total_memory >= requirements.memoryMB * MB,
});
}
if (requirements.diskGB) {
@@ -143,11 +145,21 @@ export default function Recipes() {
};
return (
<div className="recipes-page">
<div className="sk-tabgroup__inner recipes-page">
<div className="recipes-page__head">
<p className="recipes-page__lede">
{t('app.recipes.lede', 'Ready-to-go installers that run as real operations: probe, derive, apply, verify. Secrets are asked for mid-run and kept in the vault.')}
</p>
<SectionIntro
className="recipes-page__intro"
icon={<ChefHat />}
info={t('app.recipes.lede', 'Ready-to-go installers that run as real operations: probe, derive, apply, verify. Secrets are asked for mid-run and kept in the vault.')}
steps={[
{ icon: <Search />, label: t('app.recipes.stepProbe', 'Probe'), info: t('app.recipes.stepProbeInfo', 'Check the target server meets the requirements.') },
{ icon: <Play />, label: t('app.recipes.stepApply', 'Apply'), info: t('app.recipes.stepApplyInfo', 'Derive settings and run the install as a real operation.') },
{ icon: <Lock />, label: t('app.recipes.stepSecrets', 'Secrets'), info: t('app.recipes.stepSecretsInfo', 'Secrets are asked for mid-run and kept in the vault.') },
{ icon: <Check />, label: t('app.recipes.stepVerify', 'Verify'), info: t('app.recipes.stepVerifyInfo', 'Confirm the service came up healthy.') },
]}
>
{t('app.recipes.headerLine', 'One-click installers')}
</SectionIntro>
<label className="recipes-page__search">
<Search size={14} />
<input
@@ -165,7 +177,8 @@ export default function Recipes() {
<SharedButton variant="unstyled"
key={cat}
type="button"
className={`sk-pill${category === cat ? ' is-active' : ''}`}
className={`recipes-page__cat${category === cat ? ' is-active' : ''}`}
aria-pressed={category === cat}
onClick={() => setCategory(cat)}
>
{cat === 'all' ? t('common.labels.all', 'All') : cat}
@@ -214,7 +227,7 @@ export default function Recipes() {
{(recipe.requirements?.cpuCores)
&& <span>{recipe.requirements.cpuCores} vCPU</span>}
{(recipe.requirements?.memoryMB)
&& <span>{formatBytes(recipe.requirements.memoryMB * GB, { decimals: 0 })}</span>}
&& <span>{formatBytes(recipe.requirements.memoryMB * MB, { decimals: 0 })}</span>}
{(recipe.requirements?.diskGB)
&& <span>{formatBytes(recipe.requirements.diskGB * GB, { decimals: 0 })}</span>}
{(recipe.requirements?.igpu && recipe.requirements.igpu !== 'none')
+39 -32
View File
@@ -5,29 +5,25 @@
&__head {
display: flex;
align-items: flex-end;
gap: $spacing-lg;
flex-direction: column;
gap: $spacing-md;
margin-bottom: $spacing-md;
}
&__lede {
&__intro {
flex: 1;
min-width: 0;
margin: 0;
color: var(--text-secondary);
font-size: $text-sm;
line-height: 1.55;
max-width: 640px;
}
&__search {
display: flex;
align-items: center;
gap: $space-2;
width: 260px;
width: 100%;
max-width: 360px;
height: $control-md;
padding: 0 $space-3;
border: 1px solid var(--border-color);
border: 1px solid var(--border-default);
border-radius: $radius-control;
background: var(--bg-card);
color: var(--text-tertiary);
@@ -48,16 +44,27 @@
flex-wrap: wrap;
gap: $space-2;
margin-bottom: $spacing-md;
}
.sk-pill {
cursor: pointer;
opacity: 0.72;
&__cat {
display: inline-flex;
align-items: center;
min-height: $control-sm;
padding: 0 $space-3;
border: 1px solid var(--border-default);
border-radius: $radius-full;
background: var(--bg-card);
color: var(--text-secondary);
font-size: $text-sm;
font-weight: $font-weight-medium;
cursor: pointer;
&.is-active {
opacity: 1;
border-color: var(--primary-color);
color: var(--primary-color);
}
&:hover { color: var(--text-primary); }
&.is-active {
border-color: var(--accent);
background: var(--accent-bg);
color: var(--accent);
}
}
@@ -87,7 +94,7 @@
flex-direction: column;
gap: $space-3;
padding: $spacing-md;
border: 1px solid var(--border-color);
border: 1px solid var(--border-default);
border-radius: $radius-card;
background: var(--bg-card);
@@ -105,8 +112,8 @@
height: 34px; // geometry: icon tile
flex-shrink: 0;
border-radius: $radius-control;
background: color-mix(in srgb, var(--primary-color) 12%, transparent);
color: var(--primary-color);
background: var(--accent-bg);
color: var(--accent);
}
&__id {
@@ -152,7 +159,7 @@
span {
padding: $space-hair $space-2;
border-radius: $radius-badge;
border: 1px solid var(--border-color);
border: 1px solid var(--border-default);
font-size: $text-xs;
color: var(--text-tertiary);
@@ -201,7 +208,7 @@
width: 100%;
height: $control-md;
padding: 0 $space-3;
border: 1px solid var(--border-color);
border: 1px solid var(--border-default);
border-radius: $radius-control;
background: var(--bg-input, var(--bg-card));
color: var(--text-primary);
@@ -220,7 +227,7 @@
width: 100%;
padding: $space-3 $space-3;
border: none;
border-bottom: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-default);
background: transparent;
color: var(--text-primary);
cursor: pointer;
@@ -231,9 +238,9 @@
&:hover { background: var(--bg-hover, rgba(128, 128, 128, 0.06)); }
&.is-on {
background: color-mix(in srgb, var(--primary-color) 8%, transparent);
background: var(--accent-bg);
svg { color: var(--primary-color); }
svg { color: var(--accent); }
}
}
@@ -242,11 +249,11 @@
height: 14px;
flex-shrink: 0;
border-radius: 50%;
border: 1.5px solid var(--border-color);
border: 1.5px solid var(--border-default);
.is-on & {
border-color: var(--primary-color);
box-shadow: inset 0 0 0 3.5px var(--primary-color); // geometry: draws the radio's inner dot
border-color: var(--accent);
box-shadow: inset 0 0 0 3.5px var(--accent); // geometry: draws the radio's inner dot
}
}
@@ -298,7 +305,7 @@
align-items: center;
gap: $space-2;
padding: $space-2 $space-3;
border-bottom: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-default);
font-size: $text-sm;
&:last-child { border-bottom: none; }
@@ -325,7 +332,7 @@
align-items: center;
gap: $space-2;
padding: $space-2 $space-3;
border-bottom: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-default);
font-size: $text-sm;
&:last-child { border-bottom: none; }
@@ -336,6 +343,6 @@
&__url {
font-size: $text-xs;
color: var(--primary-color);
color: var(--accent);
}
}