| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- const fs = require('fs');
- const {
- Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
- AlignmentType, HeadingLevel, BorderStyle, WidthType, ShadingType,
- PageNumber, Footer, VerticalAlign, LevelFormat, ImageRun
- } = require('docx');
- // vision4it Farben (aus branding/colors.md)
- const C_DARK = "1A1A1A", C_GREEN = "13A983", C_GREY = "5C5C5C",
- C_LIGHT = "F4F4F4", C_BORDER = "D0D0D0";
- const LOGO = fs.readFileSync('/home/claude/branding/logo/logo.png');
- const LOGO_WHITE = fs.readFileSync('/home/claude/branding/logo/logo-white.png');
- // --- Helpers ---------------------------------------------------------------
- const noBorder = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" };
- const noBorders = { top: noBorder, bottom: noBorder, left: noBorder, right: noBorder };
- const thinBorder = { style: BorderStyle.SINGLE, size: 4, color: C_BORDER };
- const boxBorders = { top: thinBorder, bottom: thinBorder, left: thinBorder, right: thinBorder };
- const H1 = (text) => new Paragraph({
- heading: HeadingLevel.HEADING_1,
- spacing: { before: 340, after: 60 },
- border: { bottom: { style: BorderStyle.SINGLE, size: 8, color: C_GREEN, space: 4 } },
- children: [new TextRun({ text, bold: true, color: C_DARK, size: 28, font: "Calibri" })],
- });
- const H2 = (text) => new Paragraph({
- heading: HeadingLevel.HEADING_2,
- spacing: { before: 240, after: 100 },
- children: [new TextRun({ text, bold: true, color: C_DARK, size: 24, font: "Calibri" })],
- });
- const P = (text, opts = {}) => new Paragraph({
- spacing: { after: 120 },
- alignment: AlignmentType.JUSTIFIED,
- children: [new TextRun({ text, size: 22, color: opts.color || C_DARK, font: "Calibri" })],
- });
- const Bullet = (text) => new Paragraph({
- numbering: { reference: "bullets", level: 0 },
- spacing: { after: 60 },
- children: [new TextRun({ text, size: 22, color: C_DARK, font: "Calibri" })],
- });
- const Spacer = (h) => new Paragraph({ spacing: { after: h }, children: [] });
- // Phasentabelle
- const phaseRow = (pos, leistung, stunden, opts = {}) => new TableRow({
- children: [
- new TableCell({
- borders: { top: noBorder, left: noBorder, right: noBorder, bottom: thinBorder },
- width: { size: 900, type: WidthType.DXA },
- shading: opts.shade ? { fill: opts.shade, type: ShadingType.CLEAR } : undefined,
- margins: { top: 80, bottom: 80, left: 100, right: 100 },
- children: [new Paragraph({ alignment: AlignmentType.CENTER, children: [
- new TextRun({ text: pos, size: 22, bold: opts.bold, color: C_DARK, font: "Calibri" })] })],
- }),
- new TableCell({
- borders: { top: noBorder, left: noBorder, right: noBorder, bottom: thinBorder },
- width: { size: 6600, type: WidthType.DXA },
- shading: opts.shade ? { fill: opts.shade, type: ShadingType.CLEAR } : undefined,
- margins: { top: 80, bottom: 80, left: 100, right: 100 },
- children: [new Paragraph({ children: [
- new TextRun({ text: leistung, size: 22, bold: opts.bold, color: C_DARK, font: "Calibri" })] })],
- }),
- new TableCell({
- borders: { top: noBorder, left: noBorder, right: noBorder, bottom: thinBorder },
- width: { size: 1860, type: WidthType.DXA },
- shading: opts.shade ? { fill: opts.shade, type: ShadingType.CLEAR } : undefined,
- margins: { top: 80, bottom: 80, left: 100, right: 100 },
- children: [new Paragraph({ alignment: AlignmentType.RIGHT, children: [
- new TextRun({ text: stunden, size: 22, bold: opts.bold, color: opts.green ? C_GREEN : C_DARK, font: "Calibri" })] })],
- }),
- ],
- });
- const phaseTable = () => new Table({
- width: { size: 9360, type: WidthType.DXA },
- columnWidths: [900, 6600, 1860],
- rows: [
- new TableRow({ tableHeader: true, children: [
- ...[["Pos", 900, AlignmentType.CENTER], ["Leistung", 6600, AlignmentType.LEFT], ["Stunden", 1860, AlignmentType.RIGHT]].map(([t, w, al]) =>
- new TableCell({
- borders: { top: noBorder, left: noBorder, right: noBorder, bottom: { style: BorderStyle.SINGLE, size: 8, color: C_GREEN } },
- width: { size: w, type: WidthType.DXA },
- shading: { fill: C_LIGHT, type: ShadingType.CLEAR },
- margins: { top: 80, bottom: 80, left: 100, right: 100 },
- children: [new Paragraph({ alignment: al, children: [
- new TextRun({ text: t, size: 22, bold: true, color: C_DARK, font: "Calibri" })] })],
- })),
- ]}),
- phaseRow("1", "Vorbereitung & Planung (Bestandsaufnahme, Detail-Design, Projektmanagement)", "10"),
- phaseRow("2", "Basis-Infrastruktur (Proxmox-Host, OPNsense, VLAN-Segmentierung)", "14"),
- phaseRow("3", "Server-Rollen (DC, Fileserver, Fachanwendung)", "18"),
- phaseRow("4", "Sicherheit & Monitoring (Firewall-Regelwerk, TacticalRMM, Backup)", "12"),
- phaseRow("5", "Migration & Rollout (Datenübernahme, Client-Onboarding, Schulung)", "16"),
- phaseRow("6", "Übergabe & Dokumentation (Hudu, Hypercare, Abnahme)", "8"),
- phaseRow("", "Zwischensumme", "78", { bold: true, shade: C_LIGHT }),
- phaseRow("7", "Projektpuffer (15 %)", "12"),
- phaseRow("", "Gesamt Dienstleistung", "90 h", { bold: true, shade: C_LIGHT, green: true }),
- ],
- });
- // Infozeile Deckblatt
- const coverMeta = (label, value) => new Paragraph({
- spacing: { after: 60 },
- children: [
- new TextRun({ text: label + " ", size: 20, color: "9E9E9E", font: "Calibri" }),
- new TextRun({ text: value, size: 22, color: "FFFFFF", font: "Calibri" }),
- ],
- });
- // --- Dokument --------------------------------------------------------------
- const doc = new Document({
- numbering: {
- config: [{
- reference: "bullets",
- levels: [{
- level: 0, format: LevelFormat.BULLET, text: "\u2022",
- alignment: AlignmentType.LEFT,
- style: { paragraph: { indent: { left: 720, hanging: 360 } } },
- }],
- }],
- },
- styles: {
- default: { document: { run: { font: "Calibri", size: 22, color: C_DARK } } },
- },
- sections: [
- // ============ Section 1: Deckblatt (hell, druckfreundlich, ohne Fußzeile) ============
- {
- properties: { page: { margin: { top: 1700, bottom: 1134, left: 1418, right: 1418 } } },
- children: [
- new Paragraph({
- alignment: AlignmentType.RIGHT,
- spacing: { after: 3000 },
- children: [new ImageRun({ data: LOGO, transformation: { width: 247, height: 60 }, type: 'png' })],
- }),
- new Paragraph({
- spacing: { after: 100 },
- children: [new TextRun({ text: "ANGEBOT & KURZKONZEPT", size: 22, color: C_GREEN, bold: true, font: "Calibri", characterSpacing: 40 })],
- }),
- new Paragraph({
- spacing: { after: 140 },
- children: [new TextRun({ text: "IT-Infrastruktur", size: 72, bold: true, color: C_DARK, font: "Calibri" })],
- }),
- new Paragraph({
- spacing: { after: 200 },
- border: { bottom: { style: BorderStyle.SINGLE, size: 12, color: C_GREEN, space: 12 } },
- children: [new TextRun({ text: "Modernisierung Serverumgebung & Netzwerk", size: 30, color: C_GREY, font: "Calibri" })],
- }),
- Spacer(2600),
- new Table({
- width: { size: 9071, type: WidthType.DXA },
- columnWidths: [2200, 6871],
- rows: [
- ["Kunde", "Musterfirma GmbH, Rommerskirchen"],
- ["Datum", "21. Juli 2026"],
- ["Angebots-Nr.", "AN-2026-042 \u00b7 Version 1.0"],
- ["Ansprechpartner", "Matthias, vision4it GmbH"],
- ].map(([label, value]) => new TableRow({
- children: [
- new TableCell({
- borders: { top: noBorder, left: noBorder, right: noBorder, bottom: thinBorder },
- width: { size: 2200, type: WidthType.DXA },
- margins: { top: 100, bottom: 100, left: 0, right: 100 },
- children: [new Paragraph({ children: [new TextRun({ text: label, size: 20, color: C_GREY, font: "Calibri" })] })],
- }),
- new TableCell({
- borders: { top: noBorder, left: noBorder, right: noBorder, bottom: thinBorder },
- width: { size: 6871, type: WidthType.DXA },
- margins: { top: 100, bottom: 100, left: 100, right: 0 },
- children: [new Paragraph({ children: [new TextRun({ text: value, size: 22, color: C_DARK, font: "Calibri" })] })],
- }),
- ],
- })),
- }),
- Spacer(2200),
- new Paragraph({
- border: { top: { style: BorderStyle.SINGLE, size: 4, color: C_BORDER, space: 8 } },
- spacing: { after: 40 },
- children: [new TextRun({ text: "vision4it GmbH \u00b7 Siemensstra\u00dfe 8 \u00b7 41542 Dormagen \u00b7 Tel. 02133 8688890 \u00b7 office@vision4it.de \u00b7 vision4it.de", size: 16, color: C_GREY, font: "Calibri" })],
- }),
- new Paragraph({
- children: [new TextRun({ text: "Gesch\u00e4ftsf\u00fchrer: Matthias Gehlhaar, Phillip Gehlhaar \u00b7 Amtsgericht M\u00f6nchengladbach, HRB 22650 \u00b7 USt-IdNr. DE369304034", size: 16, color: C_GREY, font: "Calibri" })],
- }),
- ],
- },
- // ============ Section 2: Kurzkonzept (hell, mit Fußzeile) ============
- {
- properties: { page: { margin: { top: 1134, bottom: 1134, left: 1134, right: 1134 } } },
- footers: {
- default: new Footer({
- children: [
- new Paragraph({
- border: { top: { style: BorderStyle.SINGLE, size: 4, color: C_BORDER, space: 4 } },
- spacing: { after: 20 },
- children: [new TextRun({ text: "vision4it GmbH \u00b7 Siemensstra\u00dfe 8 \u00b7 41542 Dormagen \u00b7 Tel. 02133 8688890 \u00b7 office@vision4it.de", size: 14, color: C_GREY, font: "Calibri" })],
- }),
- new Paragraph({
- children: [
- new TextRun({ text: "Gesch\u00e4ftsf\u00fchrer: Matthias Gehlhaar, Phillip Gehlhaar \u00b7 Amtsgericht M\u00f6nchengladbach, HRB 22650 \u00b7 USt-IdNr. DE369304034", size: 14, color: C_GREY, font: "Calibri" }),
- new TextRun({ text: "\u2003\u2003Angebot AN-2026-042 \u00b7 Seite ", size: 14, color: C_GREY, font: "Calibri" }),
- new TextRun({ children: [PageNumber.CURRENT], size: 14, color: C_GREY, font: "Calibri" }),
- ],
- }),
- ],
- }),
- },
- children: [
- new Paragraph({
- alignment: AlignmentType.RIGHT,
- spacing: { after: 300 },
- children: [new ImageRun({ data: LOGO, transformation: { width: 124, height: 30 }, type: 'png' })],
- }),
- H1("1. Ausgangslage"),
- P("Die Musterfirma GmbH betreibt derzeit einen einzelnen physischen Server (Windows Server 2016) ohne Virtualisierung, auf dem Domänencontroller, Dateiablage und die Warenwirtschaft gemeinsam laufen. Das Backup erfolgt manuell auf USB-Festplatten, eine Netzwerksegmentierung existiert nicht. Der Support für das Betriebssystem ist ausgelaufen; Ausfälle einzelner Dienste betreffen unmittelbar den gesamten Betrieb."),
- H1("2. Zielsetzung"),
- Bullet("Ablösung der Altumgebung durch eine virtualisierte, wartbare Infrastruktur"),
- Bullet("Trennung der Dienste in eigene virtuelle Maschinen mit definierten Ressourcen"),
- Bullet("Automatisiertes, überwachtes Backup mit täglicher Erfolgskontrolle"),
- Bullet("Segmentiertes Netzwerk mit zentraler Firewall und klarem Regelwerk"),
- Bullet("Proaktives Monitoring und Patch-Management durch vision4it"),
- H1("3. Ziel-Architektur"),
- P("Als Virtualisierungsplattform kommt Proxmox VE auf einem neuen Server-Host zum Einsatz. Die bisherigen Dienste werden in getrennte virtuelle Maschinen aufgeteilt: Domänencontroller, Fileserver und Applikationsserver für die Warenwirtschaft. Am Netzwerkrand ersetzt eine OPNsense-Firewall den vorhandenen Consumer-Router und übernimmt Routing, Regelwerk und VPN-Zugänge. Alle Systeme werden self-hosted beim Kunden betrieben; es entstehen keine Cloud-Abhängigkeiten."),
- H1("4. Netzwerk & Segmentierung"),
- P("Das Netzwerk wird in getrennte VLANs aufgeteilt: Server, Clients, Drucker, Gäste-WLAN und Management. Der Verkehr zwischen den Segmenten läuft ausschließlich über die OPNsense und wird durch ein dokumentiertes Regelwerk kontrolliert. Der Gastzugang bleibt vollständig vom Firmennetz isoliert."),
- H1("5. Backup & Monitoring"),
- P("Die Datensicherung erfolgt über Proxmox Backup Server auf ein dediziertes Sicherungsziel mit täglichen, verschlüsselten Sicherungen und definierter Aufbewahrung. Alle Systeme werden in das Monitoring von vision4it (TacticalRMM) aufgenommen: Patch-Management, Ereignisüberwachung und tägliche Backup-Kontrolle inklusive."),
- H1("6. Nächste Schritte"),
- Bullet("Standortbegehung und Bestandsaufnahme"),
- Bullet("Detail-Design mit Netzplan, Firewall-Regelwerk und IP-Konzept"),
- Bullet("Hardware-Bestellung (Server, Switch, Firewall)"),
- Bullet("Umsetzung mit Parallelbetrieb und geplanter Umschaltung"),
- Bullet("Einweisung der Mitarbeiter und Übergabe in den Regelbetrieb"),
- H1("7. Zeitplan & Aufwand"),
- P("Die Umsetzung gliedert sich in sechs Phasen. Die Schätzung umfasst Aufbau, Konfiguration, Migration und eine Hypercare-Phase nach dem Go-Live."),
- Spacer(100),
- phaseTable(),
- Spacer(160),
- H2("Kalenderzeit"),
- P("Bei einem Techniker mit etwa 70 % Auslastung auf diesem Projekt ergibt sich eine Laufzeit von rund 4 bis 5 Wochen vom Kick-off bis zum Ende der Hypercare-Phase."),
- H2("Nicht enthalten"),
- Bullet("Anfahrtszeiten (separate Abrechnung)"),
- Bullet("Hardware-Lieferzeiten"),
- Bullet("Laufender Wartungsvertrag (separates Angebot)"),
- Bullet("Spätere Erweiterungen"),
- ],
- },
- ],
- });
- Packer.toBuffer(doc).then(buffer => {
- fs.writeFileSync("/home/claude/<kunde>/Kurzkonzept_<Kunde>.docx", buffer);
- console.log("OK");
- });
|