Files
excalidraw-mcp-sentinel/frontend/index.html
T
forkless bb710b46b0
Docker Build (ARM64) / Build & Push (push) Successful in 2m4s
fix: suppress CORS flood from excalidraw.com/og-image-3.png fetch
2026-06-25 00:52:35 +02:00

748 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Excalidraw POC - Backend API Integration</title>
<style>
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
background-color: #f5f5f5;
}
.header {
position: relative;
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
}
.header h1 {
margin: 0;
color: #333;
font-size: 24px;
}
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #545b62;
}
.btn-success {
background-color: #28a745;
color: white;
}
.btn-success:hover {
background-color: #218838;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover {
background-color: #c82333;
}
.status {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.status-connected {
background-color: #28a745;
}
.status-disconnected {
background-color: #dc3545;
}
.canvas-container {
height: calc(100vh - 80px);
width: 100%;
position: relative;
}
/* Widen Excalidraw left properties panel */
.App-menu_left .Island {
min-width: 260px;
}
/* Draggable font size widget */
.custom-font-size-widget {
position: fixed;
z-index: 100;
background: var(--island-bg-color, #232329);
border-radius: 8px;
padding: 6px 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
display: flex;
align-items: center;
gap: 6px;
cursor: grab;
user-select: none;
}
.custom-font-size-widget.dragging {
cursor: grabbing;
opacity: 0.9;
}
.custom-font-size-widget label {
font-size: 12px;
color: #aaa;
white-space: nowrap;
cursor: grab;
}
.custom-font-size-widget input[type="number"] {
width: 52px;
padding: 4px 6px;
font-size: 13px;
border: 1px solid #555;
border-radius: 4px;
background: #1a1a2e;
color: #fff;
text-align: center;
cursor: text;
}
.custom-font-size-widget input[type="number"]:focus {
outline: none;
border-color: #4a6cf7;
}
.custom-font-size-widget button {
padding: 4px 10px;
font-size: 12px;
border: none;
border-radius: 4px;
background: #4a6cf7;
color: #fff;
cursor: pointer;
}
.custom-font-size-widget button:hover {
background: #3a5ce5;
}
.api-panel {
position: fixed;
right: 20px;
top: 100px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
padding: 20px;
width: 300px;
max-height: 400px;
overflow-y: auto;
z-index: 1000;
}
.api-panel h3 {
margin: 0 0 15px 0;
color: #333;
font-size: 18px;
}
.api-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
label {
font-weight: 500;
color: #555;
font-size: 14px;
}
input, select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-row {
display: flex;
gap: 10px;
}
.form-row .form-group {
flex: 1;
}
.toggle-panel {
position: fixed;
right: 20px;
top: 60px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
padding: 10px;
cursor: pointer;
font-size: 14px;
z-index: 1001;
}
.api-panel.hidden {
display: none;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 16px;
border-radius: 4px;
color: white;
font-size: 14px;
z-index: 1002;
animation: slideIn 0.3s ease;
}
.notification.success {
background-color: #28a745;
}
.notification.error {
background-color: #dc3545;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.element-count {
font-size: 14px;
color: #666;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 16px;
color: #666;
}
.loading-content {
text-align: center;
}
.loading-content div:first-child {
margin-bottom: 10px;
}
/* Button group — joined buttons with shared style */
.btn-group {
display: inline-flex;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.btn-group .btn-group-item {
border-radius: 0;
border: none;
padding: 8px 14px;
font-size: 13px;
font-weight: 500;
background: #4caf50;
color: #fff;
transition: background 0.2s;
border-right: 1px solid rgba(255,255,255,0.18);
}
.btn-group .btn-group-item:last-child { border-right: none; }
.btn-group .btn-group-item:hover:not(:disabled) { background: #43a047; }
.btn-group .btn-group-item:disabled { opacity: 0.55; cursor: not-allowed; }
.btn-group .btn-group-item.btn-group-busy { opacity: 0.7; cursor: wait; }
/* Floating toast — centered in the header */
.toast {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: #333;
color: #fff;
font-size: 13px;
font-weight: 500;
padding: 5px 16px;
border-radius: 999px;
pointer-events: none;
white-space: nowrap;
animation: toast-in 0.25s ease, toast-out 0.4s ease 1.6s forwards;
z-index: 10;
}
@keyframes toast-in {
from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes toast-out {
from { opacity: 1; }
to { opacity: 0; }
}
/* Header left group */
.header-left {
display: flex;
align-items: center;
gap: 10px;
}
.header-left h1 { margin: 0; color: #333; font-size: 24px; }
/* Clickable tenant badge — opens workspace switcher */
.tenant-badge-btn {
font-size: 12px;
font-weight: 600;
background: #e9ecef;
color: #495057;
padding: 4px 12px;
border-radius: 999px;
white-space: nowrap;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
border: 1px solid transparent;
transition: background 0.15s, border-color 0.15s;
}
.tenant-badge-btn:hover {
background: #dee2e6;
border-color: #ced4da;
}
.tenant-label {
color: #868e96;
font-weight: 400;
}
/* Menu overlay + panel */
.menu-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.15);
z-index: 2000;
}
.menu-panel {
position: absolute;
top: 56px;
left: 100px;
width: 320px;
max-height: calc(100vh - 80px);
background: #fff;
border-radius: 10px;
box-shadow: 0 8px 30px rgba(0,0,0,0.18);
overflow: hidden;
display: flex;
flex-direction: column;
}
.menu-header {
padding: 14px 16px 10px;
font-size: 13px;
font-weight: 700;
color: #888;
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
justify-content: space-between;
}
.menu-search-wrap {
padding: 8px 10px 4px;
}
.menu-search {
width: 100%;
padding: 7px 10px;
font-size: 13px;
border: 1px solid #ddd;
border-radius: 6px;
outline: none;
box-sizing: border-box;
transition: border-color 0.15s;
}
.menu-search:focus {
border-color: #4caf50;
}
.menu-list {
overflow-y: auto;
max-height: 320px;
padding: 6px;
}
.menu-item {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
padding: 10px 12px;
border: none;
background: none;
border-radius: 6px;
cursor: pointer;
text-align: left;
font-size: 14px;
position: relative;
transition: background 0.15s;
}
.menu-item:hover { background: #f5f5f5; }
.menu-item-active { background: #e8f5e9; }
.menu-item-active:hover { background: #c8e6c9; }
.menu-item-name {
font-weight: 600;
color: #333;
}
.menu-item-path {
font-size: 11px;
color: #999;
margin-top: 2px;
word-break: break-all;
}
.menu-item-check {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: #4caf50;
font-weight: 700;
font-size: 16px;
}
.menu-empty {
padding: 20px;
text-align: center;
color: #aaa;
font-size: 13px;
}
.project-badge-btn {
background: #f3f0ff;
border-color: #e5dbff;
color: #5f3dc4;
}
.project-badge-btn:hover {
background: #e5dbff;
border-color: #d0bfff;
}
.project-menu-panel {
left: 220px;
}
.menu-create-wrap {
display: flex;
gap: 6px;
padding: 8px 10px 10px;
border-top: 1px solid #f0f0f0;
}
.menu-create-wrap .menu-search {
flex: 1;
}
.menu-create-btn {
padding: 7px 12px;
font-size: 13px;
font-weight: 600;
background: #5f3dc4;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s;
}
.menu-create-btn:hover:not(:disabled) { background: #4c2fa8; }
.menu-create-btn:disabled { opacity: 0.5; cursor: default; }
.tenant-row {
position: relative;
display: flex;
align-items: stretch;
}
.tenant-row:hover .project-delete-btn { opacity: 0.5; }
.project-row {
position: relative;
display: flex;
align-items: stretch;
}
.project-menu-item {
flex: 1;
padding-right: 36px;
}
.project-delete-btn {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
font-size: 14px;
opacity: 0;
padding: 4px 6px;
border-radius: 4px;
transition: opacity 0.15s, background 0.15s;
}
.project-row:hover .project-delete-btn { opacity: 0.5; }
.project-delete-btn:hover { opacity: 1 !important; background: #fff0f0; }
.project-delete-confirm {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 12px;
background: #fff5f5;
border-radius: 6px;
width: 100%;
}
.project-delete-msg {
flex: 1;
font-size: 13px;
color: #c92a2a;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.project-delete-yes {
padding: 4px 10px;
font-size: 12px;
font-weight: 600;
background: #e03131;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
.project-delete-yes:hover { background: #c92a2a; }
.project-delete-no {
padding: 4px 10px;
font-size: 12px;
background: #f1f3f5;
color: #495057;
border: none;
border-radius: 4px;
cursor: pointer;
}
.project-delete-no:hover { background: #dee2e6; }
/* Batch selection mode */
.batch-mode-toggle {
font-size: 11px;
font-weight: 600;
padding: 3px 10px;
border: 1px solid #ddd;
border-radius: 4px;
background: #f8f9fa;
color: #555;
cursor: pointer;
text-transform: none;
letter-spacing: 0;
transition: background 0.15s, border-color 0.15s;
}
.batch-mode-toggle:hover { background: #e9ecef; border-color: #ccc; }
.batch-mode-active { background: #e8f5e9; border-color: #a5d6a7; color: #2e7d32; }
.batch-mode-active:hover { background: #c8e6c9; }
.batch-actions-bar {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-bottom: 1px solid #f0f0f0;
background: #fafafa;
}
.batch-action-btn {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border: 1px solid #ddd;
border-radius: 4px;
background: #fff;
color: #555;
cursor: pointer;
transition: background 0.15s;
}
.batch-action-btn:hover { background: #e9ecef; }
.batch-count {
font-size: 11px;
color: #888;
margin-left: auto;
}
.batch-item {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
gap: 10px;
}
.batch-item-disabled {
opacity: 0.5;
cursor: default;
}
.batch-checkbox {
width: 16px;
height: 16px;
flex-shrink: 0;
accent-color: #e03131;
cursor: pointer;
}
.batch-item-disabled .batch-checkbox { cursor: default; }
.batch-item-content {
display: flex;
flex-direction: column;
min-width: 0;
}
.batch-active-label {
font-size: 10px;
font-weight: 600;
color: #4caf50;
text-transform: uppercase;
margin-left: auto;
flex-shrink: 0;
}
.batch-delete-bar {
padding: 8px 12px;
border-top: 1px solid #f0f0f0;
background: #fff5f5;
}
.batch-delete-btn {
width: 100%;
padding: 8px;
font-size: 13px;
font-weight: 600;
background: #e03131;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.15s;
}
.batch-delete-btn:hover { background: #c92a2a; }
.batch-delete-confirm {
display: flex;
align-items: center;
gap: 8px;
}
.batch-delete-msg {
flex: 1;
font-size: 13px;
color: #c92a2a;
font-weight: 500;
}
/* Clear canvas confirmation dialog */
.confirm-dialog {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 360px;
background: #fff;
border-radius: 12px;
box-shadow: 0 12px 40px rgba(0,0,0,0.22);
padding: 24px;
}
.confirm-title {
font-size: 18px;
font-weight: 700;
color: #333;
margin-bottom: 8px;
}
.confirm-msg {
font-size: 14px;
color: #666;
margin: 0 0 16px;
line-height: 1.4;
}
.confirm-checkbox-label {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #555;
cursor: pointer;
margin-bottom: 20px;
font-weight: 400;
}
.confirm-checkbox-label input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
}
.confirm-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>
<!-- CORS flood suppressor: intercepts @excalidraw/excalidraw internal fetch to
https://excalidraw.com/og-image-3.png which triggers 6× CORS errors on
non-excalidraw.com origins. Returns 204 immediately, never leaves browser. -->
<script>
window.fetch = new Proxy(window.fetch, {
apply(target, thisArg, args) {
var url = args[0];
if (typeof url === 'string' && url.indexOf('og-image-3.png') !== -1) {
return Promise.resolve(new Response(null, { status: 204 }));
}
return Reflect.apply(target, thisArg, args);
}
});
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/main.tsx"></script>
</body>
</html>