Grotti 2.2.0: Auth, API-Tokens, MCP und Doku-Trennung.
No files matched your search
@@ -1,4 +1,5 @@
|
||||
const TOKEN = document.querySelector('meta[name="token"]')?.content || "";
|
||||
const TOKEN = ""; // Legacy: Auth über Session-Cookie oder Bearer von außen
|
||||
const CSRF = document.querySelector('meta[name="csrf"]')?.content || "";
|
||||
|
||||
const STATE_LABELS = {
|
||||
printing: "druckt", paused: "pausiert", complete: "fertig", standby: "bereit",
|
||||
@@ -17,17 +18,29 @@ function escapeHtml(value) {
|
||||
async function api(path, options = {}) {
|
||||
const headers = new Headers(options.headers || {});
|
||||
if (TOKEN) headers.set("Authorization", "Bearer " + TOKEN);
|
||||
const method = (options.method || "GET").toUpperCase();
|
||||
if (CSRF && method !== "GET" && method !== "HEAD") {
|
||||
headers.set("X-CSRF-Token", CSRF);
|
||||
}
|
||||
let body = options.body;
|
||||
if (options.json !== undefined) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
body = JSON.stringify(options.json);
|
||||
}
|
||||
const response = await fetch("/api/v1" + path, {
|
||||
method: options.method || "GET",
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
cache: "no-store",
|
||||
credentials: "same-origin",
|
||||
});
|
||||
if (response.status === 401) {
|
||||
const here = location.pathname + location.search;
|
||||
if (!here.startsWith("/login") && !here.startsWith("/setup")) {
|
||||
location.href = "/login?next=" + encodeURIComponent(here);
|
||||
}
|
||||
throw new Error("Anmeldung nötig.");
|
||||
}
|
||||
if (!response.ok) {
|
||||
let message = response.statusText;
|
||||
try {
|
||||
@@ -45,8 +58,8 @@ async function api(path, options = {}) {
|
||||
}
|
||||
|
||||
function withToken(url) {
|
||||
if (!url || !TOKEN) return url;
|
||||
return url + (url.includes("?") ? "&" : "?") + "token=" + encodeURIComponent(TOKEN);
|
||||
// Session-Cookie reicht für same-origin Kameras; kein Token in der URL.
|
||||
return url;
|
||||
}
|
||||
|
||||
function euro(value) {
|
||||
@@ -103,6 +116,13 @@ function chipHtml(state, label, small) {
|
||||
return `<span class="chip ${escapeHtml(state || "")}${small ? " sm" : ""}">${escapeHtml(text)}</span>`;
|
||||
}
|
||||
|
||||
function shownState(printer) {
|
||||
const state = (printer && printer.state) || "offline";
|
||||
const hot = ((printer && printer.nozzle_target) || 0) > 40 || ((printer && printer.bed_target) || 0) > 30;
|
||||
if ((state === "standby" || state === "complete") && hot) return "heating";
|
||||
return state;
|
||||
}
|
||||
|
||||
function stepsHtml(labels, index, compact) {
|
||||
if (!labels || !labels.length) return "";
|
||||
const parts = labels.map((label, i) => {
|
||||
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 403 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 670 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 110 KiB |
@@ -111,20 +111,21 @@ summary:focus:not(:focus-visible) { outline: none; }
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.brand-mark {
|
||||
flex: none;
|
||||
width: 1.55rem;
|
||||
height: 1.55rem;
|
||||
border-radius: 99px;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.brand-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
||||
.nav-toggle {
|
||||
flex: none;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
min-width: 2.5rem;
|
||||
}
|
||||
.brand::before {
|
||||
content: "";
|
||||
width: 0.65rem;
|
||||
height: 0.65rem;
|
||||
border-radius: 99px;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 0 4px var(--accent-soft);
|
||||
}
|
||||
.nav-main, .nav-secondary { display: flex; flex-direction: column; gap: 0.1rem; }
|
||||
.nav-main a, .nav-secondary a {
|
||||
color: var(--muted);
|
||||
@@ -183,10 +184,14 @@ html.nav-collapsed .brand,
|
||||
body.nav-collapsed .brand {
|
||||
padding: 0.2rem;
|
||||
justify-content: center;
|
||||
font-size: 0;
|
||||
}
|
||||
html.nav-collapsed .brand::before,
|
||||
body.nav-collapsed .brand::before { margin: 0; }
|
||||
html.nav-collapsed .brand-name,
|
||||
body.nav-collapsed .brand-name { display: none; }
|
||||
html.nav-collapsed .brand-mark,
|
||||
body.nav-collapsed .brand-mark {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
}
|
||||
html.nav-collapsed .brand-row,
|
||||
body.nav-collapsed .brand-row { flex-direction: column; padding-bottom: 0.35rem; }
|
||||
html.nav-collapsed .nav-toggle,
|
||||
@@ -803,6 +808,11 @@ article.printer {
|
||||
font-weight: 650;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.camera-tile .cam-stage {
|
||||
position: relative;
|
||||
min-height: 140px;
|
||||
background: var(--card-2);
|
||||
}
|
||||
.camera-tile img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -810,16 +820,22 @@ article.printer {
|
||||
object-fit: contain;
|
||||
background: var(--card-2);
|
||||
}
|
||||
.camera-tile img.broken { min-height: 120px; opacity: 0.35; }
|
||||
.cam-url {
|
||||
display: block;
|
||||
padding: 0.4rem 0.75rem 0.6rem;
|
||||
font-size: 0.72rem;
|
||||
color: var(--faint);
|
||||
word-break: break-all;
|
||||
text-decoration: none;
|
||||
.camera-tile .cam-empty {
|
||||
margin: 0;
|
||||
min-height: 140px;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
justify-items: center;
|
||||
gap: 0.35rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.camera-tile .cam-empty .ico { width: 1.6rem; height: 1.6rem; opacity: 0.55; }
|
||||
.camera-tile .cam-open {
|
||||
display: inline-block;
|
||||
margin: 0.45rem 0.75rem 0.65rem;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
.cam-url:hover { color: var(--accent); }
|
||||
|
||||
.card {
|
||||
background: var(--card);
|
||||
@@ -937,6 +953,60 @@ tr.pick:hover td { background: rgba(255, 255, 255, 0.04); }
|
||||
tr.on td { background: var(--accent-soft); }
|
||||
td select { width: auto; min-width: 8.5rem; padding: 0.3rem 0.45rem; min-height: 1.9rem; border-radius: 7px; }
|
||||
.thumb { width: 64px; height: 64px; object-fit: contain; background: var(--card-2); border-radius: 12px; border: 1px solid var(--line); }
|
||||
.auth-body {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 1.5rem;
|
||||
background:
|
||||
radial-gradient(ellipse at 20% 0%, rgba(61, 206, 124, 0.12), transparent 45%),
|
||||
var(--bg);
|
||||
}
|
||||
.auth-card {
|
||||
width: min(22rem, 100%);
|
||||
background: var(--card);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 1.4rem 1.35rem 1.5rem;
|
||||
}
|
||||
.auth-card h1 { margin: 0 0 0.35rem; font-size: 1.45rem; }
|
||||
.auth-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
.auth-brand img {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 99px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.auth-quote {
|
||||
margin: 0.55rem 0 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.auth-quote blockquote {
|
||||
margin: 0;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.45;
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
.auth-quote figcaption {
|
||||
margin-top: 0.35rem;
|
||||
font-size: 0.7rem;
|
||||
color: var(--faint);
|
||||
font-style: normal;
|
||||
}
|
||||
.auth-card .stack { display: grid; gap: 0.75rem; margin-top: 1rem; }
|
||||
.side-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
.empty { color: var(--muted); padding: 1rem 0.3rem; }
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
@@ -2092,6 +2162,17 @@ label.button:hover { background: rgba(255, 255, 255, 0.04); }
|
||||
.prose table { margin: 0.8rem 0; font-size: var(--t-sm); }
|
||||
.prose blockquote { margin: 0.8rem 0; padding: 0.4rem 0.9rem; border-left: 3px solid var(--accent); background: var(--accent-soft); border-radius: 0 10px 10px 0; }
|
||||
.prose .lede { font-size: 1.05rem; color: var(--ink); }
|
||||
.prose img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin: 1rem 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: var(--card-2);
|
||||
}
|
||||
.prose .doc-chapters { columns: 2; column-gap: 1.5rem; }
|
||||
.prose .doc-chapters li { break-inside: avoid; }
|
||||
.flow-diagram { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; margin: 0.8rem 0 1rem; }
|
||||
.flow-diagram .step { font-size: var(--t-xs); padding: 0.3rem 0.65rem; }
|
||||
.terms { display: grid; grid-template-columns: max-content 1fr; gap: 0.45rem 1rem; margin: 0.6rem 0; }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Skipped minification because the original files appears to be already minified.
|
||||
* Original file: /npm/[email protected]/build/qrcode.js
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
var QRCode=function(t){"use strict";var r,e=function(){return"function"==typeof Promise&&Promise.prototype&&Promise.prototype.then},n=[0,26,44,70,100,134,172,196,242,292,346,404,466,532,581,655,733,815,901,991,1085,1156,1258,1364,1474,1588,1706,1828,1921,2051,2185,2323,2465,2611,2761,2876,3034,3196,3362,3532,3706],o=function(t){if(!t)throw new Error('"version" cannot be null or undefined');if(t<1||t>40)throw new Error('"version" should be in range from 1 to 40');return 4*t+17},a=function(t){return n[t]},i=function(t){for(var r=0;0!==t;)r++,t>>>=1;return r},u=function(t){if("function"!=typeof t)throw new Error('"toSJISFunc" is not a valid function.');r=t},s=function(){return void 0!==r},f=function(t){return r(t)};function h(t,r){return t(r={exports:{}},r.exports),r.exports}var c=h((function(t,r){r.L={bit:1},r.M={bit:0},r.Q={bit:3},r.H={bit:2},r.isValid=function(t){return t&&void 0!==t.bit&&t.bit>=0&&t.bit<4},r.from=function(t,e){if(r.isValid(t))return t;try{return function(t){if("string"!=typeof t)throw new Error("Param is not a string");switch(t.toLowerCase()){case"l":case"low":return r.L;case"m":case"medium":return r.M;case"q":case"quartile":return r.Q;case"h":case"high":return r.H;default:throw new Error("Unknown EC Level: "+t)}}(t)}catch(t){return e}}}));function g(){this.buffer=[],this.length=0}c.L,c.M,c.Q,c.H,c.isValid,g.prototype={get:function(t){var r=Math.floor(t/8);return 1==(this.buffer[r]>>>7-t%8&1)},put:function(t,r){for(var e=0;e<r;e++)this.putBit(1==(t>>>r-e-1&1))},getLengthInBits:function(){return this.length},putBit:function(t){var r=Math.floor(this.length/8);this.buffer.length<=r&&this.buffer.push(0),t&&(this.buffer[r]|=128>>>this.length%8),this.length++}};var d=g;function l(t){if(!t||t<1)throw new Error("BitMatrix size must be defined and greater than 0");this.size=t,this.data=new Uint8Array(t*t),this.reservedBit=new Uint8Array(t*t)}l.prototype.set=function(t,r,e,n){var o=t*this.size+r;this.data[o]=e,n&&(this.reservedBit[o]=!0)},l.prototype.get=function(t,r){return this.data[t*this.size+r]},l.prototype.xor=function(t,r,e){this.data[t*this.size+r]^=e},l.prototype.isReserved=function(t,r){return this.reservedBit[t*this.size+r]};var v=l,p=h((function(t,r){var e=o;r.getRowColCoords=function(t){if(1===t)return[];for(var r=Math.floor(t/7)+2,n=e(t),o=145===n?26:2*Math.ceil((n-13)/(2*r-2)),a=[n-7],i=1;i<r-1;i++)a[i]=a[i-1]-o;return a.push(6),a.reverse()},r.getPositions=function(t){for(var e=[],n=r.getRowColCoords(t),o=n.length,a=0;a<o;a++)for(var i=0;i<o;i++)0===a&&0===i||0===a&&i===o-1||a===o-1&&0===i||e.push([n[a],n[i]]);return e}}));p.getRowColCoords,p.getPositions;var w=o,m=function(t){var r=w(t);return[[0,0],[r-7,0],[0,r-7]]},E=h((function(t,r){r.Patterns={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var e=3,n=3,o=40,a=10;function i(t,e,n){switch(t){case r.Patterns.PATTERN000:return(e+n)%2==0;case r.Patterns.PATTERN001:return e%2==0;case r.Patterns.PATTERN010:return n%3==0;case r.Patterns.PATTERN011:return(e+n)%3==0;case r.Patterns.PATTERN100:return(Math.floor(e/2)+Math.floor(n/3))%2==0;case r.Patterns.PATTERN101:return e*n%2+e*n%3==0;case r.Patterns.PATTERN110:return(e*n%2+e*n%3)%2==0;case r.Patterns.PATTERN111:return(e*n%3+(e+n)%2)%2==0;default:throw new Error("bad maskPattern:"+t)}}r.isValid=function(t){return null!=t&&""!==t&&!isNaN(t)&&t>=0&&t<=7},r.from=function(t){return r.isValid(t)?parseInt(t,10):void 0},r.getPenaltyN1=function(t){for(var r=t.size,n=0,o=0,a=0,i=null,u=null,s=0;s<r;s++){o=a=0,i=u=null;for(var f=0;f<r;f++){var h=t.get(s,f);h===i?o++:(o>=5&&(n+=e+(o-5)),i=h,o=1),(h=t.get(f,s))===u?a++:(a>=5&&(n+=e+(a-5)),u=h,a=1)}o>=5&&(n+=e+(o-5)),a>=5&&(n+=e+(a-5))}return n},r.getPenaltyN2=function(t){for(var r=t.size,e=0,o=0;o<r-1;o++)for(var a=0;a<r-1;a++){var i=t.get(o,a)+t.get(o,a+1)+t.get(o+1,a)+t.get(o+1,a+1);4!==i&&0!==i||e++}return e*n},r.getPenaltyN3=function(t){for(var r=t.size,e=0,n=0,a=0,i=0;i<r;i++){n=a=0;for(var u=0;u<r;u++)n=n<<1&2047|t.get(i,u),u>=10&&(1488===n||93===n)&&e++,a=a<<1&2047|t.get(u,i),u>=10&&(1488===a||93===a)&&e++}return e*o},r.getPenaltyN4=function(t){for(var r=0,e=t.data.length,n=0;n<e;n++)r+=t.data[n];return Math.abs(Math.ceil(100*r/e/5)-10)*a},r.applyMask=function(t,r){for(var e=r.size,n=0;n<e;n++)for(var o=0;o<e;o++)r.isReserved(o,n)||r.xor(o,n,i(t,o,n))},r.getBestMask=function(t,e){for(var n=Object.keys(r.Patterns).length,o=0,a=1/0,i=0;i<n;i++){e(i),r.applyMask(i,t);var u=r.getPenaltyN1(t)+r.getPenaltyN2(t)+r.getPenaltyN3(t)+r.getPenaltyN4(t);r.applyMask(i,t),u<a&&(a=u,o=i)}return o}}));E.Patterns,E.isValid,E.getPenaltyN1,E.getPenaltyN2,E.getPenaltyN3,E.getPenaltyN4,E.applyMask,E.getBestMask;var y=[1,1,1,1,1,1,1,1,1,1,2,2,1,2,2,4,1,2,4,4,2,4,4,4,2,4,6,5,2,4,6,6,2,5,8,8,4,5,8,8,4,5,8,11,4,8,10,11,4,9,12,16,4,9,16,16,6,10,12,18,6,10,17,16,6,11,16,19,6,13,18,21,7,14,21,25,8,16,20,25,8,17,23,25,9,17,23,34,9,18,25,30,10,20,27,32,12,21,29,35,12,23,34,37,12,25,34,40,13,26,35,42,14,28,38,45,15,29,Line truncated
|
||||