Merge feature/echtzeit-ui: Echtzeit-UI
Event-Stream-Sidebar mit Suche und Kategorie-Filtern, Status-Bar mit Live-Metriken und Klick-Pause, Ribbon-Icon, Styles im System-Monitor-Design. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
3a64d723d5
4 changed files with 575 additions and 1 deletions
42
src/main.ts
42
src/main.ts
|
|
@ -11,6 +11,8 @@ import { EditorCollector } from './collectors/editor-collector';
|
||||||
import { SystemCollector } from './collectors/system-collector';
|
import { SystemCollector } from './collectors/system-collector';
|
||||||
import { LogfireSettingTab } from './ui/settings-tab';
|
import { LogfireSettingTab } from './ui/settings-tab';
|
||||||
import { InitialScanModal } from './ui/initial-scan-modal';
|
import { InitialScanModal } from './ui/initial-scan-modal';
|
||||||
|
import { StatusBar } from './ui/status-bar';
|
||||||
|
import { EventStreamView, EVENT_STREAM_VIEW_TYPE } from './ui/event-stream-view';
|
||||||
|
|
||||||
export default class LogfirePlugin extends Plugin {
|
export default class LogfirePlugin extends Plugin {
|
||||||
settings!: LogfireSettings;
|
settings!: LogfireSettings;
|
||||||
|
|
@ -24,6 +26,7 @@ export default class LogfirePlugin extends Plugin {
|
||||||
private navCollector!: NavCollector;
|
private navCollector!: NavCollector;
|
||||||
private editorCollector!: EditorCollector;
|
private editorCollector!: EditorCollector;
|
||||||
private systemCollector!: SystemCollector;
|
private systemCollector!: SystemCollector;
|
||||||
|
private statusBar!: StatusBar;
|
||||||
|
|
||||||
private paused = false;
|
private paused = false;
|
||||||
|
|
||||||
|
|
@ -77,6 +80,21 @@ export default class LogfirePlugin extends Plugin {
|
||||||
// UI: Settings tab
|
// UI: Settings tab
|
||||||
this.addSettingTab(new LogfireSettingTab(this.app, this));
|
this.addSettingTab(new LogfireSettingTab(this.app, this));
|
||||||
|
|
||||||
|
// UI: Event stream view
|
||||||
|
this.registerView(
|
||||||
|
EVENT_STREAM_VIEW_TYPE,
|
||||||
|
(leaf) => new EventStreamView(leaf, this.eventBus),
|
||||||
|
);
|
||||||
|
|
||||||
|
// UI: Status bar
|
||||||
|
this.statusBar = new StatusBar(this);
|
||||||
|
this.statusBar.start();
|
||||||
|
|
||||||
|
// Ribbon icon
|
||||||
|
this.addRibbonIcon('activity', 'Logfire: Event-Stream', () => {
|
||||||
|
this.activateEventStream();
|
||||||
|
});
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
this.registerCommands();
|
this.registerCommands();
|
||||||
|
|
||||||
|
|
@ -107,6 +125,7 @@ export default class LogfirePlugin extends Plugin {
|
||||||
async onunload(): Promise<void> {
|
async onunload(): Promise<void> {
|
||||||
console.log('[Logfire] Entlade Plugin...');
|
console.log('[Logfire] Entlade Plugin...');
|
||||||
|
|
||||||
|
this.statusBar?.destroy();
|
||||||
this.stopTracking();
|
this.stopTracking();
|
||||||
|
|
||||||
if (this.sessionManager) {
|
if (this.sessionManager) {
|
||||||
|
|
@ -217,6 +236,12 @@ export default class LogfirePlugin extends Plugin {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
private registerCommands(): void {
|
private registerCommands(): void {
|
||||||
|
this.addCommand({
|
||||||
|
id: 'show-event-stream',
|
||||||
|
name: 'Event-Stream anzeigen',
|
||||||
|
callback: () => this.activateEventStream(),
|
||||||
|
});
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'toggle-tracking',
|
id: 'toggle-tracking',
|
||||||
name: 'Tracking pausieren/fortsetzen',
|
name: 'Tracking pausieren/fortsetzen',
|
||||||
|
|
@ -263,6 +288,23 @@ export default class LogfirePlugin extends Plugin {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Event stream view
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private async activateEventStream(): Promise<void> {
|
||||||
|
const existing = this.app.workspace.getLeavesOfType(EVENT_STREAM_VIEW_TYPE);
|
||||||
|
if (existing.length > 0) {
|
||||||
|
this.app.workspace.revealLeaf(existing[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const leaf = this.app.workspace.getRightLeaf(false);
|
||||||
|
if (leaf) {
|
||||||
|
await leaf.setViewState({ type: EVENT_STREAM_VIEW_TYPE, active: true });
|
||||||
|
this.app.workspace.revealLeaf(leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Settings
|
// Settings
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
150
src/ui/event-stream-view.ts
Normal file
150
src/ui/event-stream-view.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
import { ItemView, WorkspaceLeaf } from 'obsidian';
|
||||||
|
import { LogfireEvent, EventCategory } from '../types';
|
||||||
|
import { EventBus } from '../core/event-bus';
|
||||||
|
|
||||||
|
export const EVENT_STREAM_VIEW_TYPE = 'logfire-event-stream';
|
||||||
|
const MAX_VISIBLE_ENTRIES = 500;
|
||||||
|
|
||||||
|
export class EventStreamView extends ItemView {
|
||||||
|
private entries: LogfireEvent[] = [];
|
||||||
|
private unsubscribe: (() => void) | null = null;
|
||||||
|
private listEl!: HTMLElement;
|
||||||
|
private viewPaused = false;
|
||||||
|
private filterText = '';
|
||||||
|
private enabledCategories = new Set<EventCategory>([
|
||||||
|
'file', 'content', 'navigation', 'editor', 'vault', 'plugin', 'system',
|
||||||
|
]);
|
||||||
|
|
||||||
|
constructor(leaf: WorkspaceLeaf, private eventBus: EventBus) {
|
||||||
|
super(leaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewType(): string {
|
||||||
|
return EVENT_STREAM_VIEW_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDisplayText(): string {
|
||||||
|
return 'Logfire Events';
|
||||||
|
}
|
||||||
|
|
||||||
|
getIcon(): string {
|
||||||
|
return 'activity';
|
||||||
|
}
|
||||||
|
|
||||||
|
async onOpen(): Promise<void> {
|
||||||
|
const container = this.containerEl.children[1] as HTMLElement;
|
||||||
|
container.empty();
|
||||||
|
container.addClass('logfire-event-stream');
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
|
const toolbar = container.createDiv({ cls: 'logfire-stream-toolbar' });
|
||||||
|
|
||||||
|
const searchInput = toolbar.createEl('input', {
|
||||||
|
type: 'text',
|
||||||
|
placeholder: 'Nach Quelle filtern...',
|
||||||
|
cls: 'logfire-stream-search',
|
||||||
|
});
|
||||||
|
searchInput.addEventListener('input', () => {
|
||||||
|
this.filterText = searchInput.value.toLowerCase();
|
||||||
|
this.renderList();
|
||||||
|
});
|
||||||
|
|
||||||
|
const btnGroup = toolbar.createDiv({ cls: 'logfire-stream-btn-group' });
|
||||||
|
|
||||||
|
const pauseBtn = btnGroup.createEl('button', { text: 'Pause' });
|
||||||
|
pauseBtn.addEventListener('click', () => {
|
||||||
|
this.viewPaused = !this.viewPaused;
|
||||||
|
pauseBtn.textContent = this.viewPaused ? 'Weiter' : 'Pause';
|
||||||
|
pauseBtn.toggleClass('is-active', this.viewPaused);
|
||||||
|
});
|
||||||
|
|
||||||
|
const clearBtn = btnGroup.createEl('button', { text: 'Leeren' });
|
||||||
|
clearBtn.addEventListener('click', () => {
|
||||||
|
this.entries = [];
|
||||||
|
this.renderList();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Category toggles
|
||||||
|
const categoryBar = container.createDiv({ cls: 'logfire-stream-categories' });
|
||||||
|
|
||||||
|
const categories: EventCategory[] = ['file', 'content', 'navigation', 'editor', 'vault', 'plugin', 'system'];
|
||||||
|
for (const cat of categories) {
|
||||||
|
const label = categoryBar.createEl('label', { cls: 'logfire-cat-toggle' });
|
||||||
|
const cb = label.createEl('input', { type: 'checkbox' }) as HTMLInputElement;
|
||||||
|
cb.checked = true;
|
||||||
|
label.createSpan({ text: cat });
|
||||||
|
cb.addEventListener('change', () => {
|
||||||
|
if (cb.checked) {
|
||||||
|
this.enabledCategories.add(cat);
|
||||||
|
} else {
|
||||||
|
this.enabledCategories.delete(cat);
|
||||||
|
}
|
||||||
|
this.renderList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event list
|
||||||
|
this.listEl = container.createDiv({ cls: 'logfire-stream-list' });
|
||||||
|
|
||||||
|
// Subscribe to events
|
||||||
|
this.unsubscribe = this.eventBus.onEvent('*', (event) => {
|
||||||
|
if (this.viewPaused) return;
|
||||||
|
this.entries.unshift(event);
|
||||||
|
if (this.entries.length > MAX_VISIBLE_ENTRIES) {
|
||||||
|
this.entries.length = MAX_VISIBLE_ENTRIES;
|
||||||
|
}
|
||||||
|
this.renderList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onClose(): Promise<void> {
|
||||||
|
this.unsubscribe?.();
|
||||||
|
this.unsubscribe = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderList(): void {
|
||||||
|
this.listEl.empty();
|
||||||
|
|
||||||
|
const filtered = this.entries.filter(e => {
|
||||||
|
if (!this.enabledCategories.has(e.category)) return false;
|
||||||
|
if (this.filterText && !e.source.toLowerCase().includes(this.filterText)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const event of filtered.slice(0, MAX_VISIBLE_ENTRIES)) {
|
||||||
|
const row = this.listEl.createDiv({ cls: 'logfire-stream-row' });
|
||||||
|
|
||||||
|
const time = new Date(event.timestamp);
|
||||||
|
const timeStr = `${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}:${String(time.getSeconds()).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
const payloadSummary = this.summarizePayload(event);
|
||||||
|
|
||||||
|
row.createSpan({ text: timeStr, cls: 'logfire-stream-time' });
|
||||||
|
row.createSpan({ text: event.type, cls: 'logfire-stream-type' });
|
||||||
|
row.createSpan({ text: event.source || '', cls: 'logfire-stream-source' });
|
||||||
|
if (payloadSummary) {
|
||||||
|
row.createSpan({ text: payloadSummary, cls: 'logfire-stream-payload' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private summarizePayload(event: LogfireEvent): string {
|
||||||
|
const p = event.payload;
|
||||||
|
switch (event.type) {
|
||||||
|
case 'content:words-changed':
|
||||||
|
return `+${p.wordsAdded ?? 0}/-${p.wordsRemoved ?? 0} Wörter`;
|
||||||
|
case 'editor:change':
|
||||||
|
return `+${p.insertedChars ?? 0}/-${p.deletedChars ?? 0} Zeichen`;
|
||||||
|
case 'nav:file-close':
|
||||||
|
return typeof p.duration === 'number' ? `${Math.round(p.duration / 1000)}s` : '';
|
||||||
|
case 'file:rename':
|
||||||
|
return `→ ${p.newName ?? ''}`;
|
||||||
|
case 'file:move':
|
||||||
|
return `→ ${p.newFolder ?? ''}`;
|
||||||
|
case 'plugin:command-executed':
|
||||||
|
return String(p.commandName ?? '');
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/ui/status-bar.ts
Normal file
67
src/ui/status-bar.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import type LogfirePlugin from '../main';
|
||||||
|
|
||||||
|
export class StatusBar {
|
||||||
|
private el: HTMLElement;
|
||||||
|
private intervalId: ReturnType<typeof setInterval> | null = null;
|
||||||
|
private wordsAdded = 0;
|
||||||
|
private eventCount = 0;
|
||||||
|
|
||||||
|
constructor(private plugin: LogfirePlugin) {
|
||||||
|
this.el = plugin.addStatusBarItem();
|
||||||
|
this.el.addClass('logfire-status-bar');
|
||||||
|
this.el.addEventListener('click', () => this.onClick());
|
||||||
|
}
|
||||||
|
|
||||||
|
start(): void {
|
||||||
|
this.plugin.eventBus.onEvent('*', (event) => {
|
||||||
|
this.eventCount++;
|
||||||
|
const wa = event.payload.wordsAdded;
|
||||||
|
if (typeof wa === 'number') {
|
||||||
|
this.wordsAdded += wa;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
this.intervalId = setInterval(() => this.update(), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
if (this.intervalId !== null) {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
this.intervalId = null;
|
||||||
|
}
|
||||||
|
this.el.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
private update(): void {
|
||||||
|
const paused = this.plugin.isPaused();
|
||||||
|
const indicator = paused ? '\u23F8' : '\u{1F534}';
|
||||||
|
const state = paused ? 'Pausiert' : 'Aufnahme';
|
||||||
|
const duration = this.formatDuration(this.plugin.sessionManager.sessionDurationMs);
|
||||||
|
const words = this.wordsAdded > 0 ? ` | +${this.wordsAdded}w` : '';
|
||||||
|
|
||||||
|
this.el.textContent = `${indicator} ${state} | ${this.eventCount} Events${words} | ${duration}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClick(): void {
|
||||||
|
if (this.plugin.isPaused()) {
|
||||||
|
this.plugin.resume();
|
||||||
|
} else {
|
||||||
|
this.plugin.pause();
|
||||||
|
}
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatDuration(ms: number): string {
|
||||||
|
if (ms <= 0) return '0:00';
|
||||||
|
const totalSeconds = Math.floor(ms / 1000);
|
||||||
|
const hours = Math.floor(totalSeconds / 3600);
|
||||||
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||||
|
const seconds = totalSeconds % 60;
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
return `${minutes}:${String(seconds).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
317
styles.css
317
styles.css
|
|
@ -1 +1,316 @@
|
||||||
/* Logfire – Styles */
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
Logfire — Obsidian Plugin Styles
|
||||||
|
Aesthetic: Utilitarian System Monitor
|
||||||
|
═══════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
Event Stream View — Container
|
||||||
|
--------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.logfire-event-stream {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--background-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
Event Stream — Toolbar
|
||||||
|
--------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.logfire-stream-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
background: var(--background-secondary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-search {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--background-primary);
|
||||||
|
color: var(--text-normal);
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
font-size: 12px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-search:focus {
|
||||||
|
border-color: var(--interactive-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-search::placeholder {
|
||||||
|
color: var(--text-faint);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-btn-group button {
|
||||||
|
padding: 3px 10px;
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--background-primary);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 11px;
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 100ms ease, color 100ms ease, border-color 100ms ease;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-btn-group button:hover {
|
||||||
|
background: var(--background-modifier-hover);
|
||||||
|
color: var(--text-normal);
|
||||||
|
border-color: var(--text-faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-btn-group button.is-active {
|
||||||
|
background: var(--interactive-accent);
|
||||||
|
color: var(--text-on-accent);
|
||||||
|
border-color: var(--interactive-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
Event Stream — Category Filter Bar
|
||||||
|
--------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.logfire-stream-categories {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
background: var(--background-secondary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-cat-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
padding: 2px 7px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 80ms ease, color 80ms ease;
|
||||||
|
user-select: none;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-cat-toggle:hover {
|
||||||
|
background: var(--background-modifier-hover);
|
||||||
|
color: var(--text-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-cat-toggle input[type="checkbox"] {
|
||||||
|
margin: 0;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
accent-color: var(--interactive-accent);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-cat-toggle span {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
Event Stream — List
|
||||||
|
--------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.logfire-stream-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
font-size: 11.5px;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subtle scrollbar */
|
||||||
|
.logfire-stream-list::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-list::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-list::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--background-modifier-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-list::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--text-faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
Event Stream — Row
|
||||||
|
--------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.logfire-stream-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
transition: background 60ms ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-row:hover {
|
||||||
|
background: var(--background-secondary);
|
||||||
|
border-bottom-color: var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alternating row tint for scanability */
|
||||||
|
.logfire-stream-row:nth-child(even) {
|
||||||
|
background: color-mix(in srgb, var(--background-secondary) 30%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-row:nth-child(even):hover {
|
||||||
|
background: var(--background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
Event Stream — Row Segments
|
||||||
|
--------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.logfire-stream-time {
|
||||||
|
color: var(--text-faint);
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-type {
|
||||||
|
color: var(--text-accent);
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-source {
|
||||||
|
color: var(--text-normal);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
min-width: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-stream-payload {
|
||||||
|
color: var(--text-faint);
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
Status Bar
|
||||||
|
--------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.logfire-status-bar {
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
transition: color 100ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-status-bar:hover {
|
||||||
|
color: var(--text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
Scan Modal
|
||||||
|
--------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.logfire-scan-modal {
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-scan-modal h2 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--text-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-scan-modal > p {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-scan-progress {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-scan-progress progress {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-scan-progress progress::-webkit-progress-bar {
|
||||||
|
background: var(--background-modifier-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-scan-progress progress::-webkit-progress-value {
|
||||||
|
background: var(--interactive-accent);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-scan-status {
|
||||||
|
font-size: 11.5px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--text-normal);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-scan-status > div:last-child {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-scan-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logfire-scan-buttons button {
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue