Implemented admin pane + user permission

This commit is contained in:
Martino Ferrari
2026-06-22 17:49:14 +02:00
parent 73fcbe7b28
commit ac24011487
67 changed files with 5925 additions and 411 deletions
+30 -3
View File
@@ -15,6 +15,30 @@ interface Props {
onToggleCollapse: () => void;
}
// Monochrome (currentColor) panel-kind glyph. Inline SVG avoids font-dependent
// emoji that may render in colour or as a missing-glyph box.
function KindIcon({ kind }: { kind?: 'panel' | 'plot' }) {
const plot = kind === 'plot';
return (
<span class="iface-item-icon" title={plot ? 'Plot panel' : 'HMI panel'}>
{plot ? (
<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M2.5 2.5 v11 h11" />
<path d="M4.5 11 L7 7.5 L9 9.5 L13 4.5" />
</svg>
) : (
<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="2" y="2.5" width="12" height="11" rx="1.5" />
<path d="M4.5 6 h7" />
<circle cx="6" cy="6" r="1.2" fill="currentColor" stroke="none" />
<path d="M4.5 10 h7" />
<circle cx="10" cy="10" r="1.2" fill="currentColor" stroke="none" />
</svg>
)}
</span>
);
}
export default function InterfaceList({ onLoad, onSelect, onEdit, onNewPlot, onEditId, width, collapsed, onToggleCollapse }: Props) {
const [interfaces, setInterfaces] = useState<InterfaceListItem[]>([]);
const [folders, setFolders] = useState<Folder[]>([]);
@@ -87,6 +111,7 @@ export default function InterfaceList({ onLoad, onSelect, onEdit, onNewPlot, onE
id: String(item.id || item.ID || ''),
name: String(item.name || item.Name || ''),
version: Number(item.version || item.Version || 0),
kind: item.kind === 'plot' ? ('plot' as const) : undefined,
owner: item.owner ? String(item.owner) : '',
folder: item.folder ? String(item.folder) : '',
order: Number(item.order || 0),
@@ -171,19 +196,21 @@ export default function InterfaceList({ onLoad, onSelect, onEdit, onNewPlot, onE
return (
<li
key={item.id}
class={`iface-item${dropTarget === `p:${item.id}` ? ' drop-target' : ''}`}
class={`iface-item iface-item-clickable${dropTarget === `p:${item.id}` ? ' drop-target' : ''}`}
draggable={drag}
onClick={() => onSelect?.(item.id)}
onDragStart={drag ? (e) => { dragId.current = item.id; e.dataTransfer!.effectAllowed = 'move'; } : undefined}
onDragEnd={endDrag}
onDragOver={(e) => { if (dragId.current) { e.preventDefault(); e.stopPropagation(); setDropTarget(`p:${item.id}`); } }}
onDragLeave={() => setDropTarget(t => t === `p:${item.id}` ? null : t)}
onDrop={(e) => dropOnPanel(item, e)}
>
<span class="iface-item-name" onClick={() => onSelect?.(item.id)} title={item.owner ? `Owner: ${item.owner}` : undefined}>
<span class="iface-item-name" title={item.owner ? `Owner: ${item.owner}` : undefined}>
<KindIcon kind={item.kind} />
{item.name || item.id}
{item.perm === 'read' && <span class="iface-badge" title="Read-only">ro</span>}
</span>
<div class="iface-item-actions">
<div class="iface-item-actions" onClick={(e) => e.stopPropagation()}>
{item.perm === 'write' && <button class="icon-btn" title="Edit" onClick={() => onEditId?.(item.id)}></button>}
<button class="icon-btn" title="Open fullscreen in new tab" onClick={() => handleFullscreen(item.id)}></button>
{writable && <button class="icon-btn" title="Clone" onClick={() => handleClone(item.id)}></button>}