mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-10 05:53:05 +09:00
fix(frontend): recipes page gutter, intro banner, filter chip tokens and memory units
This commit is contained in:
1 parent
07ee00ae5d
commit
f384d1851d
2 files changed
+61
-41
No files matched your search
@@ -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')
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user