@unbrained/pm-web 2026.6.14 → 2026.7.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -12
- package/README.md +1 -1
- package/dist/app.d.ts +12 -0
- package/dist/app.js +103 -0
- package/dist/app.js.map +1 -0
- package/dist/auth.d.ts +8 -0
- package/dist/auth.js +6 -1
- package/dist/auth.js.map +1 -1
- package/dist/board.d.ts +15 -0
- package/dist/crypto.d.ts +2 -0
- package/dist/db.d.ts +11 -0
- package/dist/ical.d.ts +24 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +18 -4
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.d.ts +8 -0
- package/dist/routes/admin.d.ts +2 -0
- package/dist/routes/auth.d.ts +2 -0
- package/dist/routes/github.d.ts +2 -0
- package/dist/routes/groups.d.ts +2 -0
- package/dist/routes/pm.d.ts +2 -0
- package/dist/routes/projects.d.ts +16 -0
- package/dist/routes/route-params.d.ts +2 -0
- package/dist/routes/sharing.d.ts +3 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +2 -58
- package/dist/server.js.map +1 -1
- package/dist/services/pm-runner.d.ts +25 -0
- package/dist/services/sse.d.ts +28 -0
- package/manifest.json +1 -1
- package/package.json +5 -5
- package/public/cookie-consent.css +50 -0
- package/public/cookie-consent.js +43 -0
- package/public/cookie-settings.html +24 -0
- package/public/index.html +4 -4
- package/public/legal-common.css +150 -0
- package/public/legal-notice.html +35 -0
- package/public/privacy-policy.html +51 -0
- package/public/styles.css +42 -42
- package/public/terms.html +27 -0
package/dist/server.js
CHANGED
|
@@ -1,64 +1,8 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import cookieParser from "cookie-parser";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { readFileSync } from "node:fs";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
1
|
import { initSchema, assertDbConfigured } from "./db.js";
|
|
7
|
-
import {
|
|
8
|
-
import { projectsRouter } from "./routes/projects.js";
|
|
9
|
-
import { pmRouter } from "./routes/pm.js";
|
|
10
|
-
import { groupsRouter } from "./routes/groups.js";
|
|
11
|
-
import { sharesRouter, sharedWithMeRouter } from "./routes/sharing.js";
|
|
12
|
-
import { githubRouter } from "./routes/github.js";
|
|
13
|
-
import { adminRouter } from "./routes/admin.js";
|
|
2
|
+
import { createApp } from "./app.js";
|
|
14
3
|
import { cleanupStaleClients } from "./services/sse.js";
|
|
15
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
4
|
const PORT = parseInt(process.env.PORT || "4000", 10);
|
|
17
|
-
const
|
|
18
|
-
const app = express();
|
|
19
|
-
app.use(express.json({ limit: "1mb" }));
|
|
20
|
-
app.use(cookieParser());
|
|
21
|
-
app.get("/sw.js", (_req, res) => {
|
|
22
|
-
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
|
|
23
|
-
res.setHeader("Pragma", "no-cache");
|
|
24
|
-
res.setHeader("Expires", "0");
|
|
25
|
-
res.sendFile(path.join(PUBLIC_DIR, "sw.js"));
|
|
26
|
-
});
|
|
27
|
-
app.use(express.static(PUBLIC_DIR, {
|
|
28
|
-
maxAge: process.env.NODE_ENV === "production" ? "1h" : 0,
|
|
29
|
-
}));
|
|
30
|
-
// Security headers
|
|
31
|
-
app.use((_req, res, next) => {
|
|
32
|
-
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
33
|
-
res.setHeader("X-Frame-Options", "SAMEORIGIN");
|
|
34
|
-
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
35
|
-
next();
|
|
36
|
-
});
|
|
37
|
-
// Health check — includes the running pm-web version so `pm web status` can
|
|
38
|
-
// report it. Version is resolved once at boot from package.json (best-effort).
|
|
39
|
-
const PM_WEB_VERSION = (() => {
|
|
40
|
-
try {
|
|
41
|
-
const pkg = JSON.parse(readFileSync(path.resolve(__dirname, "..", "package.json"), "utf8"));
|
|
42
|
-
return pkg.version ?? "unknown";
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return "unknown";
|
|
46
|
-
}
|
|
47
|
-
})();
|
|
48
|
-
app.get("/healthz", (_req, res) => res.json({ ok: true, version: PM_WEB_VERSION }));
|
|
49
|
-
// API routes
|
|
50
|
-
app.use("/api/auth", authRouter);
|
|
51
|
-
app.use("/api/projects", projectsRouter);
|
|
52
|
-
app.use("/api/projects/:projectId/pm", pmRouter);
|
|
53
|
-
app.use("/api/groups", groupsRouter);
|
|
54
|
-
app.use("/api/projects/:id/shares", sharesRouter);
|
|
55
|
-
app.use("/api/shared", sharedWithMeRouter);
|
|
56
|
-
app.use("/api/projects/:id/github", githubRouter);
|
|
57
|
-
app.use("/api/admin", adminRouter);
|
|
58
|
-
// SPA fallback — serve index.html for all non-API routes
|
|
59
|
-
app.get("/{*splat}", (_req, res) => {
|
|
60
|
-
res.sendFile(path.join(PUBLIC_DIR, "index.html"));
|
|
61
|
-
});
|
|
5
|
+
const app = createApp();
|
|
62
6
|
// Validate configuration before doing anything that needs the database, so a
|
|
63
7
|
// missing DATABASE_URL fails fast with a clear message instead of hanging on a
|
|
64
8
|
// DNS/connection timeout.
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;AAEtD,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;AAExB,6EAA6E;AAC7E,+EAA+E;AAC/E,0BAA0B;AAC1B,IAAI,CAAC;IACH,kBAAkB,EAAE,CAAC;AACvB,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,oCAAoC;AACpC,UAAU,EAAE;KACT,IAAI,CAAC,GAAG,EAAE;IACT,wEAAwE;IACxE,mEAAmE;IACnE,qEAAqE;IACrE,mCAAmC;IACnC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE;QAC9C,IAAI,GAAG,EAAE,CAAC;YACR,OAAO,CAAC,KAAK,CAAC,mBAAmB,IAAI,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;QAChC,OAAO,CAAC,KAAK,CAAC,oBAAoB,IAAI,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IACH,wCAAwC;IACxC,WAAW,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;AAClD,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACb,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACjG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare function getProjectDir(userId: string, slug: string): string;
|
|
2
|
+
export declare function initProject(userId: string, slug: string, prefix: string): void;
|
|
3
|
+
export declare function projectExists(userId: string, slug: string): boolean;
|
|
4
|
+
export interface PmRunOptions {
|
|
5
|
+
args: string[];
|
|
6
|
+
userId: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
input?: string;
|
|
9
|
+
jsonOutput?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface PmRunResult {
|
|
12
|
+
stdout: string;
|
|
13
|
+
stderr: string;
|
|
14
|
+
ok: boolean;
|
|
15
|
+
parsed?: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface EnsureGraphExtensionResult {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
installed: boolean;
|
|
20
|
+
active: boolean;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function ensureGraphExtension(userId: string, slug: string): EnsureGraphExtensionResult;
|
|
24
|
+
export declare function runPm(opts: PmRunOptions): PmRunResult;
|
|
25
|
+
export declare function deleteProjectDir(userId: string, slug: string): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Response } from "express";
|
|
2
|
+
export interface SSEClient {
|
|
3
|
+
id: string;
|
|
4
|
+
projectId: string;
|
|
5
|
+
userId: string;
|
|
6
|
+
displayName: string;
|
|
7
|
+
currentView: string;
|
|
8
|
+
res: Response;
|
|
9
|
+
connectedAt: Date;
|
|
10
|
+
}
|
|
11
|
+
export interface PresenceUser {
|
|
12
|
+
userId: string;
|
|
13
|
+
displayName: string;
|
|
14
|
+
currentView: string;
|
|
15
|
+
connectedAt: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function addSSEClient(client: SSEClient): () => void;
|
|
18
|
+
export declare function broadcastProjectEvent(projectId: string, event: SSEEvent): void;
|
|
19
|
+
export declare function broadcastPresence(projectId: string): void;
|
|
20
|
+
export declare function updateClientView(clientId: string, currentView: string): void;
|
|
21
|
+
export declare function getProjectPresence(projectId: string): PresenceUser[];
|
|
22
|
+
export declare function setupSSEHeaders(res: Response): void;
|
|
23
|
+
export declare function getSSEClientCount(): number;
|
|
24
|
+
export declare function cleanupStaleClients(): void;
|
|
25
|
+
export interface SSEEvent {
|
|
26
|
+
type: string;
|
|
27
|
+
data: unknown;
|
|
28
|
+
}
|
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pm-web",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.7.11",
|
|
4
4
|
"description": "Full web UI for pm-cli — browse, create, update, search and manage pm projects in the browser. Self-hosted via Docker or Node.js.",
|
|
5
5
|
"author": "@unbraind",
|
|
6
6
|
"entry": "./dist/index.js",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unbrained/pm-web",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.7.11",
|
|
4
4
|
"description": "Full web UI for pm-cli — browse, create, update, search and manage pm projects in the browser. Self-hosted via Docker or Node.js.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -46,15 +46,15 @@
|
|
|
46
46
|
"uuid": "^14.0.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@types/bcryptjs": "^
|
|
49
|
+
"@types/bcryptjs": "^3.0.0",
|
|
50
50
|
"@types/cookie-parser": "^1.4.8",
|
|
51
51
|
"@types/express": "^5.0.6",
|
|
52
52
|
"@types/jsonwebtoken": "^9.0.9",
|
|
53
53
|
"@types/node": "^25.9.2",
|
|
54
54
|
"@types/pg": "^8.11.11",
|
|
55
55
|
"@types/uuid": "^10.0.0",
|
|
56
|
-
"@unbrained/pm-cli": "^2026.
|
|
57
|
-
"pm-changelog": "^2026.6.
|
|
56
|
+
"@unbrained/pm-cli": "^2026.7.5",
|
|
57
|
+
"pm-changelog": "^2026.6.30",
|
|
58
58
|
"tsx": "^4.22.4",
|
|
59
59
|
"typescript": "^6.0.3"
|
|
60
60
|
},
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"main": "dist/index.js",
|
|
70
70
|
"types": "dist/index.d.ts",
|
|
71
71
|
"engines": {
|
|
72
|
-
"node": ">=
|
|
72
|
+
"node": ">=22.18.0"
|
|
73
73
|
},
|
|
74
74
|
"author": "@unbraind",
|
|
75
75
|
"license": "MIT",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* Cookie consent banner — shared by the SPA (index.html) and legal pages.
|
|
2
|
+
Legal pages get these rules via legal-common.css; keep both in sync. */
|
|
3
|
+
|
|
4
|
+
.cookie-consent {
|
|
5
|
+
position: fixed;
|
|
6
|
+
left: 24px;
|
|
7
|
+
right: 24px;
|
|
8
|
+
bottom: 24px;
|
|
9
|
+
z-index: 3000;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: space-between;
|
|
13
|
+
gap: 18px;
|
|
14
|
+
max-width: 1040px;
|
|
15
|
+
margin: 0 auto;
|
|
16
|
+
padding: 16px;
|
|
17
|
+
border: 1px solid var(--border);
|
|
18
|
+
border-radius: var(--radius);
|
|
19
|
+
background: rgba(15, 23, 42, 0.96);
|
|
20
|
+
box-shadow: 0 12px 48px rgba(0,0,0,0.35);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.cookie-consent[hidden] {
|
|
24
|
+
display: none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.cookie-consent-text {
|
|
28
|
+
display: grid;
|
|
29
|
+
gap: 4px;
|
|
30
|
+
color: var(--text-secondary);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.cookie-consent-text strong {
|
|
34
|
+
color: var(--text-primary);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.cookie-consent-actions {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: 10px;
|
|
41
|
+
flex-wrap: wrap;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.cookie-consent {
|
|
45
|
+
left: 12px;
|
|
46
|
+
right: 12px;
|
|
47
|
+
bottom: 12px;
|
|
48
|
+
align-items: stretch;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
var storageKey = "pm_cookie_preferences_v1";
|
|
3
|
+
var banner = document.getElementById("cookie-consent");
|
|
4
|
+
var links = document.querySelectorAll("[data-cookie-settings]");
|
|
5
|
+
|
|
6
|
+
function save(choice) {
|
|
7
|
+
try {
|
|
8
|
+
localStorage.setItem(storageKey, JSON.stringify({ necessary: true, optional: false, choice: choice, savedAt: new Date().toISOString() }));
|
|
9
|
+
} catch (_) {
|
|
10
|
+
/* ignore unavailable storage */
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function hasChoice() {
|
|
15
|
+
try {
|
|
16
|
+
return Boolean(localStorage.getItem(storageKey));
|
|
17
|
+
} catch (_) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function show(event) {
|
|
23
|
+
if (event) event.preventDefault();
|
|
24
|
+
if (banner) banner.hidden = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hide() {
|
|
28
|
+
if (banner) banner.hidden = true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
links.forEach(function (link) {
|
|
32
|
+
link.addEventListener("click", show);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
document.querySelectorAll("[data-cookie-accept], [data-cookie-decline]").forEach(function (button) {
|
|
36
|
+
button.addEventListener("click", function () {
|
|
37
|
+
save(button.hasAttribute("data-cookie-accept") ? "acknowledged" : "necessary");
|
|
38
|
+
hide();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!hasChoice()) show();
|
|
43
|
+
})();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="de" data-theme="auto">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="description" content="Cookie- und Speicherinformationen fuer pm-web.">
|
|
7
|
+
<title>Cookie Settings - pm-web</title>
|
|
8
|
+
<link rel="stylesheet" href="/styles.css?v=20">
|
|
9
|
+
<link rel="stylesheet" href="/legal-common.css?v=1">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="legal-shell">
|
|
12
|
+
<nav class="legal-nav"><a class="legal-brand" href="/">pm-web</a><div class="legal-links"><a href="/legal-notice">Legal Notice</a><a href="/privacy-policy">Privacy Policy</a><a href="/terms">Terms</a></div></nav>
|
|
13
|
+
<main class="legal-main">
|
|
14
|
+
<p class="legal-kicker">Legal</p>
|
|
15
|
+
<h1>Cookie Settings</h1>
|
|
16
|
+
<p class="legal-updated">Stand: 26. Juni 2026</p>
|
|
17
|
+
<section class="legal-section"><h2>Aktuelle Einstellung</h2><p>pm-web verwendet keine optionalen Tracking- oder Marketing-Cookies. Technisch notwendige Speicherung kann fuer Anmeldung, Sicherheit, Darstellung und Consent-Status erforderlich sein.</p><button type="button" class="btn btn-primary" data-cookie-settings>Open Cookie Settings</button></section>
|
|
18
|
+
<section class="legal-section"><h2>Notwendige Speicherung</h2><p><code>pm_token</code> speichert die angemeldete Sitzung als HTTP-only Cookie. Lokale Einstellungen wie Theme, PWA-Installationshinweise und Cookie-Status werden im Browser gespeichert.</p></section>
|
|
19
|
+
<section class="legal-section"><h2>Optionale Cookies</h2><p>Derzeit werden keine optionalen Cookies gesetzt. Sollten Analyse- oder Marketingdienste spaeter aktiviert werden, werden sie erst nach ausdruecklicher Einwilligung geladen.</p></section>
|
|
20
|
+
</main>
|
|
21
|
+
<div class="cookie-consent" id="cookie-consent" hidden><div class="cookie-consent-text"><strong>Cookie Settings</strong><span>We only use technically necessary storage for sign-in, security, and preferences. No optional tracking cookies.</span></div><div class="cookie-consent-actions"><a href="/privacy-policy">Privacy Policy</a><button type="button" class="btn btn-secondary btn-sm" data-cookie-decline>Necessary only</button><button type="button" class="btn btn-primary btn-sm" data-cookie-accept>Got it</button></div></div>
|
|
22
|
+
<script src="/cookie-consent.js?v=1"></script>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|
package/public/index.html
CHANGED
|
@@ -13,10 +13,8 @@
|
|
|
13
13
|
<link rel="manifest" href="/manifest.json">
|
|
14
14
|
<link rel="apple-touch-icon" href="/icons/icon-192.png">
|
|
15
15
|
<link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512.png">
|
|
16
|
-
<link rel="
|
|
17
|
-
<link rel="
|
|
18
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
19
|
-
<link rel="stylesheet" href="/styles.css?v=19">
|
|
16
|
+
<link rel="stylesheet" href="/styles.css?v=20">
|
|
17
|
+
<link rel="stylesheet" href="/cookie-consent.css?v=1">
|
|
20
18
|
</head>
|
|
21
19
|
<body>
|
|
22
20
|
<a href="#main-content" class="skip-link">Skip to main content</a>
|
|
@@ -262,5 +260,7 @@
|
|
|
262
260
|
</nav>
|
|
263
261
|
|
|
264
262
|
<script type="module" src="/src/app.js?v=9"></script>
|
|
263
|
+
<div class="cookie-consent" id="cookie-consent" hidden><div class="cookie-consent-text"><strong>Cookie Settings</strong><span>We only use technically necessary storage for sign-in, security, and preferences. No optional tracking cookies.</span></div><div class="cookie-consent-actions"><a href="/privacy-policy">Privacy Policy</a><button type="button" class="btn btn-secondary btn-sm" data-cookie-decline>Necessary only</button><button type="button" class="btn btn-primary btn-sm" data-cookie-accept>Got it</button></div></div>
|
|
264
|
+
<script src="/cookie-consent.js?v=1"></script>
|
|
265
265
|
</body>
|
|
266
266
|
</html>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
.legal-shell {
|
|
2
|
+
min-height: 100vh;
|
|
3
|
+
background: var(--bg-base);
|
|
4
|
+
color: var(--text-primary);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.legal-nav {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: space-between;
|
|
11
|
+
gap: 16px;
|
|
12
|
+
padding: 18px 28px;
|
|
13
|
+
border-bottom: 1px solid var(--border);
|
|
14
|
+
background: var(--bg-sidebar);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.legal-brand {
|
|
18
|
+
color: var(--accent);
|
|
19
|
+
font-family: 'SFMono-Regular', Monaco, 'Cascadia Code', 'Liberation Mono', monospace;
|
|
20
|
+
font-size: 18px;
|
|
21
|
+
font-weight: 600;
|
|
22
|
+
text-decoration: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.legal-links {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 14px;
|
|
29
|
+
flex-wrap: wrap;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.legal-links a {
|
|
33
|
+
color: var(--text-secondary);
|
|
34
|
+
text-decoration: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.legal-links a:hover {
|
|
38
|
+
color: var(--accent);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.legal-main {
|
|
42
|
+
max-width: 880px;
|
|
43
|
+
margin: 0 auto;
|
|
44
|
+
padding: 64px 24px 96px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.legal-kicker,
|
|
48
|
+
.legal-updated {
|
|
49
|
+
color: var(--text-secondary);
|
|
50
|
+
font-family: 'SFMono-Regular', Monaco, 'Cascadia Code', 'Liberation Mono', monospace;
|
|
51
|
+
font-size: 12px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.legal-kicker {
|
|
55
|
+
color: var(--accent);
|
|
56
|
+
margin-bottom: 10px;
|
|
57
|
+
text-transform: uppercase;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.legal-main h1 {
|
|
61
|
+
font-size: clamp(32px, 7vw, 56px);
|
|
62
|
+
line-height: 1.05;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.legal-updated {
|
|
66
|
+
margin: 14px 0 36px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.legal-section {
|
|
70
|
+
border-top: 1px solid var(--border);
|
|
71
|
+
padding: 28px 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.legal-section h2 {
|
|
75
|
+
font-size: 18px;
|
|
76
|
+
margin-bottom: 12px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.legal-section p,
|
|
80
|
+
.legal-section li {
|
|
81
|
+
color: var(--text-secondary);
|
|
82
|
+
margin-bottom: 12px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.legal-section code {
|
|
86
|
+
color: var(--accent);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.cookie-consent {
|
|
90
|
+
position: fixed;
|
|
91
|
+
left: 24px;
|
|
92
|
+
right: 24px;
|
|
93
|
+
bottom: 24px;
|
|
94
|
+
z-index: 3000;
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
justify-content: space-between;
|
|
98
|
+
gap: 18px;
|
|
99
|
+
max-width: 1040px;
|
|
100
|
+
margin: 0 auto;
|
|
101
|
+
padding: 16px;
|
|
102
|
+
border: 1px solid var(--border);
|
|
103
|
+
border-radius: var(--radius);
|
|
104
|
+
background: rgba(15, 23, 42, 0.96);
|
|
105
|
+
box-shadow: 0 12px 48px rgba(0,0,0,0.35);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.cookie-consent[hidden] {
|
|
109
|
+
display: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.cookie-consent-text {
|
|
113
|
+
display: grid;
|
|
114
|
+
gap: 4px;
|
|
115
|
+
color: var(--text-secondary);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.cookie-consent-text strong {
|
|
119
|
+
color: var(--text-primary);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.cookie-consent-actions {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 10px;
|
|
126
|
+
flex-wrap: wrap;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.legal-footer {
|
|
130
|
+
display: flex;
|
|
131
|
+
gap: 12px;
|
|
132
|
+
flex-wrap: wrap;
|
|
133
|
+
padding-top: 24px;
|
|
134
|
+
border-top: 1px solid var(--border);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@media (max-width: 720px) {
|
|
138
|
+
.legal-nav {
|
|
139
|
+
align-items: flex-start;
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.cookie-consent {
|
|
144
|
+
left: 12px;
|
|
145
|
+
right: 12px;
|
|
146
|
+
bottom: 12px;
|
|
147
|
+
align-items: stretch;
|
|
148
|
+
flex-direction: column;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="de" data-theme="auto">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="description" content="Legal notice and provider information for pm-web.">
|
|
7
|
+
<title>Legal Notice - pm-web</title>
|
|
8
|
+
<link rel="stylesheet" href="/styles.css?v=20">
|
|
9
|
+
<link rel="stylesheet" href="/legal-common.css?v=1">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="legal-shell">
|
|
12
|
+
<nav class="legal-nav"><a class="legal-brand" href="/">pm-web</a><div class="legal-links"><a href="/privacy-policy">Privacy Policy</a><a href="/terms">Terms</a><a href="/cookie-settings">Cookies</a><a href="https://pm-cli.unbrained.dev/legal-notice">pm-cli Legal Notice</a></div></nav>
|
|
13
|
+
<main class="legal-main">
|
|
14
|
+
<p class="legal-kicker">Legal</p>
|
|
15
|
+
<h1>Legal Notice</h1>
|
|
16
|
+
<p class="legal-updated">Stand: 26. Juni 2026</p>
|
|
17
|
+
<section class="legal-section">
|
|
18
|
+
<h2>Angaben gemaess ECG und MedienG</h2>
|
|
19
|
+
<p><strong>Stefan Preu</strong><br>Steinbruch 5<br>3250 Wieselburg<br>Oesterreich</p>
|
|
20
|
+
<p>E-Mail: <a href="mailto:stefan@preu.at">stefan@preu.at</a></p>
|
|
21
|
+
<p>Verantwortlich fuer den Inhalt dieser Website: Stefan Preu.</p>
|
|
22
|
+
</section>
|
|
23
|
+
<section class="legal-section">
|
|
24
|
+
<h2>Online-Angebot</h2>
|
|
25
|
+
<p>Dieses Impressum gilt fuer <a href="https://pm-web.unbrained.dev/">pm-web.unbrained.dev</a>. pm-web ist eine webbasierte Oberflaeche fuer pm-cli.</p>
|
|
26
|
+
</section>
|
|
27
|
+
<section class="legal-section">
|
|
28
|
+
<h2>Haftung und externe Links</h2>
|
|
29
|
+
<p>Die Inhalte werden sorgfaeltig gepflegt. Fuer externe Links wird keine Verantwortung fuer fremde Inhalte uebernommen; fuer diese sind ausschliesslich deren Betreiber verantwortlich.</p>
|
|
30
|
+
</section>
|
|
31
|
+
</main>
|
|
32
|
+
<div class="cookie-consent" id="cookie-consent" hidden><div class="cookie-consent-text"><strong>Cookie Settings</strong><span>We only use technically necessary storage for sign-in, security, and preferences. No optional tracking cookies.</span></div><div class="cookie-consent-actions"><a href="/privacy-policy">Privacy Policy</a><button type="button" class="btn btn-secondary btn-sm" data-cookie-decline>Necessary only</button><button type="button" class="btn btn-primary btn-sm" data-cookie-accept>Got it</button></div></div>
|
|
33
|
+
<script src="/cookie-consent.js?v=1"></script>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="de" data-theme="auto">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="description" content="Privacy policy for pm-web.">
|
|
7
|
+
<title>Privacy Policy - pm-web</title>
|
|
8
|
+
<link rel="stylesheet" href="/styles.css?v=20">
|
|
9
|
+
<link rel="stylesheet" href="/legal-common.css?v=1">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="legal-shell">
|
|
12
|
+
<nav class="legal-nav"><a class="legal-brand" href="/">pm-web</a><div class="legal-links"><a href="/legal-notice">Legal Notice</a><a href="/terms">Terms</a><a href="/cookie-settings">Cookies</a><a href="https://pm-cli.unbrained.dev/privacy-policy">pm-cli Privacy Policy</a></div></nav>
|
|
13
|
+
<main class="legal-main">
|
|
14
|
+
<p class="legal-kicker">Legal</p>
|
|
15
|
+
<h1>Privacy Policy</h1>
|
|
16
|
+
<p class="legal-updated">Stand: 26. Juni 2026</p>
|
|
17
|
+
<section class="legal-section">
|
|
18
|
+
<h2>Verantwortlicher</h2>
|
|
19
|
+
<p>Stefan Preu, Steinbruch 5, 3250 Wieselburg, Oesterreich, <a href="mailto:stefan@preu.at">stefan@preu.at</a>.</p>
|
|
20
|
+
</section>
|
|
21
|
+
<section class="legal-section">
|
|
22
|
+
<h2>Verarbeitete Daten</h2>
|
|
23
|
+
<p>Beim Besuch werden technisch notwendige Serverdaten verarbeitet, insbesondere IP-Adresse, Zeitpunkt, angefragte URL, HTTP-Status, User-Agent und Referrer.</p>
|
|
24
|
+
<p>Bei Registrierung und Nutzung verarbeitet pm-web Konto- und Nutzungsdaten: E-Mail-Adresse, Anzeigename, Passwort-Hash, Projekt- und Gruppendaten, Freigaben, optionale GitHub-Verknuepfungsdaten sowie technische Sitzungsdaten.</p>
|
|
25
|
+
</section>
|
|
26
|
+
<section class="legal-section">
|
|
27
|
+
<h2>Zwecke und Rechtsgrundlagen</h2>
|
|
28
|
+
<p>Die Verarbeitung erfolgt zur Bereitstellung der Anwendung, Authentifizierung, Projektverwaltung, Zusammenarbeit, Sicherheit, Missbrauchsschutz und Fehleranalyse. Rechtsgrundlagen sind Art. 6 Abs. 1 lit. b, lit. f und, soweit einschlaegig, lit. c DSGVO.</p>
|
|
29
|
+
</section>
|
|
30
|
+
<section class="legal-section">
|
|
31
|
+
<h2>Cookies und lokale Speicherung</h2>
|
|
32
|
+
<p>pm-web verwendet ein technisch notwendiges HTTP-only Authentifizierungs-Cookie (<code>pm_token</code>). Lokale Browser-Speicherung kann fuer Theme, Installationshinweise und Cookie-Status genutzt werden. Es werden keine optionalen Tracking- oder Marketing-Cookies gesetzt.</p>
|
|
33
|
+
</section>
|
|
34
|
+
<section class="legal-section">
|
|
35
|
+
<h2>Drittanbieter</h2>
|
|
36
|
+
<p>Schriftarten werden lokal beziehungsweise ueber Systemschriftarten dargestellt. GitHub-Funktionen werden nur genutzt, wenn Nutzer diese aktiv verwenden oder entsprechende Links oeffnen.</p>
|
|
37
|
+
</section>
|
|
38
|
+
<section class="legal-section">
|
|
39
|
+
<h2>Speicherdauer</h2>
|
|
40
|
+
<p>Konto- und Projektdaten bleiben gespeichert, bis sie geloescht werden oder gesetzliche Pflichten entgegenstehen. Server- und Sicherheitslogs werden nur so lange gespeichert, wie sie fuer Betrieb, Sicherheit und Fehleranalyse erforderlich sind.</p>
|
|
41
|
+
</section>
|
|
42
|
+
<section class="legal-section">
|
|
43
|
+
<h2>Betroffenenrechte</h2>
|
|
44
|
+
<p>Betroffene Personen haben Rechte auf Auskunft, Berichtigung, Loeschung, Einschraenkung, Datenuebertragbarkeit, Widerspruch und Beschwerde bei einer Aufsichtsbehoerde. Anfragen bitte an <a href="mailto:stefan@preu.at">stefan@preu.at</a>.</p>
|
|
45
|
+
<p>Zustaendige oesterreichische Aufsichtsbehoerde: Datenschutzbehoerde, Barichgasse 40-42, 1030 Wien, <a href="https://www.dsb.gv.at/" target="_blank" rel="noopener">dsb.gv.at</a>.</p>
|
|
46
|
+
</section>
|
|
47
|
+
</main>
|
|
48
|
+
<div class="cookie-consent" id="cookie-consent" hidden><div class="cookie-consent-text"><strong>Cookie Settings</strong><span>We only use technically necessary storage for sign-in, security, and preferences. No optional tracking cookies.</span></div><div class="cookie-consent-actions"><a href="/privacy-policy">Privacy Policy</a><button type="button" class="btn btn-secondary btn-sm" data-cookie-decline>Necessary only</button><button type="button" class="btn btn-primary btn-sm" data-cookie-accept>Got it</button></div></div>
|
|
49
|
+
<script src="/cookie-consent.js?v=1"></script>
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|