Platform bundle: marketing publish, IT ops, packaging, agents mesh.

Volledige Foodlinkk Command Center uitbreiding met social automatisering,
reclamefolder filters, Proxmox monitoring en documentatie.
This commit is contained in:
Aissa
2026-06-09 00:41:27 +00:00
commit 5d60d33db1
212 changed files with 30044 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
{% extends "base.html" %}
{% block title %}Packaging Studio · Foodlinkk{% endblock %}
{% block extra_head %}
<style>
.pack-grid { display: grid; grid-template-columns: 1fr 340px; gap: 1rem; }
@media (max-width: 1024px) { .pack-grid { grid-template-columns: 1fr; } }
.pack-preview {
min-height: 460px;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at top, rgba(0,229,255,.08), rgba(0,0,0,.35));
border: 1px solid rgba(0,229,255,.2);
border-radius: 12px;
overflow: auto;
padding: 1rem;
}
.pack-preview svg { width: 100%; max-height: 72vh; }
.pack-tools { display:flex; gap:.5rem; flex-wrap:wrap; margin-top:.75rem; }
.elements-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.5rem; }
.project-list { max-height: 260px; overflow: auto; display:flex; flex-direction:column; gap:.5rem; }
.project-item { border:1px solid rgba(148,163,184,.15); border-radius:10px; padding:.6rem; background:rgba(0,0,0,.25); }
.project-item small { color: var(--text-secondary); display:block; margin-top:.25rem; }
</style>
{% endblock %}
{% block content %}
<div x-data="packagingStudio()" x-init="init()">
<header class="page-header">
<h1>Packaging Studio</h1>
<p class="subtitle page-tip">Foodlinkk branded box, wrap en labels met live SVG preview.</p>
</header>
<div class="pack-grid">
<section class="panel">
<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.75rem;">
<label>Type
<select class="form-input" x-model="form.type">
<option value="folding_box">Folding box</option>
<option value="wrap">Wrap</option>
<option value="round_label">Round label</option>
</select>
</label>
<label>Barcode value
<input class="form-input" x-model="form.barcode_value" placeholder="8710000000012" />
</label>
<label>Breedte (mm)<input type="number" class="form-input" x-model.number="form.width_mm" min="10" /></label>
<label>Hoogte (mm)<input type="number" class="form-input" x-model.number="form.height_mm" min="10" /></label>
<label>Diepte (mm)<input type="number" class="form-input" x-model.number="form.depth_mm" min="0" /></label>
</div>
<h2 style="font-size:1rem;margin:1rem 0 .5rem;">Elementen</h2>
<div class="elements-grid">
<label><input type="checkbox" x-model="form.elements.barcode"> Barcode</label>
<label><input type="checkbox" x-model="form.elements.logo_area"> Logo area</label>
<label><input type="checkbox" x-model="form.elements.fold_lines"> Fold lines</label>
<label><input type="checkbox" x-model="form.elements.cut_lines"> Cut lines</label>
</div>
<div class="pack-tools">
<button class="btn btn-primary" @click="generate()" :disabled="loading">
<span x-show="!loading">Genereer verpakking</span>
<span x-show="loading">Genereren…</span>
</button>
<a class="btn btn-ghost" :href="downloadUrl('svg')" x-show="projectId">Download SVG</a>
<a class="btn btn-ghost" :href="downloadUrl('png')" x-show="projectId">Download PNG</a>
<a class="btn btn-ghost" :href="downloadUrl('pdf')" x-show="projectId">Download PDF</a>
</div>
</section>
<aside class="panel">
<h2 style="font-size:1rem;margin:0 0 .75rem;">Recente projecten</h2>
<div class="project-list">
<template x-for="p in projects" :key="p.id">
<div class="project-item">
<strong x-text="p.spec.type"></strong>
<small x-text="p.spec.width_mm + '×' + p.spec.height_mm + '×' + p.spec.depth_mm + ' mm'"></small>
<small x-text="(p.created_at || '').replace('T',' ').slice(0,19)"></small>
<button class="btn btn-ghost" style="margin-top:.4rem;" @click="loadProject(p.id)">Open</button>
</div>
</template>
<p class="page-tip" x-show="!projects.length">Nog geen packaging projecten.</p>
</div>
</aside>
</div>
<div class="panel" style="margin-top:1rem;">
<div class="pack-preview">
<div x-show="svg" x-html="svg"></div>
<p class="page-tip" x-show="!svg && !loading">Start met Genereren om de preview te zien.</p>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function packagingStudio() {
return {
loading: false,
svg: '',
projectId: '',
projects: [],
form: {
type: 'folding_box',
width_mm: 120,
height_mm: 80,
depth_mm: 40,
barcode_value: '8710000000012',
brand: {},
elements: { barcode: true, logo_area: true, fold_lines: true, cut_lines: true }
},
async init() { await this.refreshProjects(); },
downloadUrl(format) {
if (!this.projectId) return '#';
return '/api/packaging/download/' + this.projectId + '?format=' + format;
},
async refreshProjects() {
try {
const r = await Cockpit.api('/api/packaging/projects?limit=20');
this.projects = r.items || [];
} catch (e) {
this.projects = [];
}
},
async loadProject(id) {
this.projectId = id;
try {
const data = await fetch('/api/packaging/download/' + id + '?format=svg').then(x => x.text());
this.svg = data;
} catch (e) {
Cockpit.toast('Kon project niet laden', 'error');
}
},
async generate() {
if (this.loading) return;
this.loading = true;
try {
const r = await Cockpit.api('/api/packaging/generate', {
method: 'POST',
body: JSON.stringify(this.form)
});
this.svg = r.svg || '';
this.projectId = r.id || '';
await this.refreshProjects();
Cockpit.toast('Packaging gegenereerd', 'success');
} catch (e) {
Cockpit.toast(e.message || 'Genereren mislukt', 'error');
}
this.loading = false;
}
};
}
</script>
{% endblock %}