mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-25 01:37:05 +00:00
feat(app-store): editorial hero, category rail, security scan signal per tile (#679)
* feat(app-store): editorial hero, category rail, security scan signal per tile Rework the App Store view into an editorial layout with a 180px category rail, a featured-template hero, and compact tiles that surface star counts and vulnerability scan status at a glance. The deploy sheet splits into Essentials (one-click deploy) and Advanced (full port/volume/env control) tabs. Backend enriches /api/templates with scan_status, scan_cve_count, and a featured flag computed from the highest-star template with recorded stars. Scan lookups use a single batched SQL query to avoid N+1 round-trips. * fix(app-store): split firstSentence helper into its own module The firstSentence helper lived alongside the TemplateLogo component, which violates react-refresh/only-export-components — a file that exports a component must not also export non-component values, or Fast Refresh cannot establish an HMR boundary. Move the helper into appstore/util.ts and update the two import sites.
This commit is contained in:
+30
-1
@@ -7258,7 +7258,36 @@ app.post('/api/system/networks', async (req: Request, res: Response) => {
|
||||
app.get('/api/templates', authMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const templates = await templateService.getTemplates();
|
||||
res.json(templates);
|
||||
|
||||
const imageRefs = templates.map(t => t.image).filter((i): i is string => !!i);
|
||||
const scanSummary = DatabaseService.getInstance().getLatestScanSummaryByImageRefs(req.nodeId, imageRefs);
|
||||
|
||||
let featuredIndex = -1;
|
||||
let featuredStars = 0;
|
||||
templates.forEach((t, i) => {
|
||||
const s = t.stars ?? 0;
|
||||
if (s > featuredStars) {
|
||||
featuredStars = s;
|
||||
featuredIndex = i;
|
||||
}
|
||||
});
|
||||
|
||||
const enriched = templates.map((t, i) => {
|
||||
const summary = t.image ? scanSummary.get(t.image) : undefined;
|
||||
const scan_status: 'clean' | 'vulnerable' | 'unscanned' = summary
|
||||
? (summary.total === 0 ? 'clean' : 'vulnerable')
|
||||
: 'unscanned';
|
||||
return {
|
||||
...t,
|
||||
scan_status,
|
||||
scan_cve_count: summary?.total ?? 0,
|
||||
scan_critical_count: summary?.critical ?? 0,
|
||||
scan_high_count: summary?.high ?? 0,
|
||||
featured: i === featuredIndex,
|
||||
};
|
||||
});
|
||||
|
||||
res.json(enriched);
|
||||
} catch (error) {
|
||||
console.error('[Templates] Failed to fetch:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch templates' });
|
||||
|
||||
Reference in New Issue
Block a user