Version 0.0.1 #2
@@ -863,7 +863,9 @@ impl core_api::plugin::Plugin for HonchoPlugin {
|
|||||||
icon: "gear",
|
icon: "gear",
|
||||||
entry: "web/config.js".into(),
|
entry: "web/config.js".into(),
|
||||||
admin_only: true,
|
admin_only: true,
|
||||||
priority: 10,
|
// Sidebar priority: core "Your space" items live in 10–90, so
|
||||||
|
// plugin pages use ≥100 to land after them (see sidebar.js NAV).
|
||||||
|
priority: 120,
|
||||||
},
|
},
|
||||||
PluginPage {
|
PluginPage {
|
||||||
page_id: "memory",
|
page_id: "memory",
|
||||||
@@ -871,7 +873,7 @@ impl core_api::plugin::Plugin for HonchoPlugin {
|
|||||||
icon: "stars",
|
icon: "stars",
|
||||||
entry: "web/memory.js".into(),
|
entry: "web/memory.js".into(),
|
||||||
admin_only: false,
|
admin_only: false,
|
||||||
priority: 10,
|
priority: 130,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,7 +321,9 @@ impl Plugin for MobileConnectorPlugin {
|
|||||||
icon: "qr-code",
|
icon: "qr-code",
|
||||||
entry: "web/pairing.js".into(),
|
entry: "web/pairing.js".into(),
|
||||||
admin_only: true,
|
admin_only: true,
|
||||||
priority: 10,
|
// Sidebar priority: core "Your space" items live in 10–90, so
|
||||||
|
// plugin pages use ≥100 to land after them (see sidebar.js NAV).
|
||||||
|
priority: 100,
|
||||||
},
|
},
|
||||||
PluginPage {
|
PluginPage {
|
||||||
page_id: "devices",
|
page_id: "devices",
|
||||||
@@ -329,7 +331,7 @@ impl Plugin for MobileConnectorPlugin {
|
|||||||
icon: "phone",
|
icon: "phone",
|
||||||
entry: "web/devices.js".into(),
|
entry: "web/devices.js".into(),
|
||||||
admin_only: true,
|
admin_only: true,
|
||||||
priority: 20,
|
priority: 110,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+185
-138
@@ -3,6 +3,66 @@ import { LightElement } from '../lib/base.js';
|
|||||||
import { t, I18nMixin } from '../lib/i18n.js';
|
import { t, I18nMixin } from '../lib/i18n.js';
|
||||||
|
|
||||||
|
|
||||||
|
// ── Navigation model ──────────────────────────────────────────────────────────
|
||||||
|
// The nav is data, not markup: every entry declares its `group` and a numeric
|
||||||
|
// `priority` (lower = higher up), and each group is rendered by sorting its
|
||||||
|
// entries on that key. Retuning the order is editing a number here, never moving
|
||||||
|
// JSX around. Plugin-contributed pages (`GET /api/plugins/pages`) carry their own
|
||||||
|
// `priority` (see `PluginPage`) and merge into the `workspace` group on the *same*
|
||||||
|
// number line — the convention is core items live in 10–90 and plugin pages ≥100,
|
||||||
|
// so a page lands after the daily items by default yet stays freely placeable.
|
||||||
|
//
|
||||||
|
// The split axis is **function**, not permission: `adminOnly`/`debugOnly` gate
|
||||||
|
// individual entries, and a whole section disappears when it has no visible entry
|
||||||
|
// for this user (so "Configuration" — all admin entries — vanishes for non-admins
|
||||||
|
// without a section-level role check). `aliases` lists extra `_activePage` values
|
||||||
|
// that should light the entry (e.g. the detail route paired with its list).
|
||||||
|
const NAV = [
|
||||||
|
// Il tuo spazio — daily productivity. Visible to everyone (full mode).
|
||||||
|
{ id: 'home', group: 'workspace', priority: 10, icon: 'chat-dots', labelKey: 'nav.chat' },
|
||||||
|
{ id: 'inbox', group: 'workspace', priority: 20, icon: 'inbox', labelKey: 'nav.inbox' },
|
||||||
|
{ id: 'dashboard', group: 'workspace', priority: 30, icon: 'speedometer2', labelKey: 'nav.dashboard' },
|
||||||
|
{ id: 'projects', group: 'workspace', priority: 40, icon: 'kanban', labelKey: 'nav.projects' },
|
||||||
|
{ id: 'tasks', group: 'workspace', priority: 50, icon: 'lightning-charge',labelKey: 'nav.tasks' },
|
||||||
|
// Shared folders is admin-managed but *content*, so it lives with the daily
|
||||||
|
// items, not buried in Configuration — the link stays admin-gated per-entry.
|
||||||
|
{ id: 'shared-folders', group: 'workspace', priority: 60, icon: 'folder-symlink', labelKey: 'nav.shared_folders', adminOnly: true },
|
||||||
|
|
||||||
|
// Estensioni — what the assistant is made of / can use. Visible to everyone;
|
||||||
|
// Agents is read-only for non-admins (editable only by the admin server-side).
|
||||||
|
{ id: 'connectors', group: 'extensions', priority: 10, icon: 'plug', labelKey: 'nav.connectors', aliases: ['connector'] },
|
||||||
|
{ id: 'plugins', group: 'extensions', priority: 20, icon: 'puzzle', labelKey: 'nav.plugins' },
|
||||||
|
{ id: 'agents', group: 'extensions', priority: 30, icon: 'people', labelKey: 'nav.agents' },
|
||||||
|
|
||||||
|
// Configurazione — rarely-touched setup. Every entry is admin-only today, so
|
||||||
|
// the section is admin-only in effect via the empty-section rule.
|
||||||
|
{ id: 'users', group: 'config', priority: 10, icon: 'person-badge', labelKey: 'nav.users', adminOnly: true },
|
||||||
|
{ id: 'roles', group: 'config', priority: 20, icon: 'tags', labelKey: 'nav.roles', adminOnly: true },
|
||||||
|
{ id: 'models', group: 'config', priority: 30, icon: 'cpu', labelKey: 'nav.models', adminOnly: true },
|
||||||
|
{ id: 'providers', group: 'config', priority: 40, icon: 'plug', labelKey: 'nav.providers', adminOnly: true },
|
||||||
|
{ id: 'approval', group: 'config', priority: 50, icon: 'shield-check', labelKey: 'nav.security', adminOnly: true },
|
||||||
|
{ id: 'plugin-catalog', group: 'config', priority: 70, icon: 'puzzle-fill', labelKey: 'nav.plugin_catalog', adminOnly: true, aliases: ['plugin-detail'] },
|
||||||
|
{ id: 'catalog', group: 'config', priority: 80, icon: 'journal-text', labelKey: 'nav.catalog', adminOnly: true, aliases: ['marketplace'] },
|
||||||
|
{ id: 'config', group: 'config', priority: 90, icon: 'gear', labelKey: 'nav.config', adminOnly: true },
|
||||||
|
|
||||||
|
// Sviluppo — debug surface, only with the debug flag on.
|
||||||
|
{ id: 'llm-requests', group: 'dev', priority: 10, icon: 'journal-code', labelKey: 'nav.llm_requests', debugOnly: true },
|
||||||
|
{ id: 'tic', group: 'dev', priority: 20, icon: 'bell', labelKey: 'nav.tic', debugOnly: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Section order + which sections collapse. Configuration and Development are
|
||||||
|
// collapsible and closed by default (rarely touched); the two productivity
|
||||||
|
// sections are always open.
|
||||||
|
const GROUPS = [
|
||||||
|
{ id: 'workspace', labelKey: 'nav.section.workspace', collapsible: false },
|
||||||
|
{ id: 'extensions', labelKey: 'nav.section.extensions', collapsible: false },
|
||||||
|
{ id: 'config', labelKey: 'nav.section.config', collapsible: true },
|
||||||
|
{ id: 'dev', labelKey: 'nav.section.dev', collapsible: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
const COLLAPSE_KEY = 'sidebar-collapsed';
|
||||||
|
|
||||||
|
|
||||||
export class AppSidebar extends I18nMixin(LightElement) {
|
export class AppSidebar extends I18nMixin(LightElement) {
|
||||||
static properties = {
|
static properties = {
|
||||||
_activePage: { state: true },
|
_activePage: { state: true },
|
||||||
@@ -12,6 +72,7 @@ export class AppSidebar extends I18nMixin(LightElement) {
|
|||||||
_recentProjects: { state: true },
|
_recentProjects: { state: true },
|
||||||
_me: { state: true },
|
_me: { state: true },
|
||||||
_pluginPages: { state: true },
|
_pluginPages: { state: true },
|
||||||
|
_collapsed: { state: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -24,6 +85,7 @@ export class AppSidebar extends I18nMixin(LightElement) {
|
|||||||
this._recentProjects = [];
|
this._recentProjects = [];
|
||||||
this._me = null;
|
this._me = null;
|
||||||
this._pluginPages = [];
|
this._pluginPages = [];
|
||||||
|
this._collapsed = { config: true, dev: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@@ -53,6 +115,7 @@ export class AppSidebar extends I18nMixin(LightElement) {
|
|||||||
// Poll inbox count independently of whether the page is open.
|
// Poll inbox count independently of whether the page is open.
|
||||||
this._pollInbox();
|
this._pollInbox();
|
||||||
this._pollTimer = setInterval(() => this._pollInbox(), 10000);
|
this._pollTimer = setInterval(() => this._pollInbox(), 10000);
|
||||||
|
this._loadCollapsed();
|
||||||
this._loadDebugMode();
|
this._loadDebugMode();
|
||||||
this._loadRecentProjects();
|
this._loadRecentProjects();
|
||||||
this._loadMe();
|
this._loadMe();
|
||||||
@@ -76,6 +139,20 @@ export class AppSidebar extends I18nMixin(LightElement) {
|
|||||||
clearInterval(this._pollTimer);
|
clearInterval(this._pollTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persisted per-section collapse state. Defaults (config + dev closed) apply
|
||||||
|
// until the user toggles a section, then their choice is remembered.
|
||||||
|
_loadCollapsed() {
|
||||||
|
try {
|
||||||
|
const saved = JSON.parse(localStorage.getItem(COLLAPSE_KEY) || '{}');
|
||||||
|
this._collapsed = { config: true, dev: true, ...saved };
|
||||||
|
} catch { /* keep defaults */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggleSection(id) {
|
||||||
|
this._collapsed = { ...this._collapsed, [id]: !this._collapsed[id] };
|
||||||
|
try { localStorage.setItem(COLLAPSE_KEY, JSON.stringify(this._collapsed)); } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
async _loadDebugMode() {
|
async _loadDebugMode() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/dev/debug_mode');
|
const res = await fetch('/api/dev/debug_mode');
|
||||||
@@ -197,6 +274,107 @@ export class AppSidebar extends I18nMixin(LightElement) {
|
|||||||
this._applyPage('tasks');
|
this._applyPage('tasks');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Entry-level helpers ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_itemVisible(item) {
|
||||||
|
if (item.adminOnly && this._me?.role_id !== 'admin') return false;
|
||||||
|
if (item.debugOnly && !this._debugMode) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isActive(item) {
|
||||||
|
if (this._activePage === item.id) return true;
|
||||||
|
return (item.aliases || []).includes(this._activePage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core entries of a group plus (workspace only) the plugin pages, merged on the
|
||||||
|
// shared `priority` line and sorted ascending (lower = higher up).
|
||||||
|
_entriesForGroup(groupId) {
|
||||||
|
const core = NAV
|
||||||
|
.filter((i) => i.group === groupId && this._itemVisible(i))
|
||||||
|
.map((i) => ({ kind: 'core', priority: i.priority, item: i }));
|
||||||
|
const plugins = groupId === 'workspace'
|
||||||
|
? this._pluginPages.map((p) => ({ kind: 'plugin', priority: p.priority ?? 100, page: p }))
|
||||||
|
: [];
|
||||||
|
return [...core, ...plugins].sort((a, b) => a.priority - b.priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Render helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_renderGroup(group) {
|
||||||
|
const entries = this._entriesForGroup(group.id);
|
||||||
|
if (!entries.length) return nothing; // empty section → hidden
|
||||||
|
const collapsed = group.collapsible && this._collapsed[group.id];
|
||||||
|
const header = group.collapsible
|
||||||
|
? html`
|
||||||
|
<button class="sidebar-section-label is-collapsible"
|
||||||
|
@click=${() => this._toggleSection(group.id)}>
|
||||||
|
<span>${t(group.labelKey)}</span>
|
||||||
|
<i class="bi bi-chevron-${collapsed ? 'down' : 'up'}"></i>
|
||||||
|
</button>`
|
||||||
|
: html`<div class="sidebar-section-label"><span>${t(group.labelKey)}</span></div>`;
|
||||||
|
return html`
|
||||||
|
<div class="sidebar-section">
|
||||||
|
${header}
|
||||||
|
${collapsed ? nothing : entries.map((e) => this._renderEntry(e))}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderEntry(entry) {
|
||||||
|
if (entry.kind === 'plugin') return this._renderPluginEntry(entry.page);
|
||||||
|
const item = entry.item;
|
||||||
|
switch (item.id) {
|
||||||
|
case 'home': return this._renderHome();
|
||||||
|
case 'inbox': return this._renderInbox(item);
|
||||||
|
case 'tasks': return this._renderTasksMenu();
|
||||||
|
case 'projects': return html`${this._renderStdLink(item)}${this._renderRecentProjects()}`;
|
||||||
|
default: return this._renderStdLink(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderStdLink(item) {
|
||||||
|
return html`
|
||||||
|
<a href="#${item.id}" class="sidebar-link ${this._isActive(item) ? 'active' : ''}"
|
||||||
|
@click=${(e) => this._togglePage(item.id, e)}>
|
||||||
|
<i class="bi bi-${item.icon}"></i>
|
||||||
|
<span class="sidebar-link-name">${t(item.labelKey)}</span>
|
||||||
|
</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderHome() {
|
||||||
|
return html`
|
||||||
|
<a href="#" class="sidebar-link ${this._activePage === 'home' ? 'active' : ''}"
|
||||||
|
@click=${(e) => this._togglePage('home', e)}>
|
||||||
|
<i class="bi bi-chat-dots"></i>
|
||||||
|
<span class="sidebar-link-name">${t('nav.chat')}</span>
|
||||||
|
</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderInbox(item) {
|
||||||
|
return html`
|
||||||
|
<a href="#inbox" class="sidebar-link ${this._isActive(item) ? 'active' : ''}"
|
||||||
|
@click=${(e) => this._togglePage('inbox', e)}>
|
||||||
|
<i class="bi bi-inbox"></i>
|
||||||
|
<span class="sidebar-link-name">
|
||||||
|
${t('nav.inbox')}
|
||||||
|
${this._inboxCount > 0
|
||||||
|
? html`<span class="badge bg-danger ms-1" style="font-size:0.65rem">${this._inboxCount}</span>`
|
||||||
|
: ''}
|
||||||
|
</span>
|
||||||
|
</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderPluginEntry(p) {
|
||||||
|
const route = `plugin/${p.plugin_id}/${p.page_id}`;
|
||||||
|
return html`
|
||||||
|
<a href="#${route}"
|
||||||
|
class="sidebar-link ${this._activePage === route ? 'active' : ''}"
|
||||||
|
@click=${(e) => this._togglePage(route, e)}>
|
||||||
|
<i class="bi bi-${p.icon}"></i>
|
||||||
|
<span class="sidebar-link-name">${p.title}</span>
|
||||||
|
</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
_renderTasksMenu() {
|
_renderTasksMenu() {
|
||||||
const active = this._activePage === 'tasks';
|
const active = this._activePage === 'tasks';
|
||||||
const sec = this._tasksSection;
|
const sec = this._tasksSection;
|
||||||
@@ -235,23 +413,6 @@ export class AppSidebar extends I18nMixin(LightElement) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPluginPages() {
|
|
||||||
if (!this._pluginPages.length) return nothing;
|
|
||||||
return html`
|
|
||||||
<hr class="sidebar-divider" />
|
|
||||||
${this._pluginPages.map(p => {
|
|
||||||
const route = `plugin/${p.plugin_id}/${p.page_id}`;
|
|
||||||
return html`
|
|
||||||
<a href="#${route}"
|
|
||||||
class="sidebar-link ${this._activePage === route ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage(route, e)}>
|
|
||||||
<i class="bi bi-${p.icon}"></i>
|
|
||||||
<span class="sidebar-link-name">${p.title}</span>
|
|
||||||
</a>`;
|
|
||||||
})}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderRecentProjects() {
|
_renderRecentProjects() {
|
||||||
if (!this._recentProjects.length) return nothing;
|
if (!this._recentProjects.length) return nothing;
|
||||||
return html`
|
return html`
|
||||||
@@ -273,9 +434,10 @@ export class AppSidebar extends I18nMixin(LightElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// Simplified interface (role attrs `ui_mode: "simple"`): chat + inbox only.
|
// Simplified interface (role attrs `ui_mode: "simple"`): chat + inbox only,
|
||||||
// Hiding links is not access control — every route stays capability-gated
|
// ungrouped. Hiding links is not access control — every route stays
|
||||||
// server-side; this only shapes the navigation for less technical members.
|
// capability-gated server-side; this only shapes the nav for less technical
|
||||||
|
// members.
|
||||||
const simple = this._me?.ui_mode === 'simple';
|
const simple = this._me?.ui_mode === 'simple';
|
||||||
return html`
|
return html`
|
||||||
<div class="sidebar-brand">
|
<div class="sidebar-brand">
|
||||||
@@ -286,125 +448,10 @@ export class AppSidebar extends I18nMixin(LightElement) {
|
|||||||
<hr class="sidebar-divider" />
|
<hr class="sidebar-divider" />
|
||||||
|
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav">
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'home' ? 'active' : ''}"
|
${simple
|
||||||
@click=${(e) => this._togglePage('home', e)}>
|
? html`${this._renderHome()}${this._renderInbox({ id: 'inbox' })}`
|
||||||
<i class="bi bi-chat-dots"></i>
|
: GROUPS.map((g) => this._renderGroup(g))}
|
||||||
<span class="sidebar-link-name">${t('nav.chat')}</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="#inbox" class="sidebar-link ${this._activePage === 'inbox' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('inbox', e)}>
|
|
||||||
<i class="bi bi-inbox"></i>
|
|
||||||
<span class="sidebar-link-name">
|
|
||||||
${t('nav.inbox')}
|
|
||||||
${this._inboxCount > 0
|
|
||||||
? html`<span class="badge bg-danger ms-1" style="font-size:0.65rem">${this._inboxCount}</span>`
|
|
||||||
: ''}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
${simple ? nothing : html`
|
|
||||||
<a href="#dashboard"
|
|
||||||
class="sidebar-link ${this._activePage === 'dashboard' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('dashboard', e)}>
|
|
||||||
<i class="bi bi-speedometer2"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.dashboard')}</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="#projects"
|
|
||||||
class="sidebar-link ${this._activePage === 'projects' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('projects', e)}>
|
|
||||||
<i class="bi bi-kanban"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.projects')}</span>
|
|
||||||
</a>
|
|
||||||
${this._renderRecentProjects()}
|
|
||||||
|
|
||||||
${this._renderTasksMenu()}
|
|
||||||
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'models' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('models', e)}>
|
|
||||||
<i class="bi bi-cpu"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.models')}</span>
|
|
||||||
</a>
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'providers' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('providers', e)}>
|
|
||||||
<i class="bi bi-plug"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.providers')}</span>
|
|
||||||
</a>
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'approval' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('approval', e)}>
|
|
||||||
<i class="bi bi-shield-check"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.security')}</span>
|
|
||||||
</a>
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'agents' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('agents', e)}>
|
|
||||||
<i class="bi bi-people"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.agents')}</span>
|
|
||||||
</a>
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'users' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('users', e)}>
|
|
||||||
<i class="bi bi-person-badge"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.users')}</span>
|
|
||||||
</a>
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'roles' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('roles', e)}>
|
|
||||||
<i class="bi bi-tags"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.roles')}</span>
|
|
||||||
</a>
|
|
||||||
${this._me?.role_id === 'admin' ? html`
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'shared-folders' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('shared-folders', e)}>
|
|
||||||
<i class="bi bi-folder-symlink"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.shared_folders')}</span>
|
|
||||||
</a>` : nothing}
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'connectors' || this._activePage === 'connector' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('connectors', e)}>
|
|
||||||
<i class="bi bi-plug"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.connectors')}</span>
|
|
||||||
</a>
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'plugins' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('plugins', e)}>
|
|
||||||
<i class="bi bi-puzzle"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.plugins')}</span>
|
|
||||||
</a>
|
|
||||||
${this._me?.role_id === 'admin' ? html`
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'plugin-catalog' || this._activePage === 'plugin-detail' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('plugin-catalog', e)}>
|
|
||||||
<i class="bi bi-puzzle-fill"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.plugin_catalog')}</span>
|
|
||||||
</a>` : nothing}
|
|
||||||
${this._me?.role_id === 'admin' ? html`
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'catalog' || this._activePage === 'marketplace' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('catalog', e)}>
|
|
||||||
<i class="bi bi-journal-text"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.catalog')}</span>
|
|
||||||
</a>` : nothing}
|
|
||||||
<a href="#" class="sidebar-link ${this._activePage === 'config' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('config', e)}>
|
|
||||||
<i class="bi bi-gear"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.config')}</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
${this._renderPluginPages()}
|
|
||||||
|
|
||||||
${this._debugMode ? html`
|
|
||||||
<hr class="sidebar-divider" />
|
|
||||||
<a href="#llm-requests"
|
|
||||||
class="sidebar-link ${this._activePage === 'llm-requests' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('llm-requests', e)}>
|
|
||||||
<i class="bi bi-journal-code"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.llm_requests')}</span>
|
|
||||||
</a>
|
|
||||||
<a href="#tic"
|
|
||||||
class="sidebar-link ${this._activePage === 'tic' ? 'active' : ''}"
|
|
||||||
@click=${(e) => this._togglePage('tic', e)}>
|
|
||||||
<i class="bi bi-bell"></i>
|
|
||||||
<span class="sidebar-link-name">${t('nav.tic')}</span>
|
|
||||||
</a>
|
|
||||||
` : nothing}
|
|
||||||
`}
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-14
@@ -41,35 +41,53 @@ app-sidebar {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-section-toggle {
|
/* ── Grouped nav sections ── */
|
||||||
|
|
||||||
|
.sidebar-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section-label {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0.2rem 1rem 0.2rem 0.85rem;
|
text-align: left;
|
||||||
font-size: 0.7rem;
|
padding: 0.2rem 0.55rem;
|
||||||
|
margin-top: 0.7rem;
|
||||||
|
font-size: 0.68rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--sidebar-label-color);
|
color: var(--sidebar-label-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section:first-child .sidebar-section-label {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section-label span {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section-label.is-collapsible {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-section-toggle:hover {
|
.sidebar-section-label.is-collapsible:hover {
|
||||||
color: var(--bs-body-color);
|
color: var(--sidebar-text-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-section-toggle i {
|
.sidebar-section-label i {
|
||||||
font-size: 0.65rem;
|
font-size: 0.6rem;
|
||||||
}
|
opacity: 0.7;
|
||||||
|
|
||||||
.sidebar-section-count {
|
|
||||||
margin-left: auto;
|
|
||||||
font-size: 0.65rem;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-nav {
|
.sidebar-nav {
|
||||||
|
|||||||
+5
-1
@@ -1,5 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
// ── Navigation ─────────────────────────────────────────────────────────────
|
// ── Navigation ─────────────────────────────────────────────────────────────
|
||||||
|
'nav.section.workspace': 'Your space',
|
||||||
|
'nav.section.extensions': 'Extensions',
|
||||||
|
'nav.section.config': 'Configuration',
|
||||||
|
'nav.section.dev': 'Development',
|
||||||
'nav.chat': 'Chat',
|
'nav.chat': 'Chat',
|
||||||
'nav.inbox': 'Inbox',
|
'nav.inbox': 'Inbox',
|
||||||
'nav.dashboard': 'Dashboard',
|
'nav.dashboard': 'Dashboard',
|
||||||
@@ -18,7 +22,7 @@ export default {
|
|||||||
'nav.connectors': 'Connectors',
|
'nav.connectors': 'Connectors',
|
||||||
'nav.plugins': 'Plugins',
|
'nav.plugins': 'Plugins',
|
||||||
'nav.plugin_catalog': 'Plugin Catalog',
|
'nav.plugin_catalog': 'Plugin Catalog',
|
||||||
'nav.catalog': 'Catalog',
|
'nav.catalog': 'Connectors Catalog',
|
||||||
'nav.config': 'Settings',
|
'nav.config': 'Settings',
|
||||||
'nav.llm_requests': 'LLM Requests',
|
'nav.llm_requests': 'LLM Requests',
|
||||||
'nav.tic': 'TIC Sessions',
|
'nav.tic': 'TIC Sessions',
|
||||||
|
|||||||
+5
-1
@@ -1,5 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
// ── Navigation ─────────────────────────────────────────────────────────────
|
// ── Navigation ─────────────────────────────────────────────────────────────
|
||||||
|
'nav.section.workspace': 'Votre espace',
|
||||||
|
'nav.section.extensions': 'Extensions',
|
||||||
|
'nav.section.config': 'Configuration',
|
||||||
|
'nav.section.dev': 'Développement',
|
||||||
'nav.chat': 'Discussion',
|
'nav.chat': 'Discussion',
|
||||||
'nav.inbox': 'Boîte de réception',
|
'nav.inbox': 'Boîte de réception',
|
||||||
'nav.dashboard': 'Tableau de bord',
|
'nav.dashboard': 'Tableau de bord',
|
||||||
@@ -18,7 +22,7 @@ export default {
|
|||||||
'nav.connectors': 'Connecteurs',
|
'nav.connectors': 'Connecteurs',
|
||||||
'nav.plugins': 'Plugins',
|
'nav.plugins': 'Plugins',
|
||||||
'nav.plugin_catalog': 'Catalogue des plugins',
|
'nav.plugin_catalog': 'Catalogue des plugins',
|
||||||
'nav.catalog': 'Catalogue',
|
'nav.catalog': 'Catalogue des connecteurs',
|
||||||
'nav.config': 'Paramètres',
|
'nav.config': 'Paramètres',
|
||||||
'nav.llm_requests': 'Requêtes LLM',
|
'nav.llm_requests': 'Requêtes LLM',
|
||||||
'nav.tic': 'Sessions TIC',
|
'nav.tic': 'Sessions TIC',
|
||||||
|
|||||||
+5
-1
@@ -1,5 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
// ── Navigazione ────────────────────────────────────────────────────────────
|
// ── Navigazione ────────────────────────────────────────────────────────────
|
||||||
|
'nav.section.workspace': 'Il tuo spazio',
|
||||||
|
'nav.section.extensions': 'Estensioni',
|
||||||
|
'nav.section.config': 'Configurazione',
|
||||||
|
'nav.section.dev': 'Sviluppo',
|
||||||
'nav.chat': 'Chat',
|
'nav.chat': 'Chat',
|
||||||
'nav.inbox': 'Richieste',
|
'nav.inbox': 'Richieste',
|
||||||
'nav.dashboard': 'Dashboard',
|
'nav.dashboard': 'Dashboard',
|
||||||
@@ -18,7 +22,7 @@ export default {
|
|||||||
'nav.connectors': 'Connettori',
|
'nav.connectors': 'Connettori',
|
||||||
'nav.plugins': 'Plugin',
|
'nav.plugins': 'Plugin',
|
||||||
'nav.plugin_catalog': 'Catalogo plugin',
|
'nav.plugin_catalog': 'Catalogo plugin',
|
||||||
'nav.catalog': 'Catalogo',
|
'nav.catalog': 'Catalogo connettori',
|
||||||
'nav.config': 'Impostazioni',
|
'nav.config': 'Impostazioni',
|
||||||
'nav.llm_requests': 'Richieste LLM',
|
'nav.llm_requests': 'Richieste LLM',
|
||||||
'nav.tic': 'Sessioni TIC',
|
'nav.tic': 'Sessioni TIC',
|
||||||
|
|||||||
Reference in New Issue
Block a user