init: fuzzygraph app from fn_registry

This commit is contained in:
2026-04-06 00:56:50 +02:00
commit 23198eee0c
42 changed files with 5539 additions and 0 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FuzzyGraph</title>
<script type="module" crossorigin src="/assets/index-CYqMr7xa.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Cjyz0t73.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FuzzyGraph</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+33
View File
@@ -0,0 +1,33 @@
{
"name": "fuzzygraph",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "tsc -b && vite build",
"preview": "vite preview --host"
},
"dependencies": {
"@base-ui/react": "^1.3.0",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-slider": "^1.3.6",
"@tanstack/react-table": "^8.21.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.577.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"@tailwindcss/vite": "^4.2.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.0",
"tailwindcss": "^4.2.2",
"typescript": "~5.9.3",
"vite": "^8.0.0"
}
}
+1
View File
@@ -0,0 +1 @@
925e378ac695fa8339fd9576604d1d9f
+1289
View File
File diff suppressed because it is too large Load Diff
+215
View File
@@ -0,0 +1,215 @@
import { useEffect, useState, useCallback } from 'react'
import type { ProjectInfo, Entity, Relation, GraphData, EntityTypePreset } from './types'
import { ProjectSidebar } from './components/ProjectSidebar'
import { SearchBar } from '@fn_library'
import { GraphView } from './components/GraphView'
import { EntityTable } from './components/EntityTable'
import { RelationTable } from './components/RelationTable'
import { EntityDetail } from './components/EntityDetail'
import { AssertionPanel } from './components/AssertionPanel'
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@fn_library'
import * as WailsApp from './wailsjs/go/main/App'
export default function App() {
const [projects, setProjects] = useState<ProjectInfo[]>([])
const [currentProject, setCurrentProject] = useState<string>('')
const [entities, setEntities] = useState<Entity[]>([])
const [relations, setRelations] = useState<Relation[]>([])
const [graphData, setGraphData] = useState<GraphData>({ nodes: [], edges: [] })
const [presets, setPresets] = useState<EntityTypePreset[]>([])
const [relationPresets, setRelationPresets] = useState<string[]>([])
const [activeTab, setActiveTab] = useState('graph')
const [selectedEntityId, setSelectedEntityId] = useState<string | null>(null)
useEffect(() => {
console.log('[App] mount — loading presets and projects')
refreshProjects()
WailsApp.GetEntityPresets()
.then(p => { console.log('[App] GetEntityPresets OK:', p?.length, 'presets'); setPresets(p as unknown as EntityTypePreset[]) })
.catch(e => console.error('[App] GetEntityPresets ERROR:', e))
WailsApp.GetRelationPresets()
.then(p => { console.log('[App] GetRelationPresets OK:', p?.length); setRelationPresets(p) })
.catch(e => console.error('[App] GetRelationPresets ERROR:', e))
}, [])
const refreshProjects = useCallback(() => {
console.log('[App] refreshProjects called')
WailsApp.ListProjects()
.then(p => {
const list = (p || []) as unknown as ProjectInfo[]
console.log('[App] ListProjects OK:', list.length, 'projects', JSON.stringify(list))
setProjects(list)
})
.catch(e => console.error('[App] ListProjects ERROR:', e))
}, [])
const refreshData = useCallback(() => {
console.log('[App] refreshData called')
WailsApp.ListEntities()
.then(e => { const list = (e || []) as unknown as Entity[]; console.log('[App] ListEntities OK:', list.length); setEntities(list) })
.catch(e => console.error('[App] ListEntities ERROR:', e))
WailsApp.ListRelations()
.then(r => { const list = (r || []) as unknown as Relation[]; console.log('[App] ListRelations OK:', list.length); setRelations(list) })
.catch(e => console.error('[App] ListRelations ERROR:', e))
WailsApp.GetGraphData()
.then(g => { const data = (g || { nodes: [], edges: [] }) as unknown as GraphData; console.log('[App] GetGraphData OK: nodes=', data.nodes?.length, 'edges=', data.edges?.length); setGraphData(data) })
.catch(e => console.error('[App] GetGraphData ERROR:', e))
}, [])
const handleSwitchProject = useCallback(async (name: string) => {
console.log('[App] handleSwitchProject:', name)
try {
await WailsApp.SwitchProject(name)
console.log('[App] SwitchProject OK')
setCurrentProject(name)
refreshData()
} catch (e) {
console.error('[App] SwitchProject ERROR:', e)
}
}, [refreshData])
const handleCreateProject = useCallback(async (name: string) => {
console.log('[App] handleCreateProject:', name)
try {
const result = await WailsApp.CreateProject(name)
console.log('[App] CreateProject OK:', JSON.stringify(result))
refreshProjects()
console.log('[App] switching to new project...')
await handleSwitchProject(name)
console.log('[App] switched OK')
} catch (e) {
console.error('[App] CreateProject ERROR:', e)
}
}, [refreshProjects, handleSwitchProject])
const handleDeleteProject = useCallback(async (name: string) => {
console.log('[App] handleDeleteProject:', name)
try {
await WailsApp.DeleteProject(name)
console.log('[App] DeleteProject OK')
if (currentProject === name) {
setCurrentProject('')
setEntities([])
setRelations([])
setGraphData({ nodes: [], edges: [] })
}
refreshProjects()
} catch (e) {
console.error('[App] DeleteProject ERROR:', e)
}
}, [currentProject, refreshProjects])
const handleSearch = useCallback(async (query: string) => {
console.log('[App] handleSearch:', query)
if (!query.trim()) {
refreshData()
return
}
try {
const [ents, graph] = await Promise.all([
WailsApp.SearchEntities(query),
WailsApp.SearchGraph(query),
])
console.log('[App] Search OK: entities=', (ents as unknown[])?.length, 'graph nodes=', (graph as unknown as GraphData)?.nodes?.length)
setEntities((ents || []) as unknown as Entity[])
setGraphData((graph || { nodes: [], edges: [] }) as unknown as GraphData)
} catch (e) {
console.error('[App] Search ERROR:', e)
}
}, [refreshData])
const handleNodeClick = useCallback((nodeId: string) => {
console.log('[App] handleNodeClick:', nodeId)
setSelectedEntityId(nodeId)
}, [])
const handleNodeDoubleClick = useCallback(async (nodeId: string) => {
console.log('[App] handleNodeDoubleClick:', nodeId)
try {
const ego = await WailsApp.GetEntityNeighbors(nodeId, 2)
setGraphData((ego || { nodes: [], edges: [] }) as unknown as GraphData)
} catch (e) {
console.error('[App] GetEntityNeighbors ERROR:', e)
}
}, [])
const selectedEntity = entities.find(e => e.id === selectedEntityId) ?? null
console.log('[App] render: projects=', projects.length, 'currentProject=', currentProject, 'entities=', entities.length, 'relations=', relations.length)
return (
<div className="flex h-screen overflow-hidden">
<ProjectSidebar
projects={projects}
current={currentProject}
onSwitch={handleSwitchProject}
onCreate={handleCreateProject}
onDelete={handleDeleteProject}
/>
<main className="flex-1 flex flex-col overflow-hidden">
{currentProject ? (
<>
<div className="px-4 pt-3 pb-2 flex items-center gap-3 border-b" style={{ borderColor: 'var(--border)' }}>
<h2 className="text-sm font-semibold" style={{ color: 'var(--foreground)' }}>{currentProject}</h2>
<SearchBar onSearch={handleSearch} />
</div>
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col overflow-hidden">
<TabsList className="mx-4 mt-2">
<TabsTrigger value="graph">Graph</TabsTrigger>
<TabsTrigger value="entities">Entities ({entities.length})</TabsTrigger>
<TabsTrigger value="relations">Relations ({relations.length})</TabsTrigger>
<TabsTrigger value="assertions">Assertions</TabsTrigger>
</TabsList>
<TabsContent value="graph" className="flex-1 flex overflow-hidden m-0 p-0">
<div className="flex-1 relative">
<GraphView
data={graphData}
presets={presets}
onNodeClick={handleNodeClick}
onNodeDoubleClick={handleNodeDoubleClick}
/>
</div>
{selectedEntity && (
<EntityDetail
entity={selectedEntity}
relations={relations}
onClose={() => setSelectedEntityId(null)}
onUpdate={refreshData}
/>
)}
</TabsContent>
<TabsContent value="entities" className="flex-1 overflow-auto px-4 pb-4 m-0">
<EntityTable
entities={entities}
presets={presets}
onRefresh={refreshData}
/>
</TabsContent>
<TabsContent value="relations" className="flex-1 overflow-auto px-4 pb-4 m-0">
<RelationTable
relations={relations}
entities={entities}
relationPresets={relationPresets}
onRefresh={refreshData}
/>
</TabsContent>
<TabsContent value="assertions" className="flex-1 overflow-auto px-4 pb-4 m-0">
<AssertionPanel entities={entities} />
</TabsContent>
</Tabs>
</>
) : (
<div className="flex-1 flex items-center justify-center">
<p style={{ color: 'var(--muted-foreground)' }}>Select or create a project to begin</p>
</div>
)}
</main>
</div>
)
}
+39
View File
@@ -0,0 +1,39 @@
@import "tailwindcss";
:root {
--background: oklch(8% 0.015 260);
--foreground: oklch(95% 0.01 260);
--muted: oklch(18% 0.02 260);
--muted-foreground: oklch(60% 0.02 260);
--border: oklch(15% 0.01 260);
--primary: oklch(65% 0.22 260);
--primary-foreground: oklch(98% 0.01 260);
--secondary: oklch(20% 0.02 260);
--secondary-foreground: oklch(95% 0.01 260);
--accent: oklch(18% 0.03 260);
--accent-foreground: oklch(95% 0.01 260);
--destructive: oklch(55% 0.22 25);
--destructive-foreground: oklch(98% 0.01 260);
--card: oklch(11% 0.015 260);
--card-foreground: oklch(95% 0.01 260);
--popover: oklch(12% 0.015 260);
--popover-foreground: oklch(95% 0.01 260);
--ring: oklch(65% 0.22 260);
--input: oklch(22% 0.02 260);
--radius: 0.5rem;
--success: oklch(65% 0.2 145);
--success-foreground: oklch(98% 0.01 145);
}
body {
background-color: var(--background);
color: var(--foreground);
font-family: 'Geist Variable', system-ui, -apple-system, sans-serif;
-webkit-font-smoothing: antialiased;
}
[data-slot="card"] {
border: none;
box-shadow: none;
}
+170
View File
@@ -0,0 +1,170 @@
import { useState } from 'react'
import type { Entity, Assertion, AssertionResult, AssertionInput } from '../types'
import { Plus, Play, Trash2 } from 'lucide-react'
import { SimpleSelect } from '@fn_library'
import { Card, CardHeader, CardTitle, CardContent } from '@fn_library'
import { Button } from '@fn_library'
import * as WailsApp from '../wailsjs/go/main/App'
interface Props {
entities: Entity[]
}
export function AssertionPanel({ entities }: Props) {
const [assertions, setAssertions] = useState<Assertion[]>([])
const [results, setResults] = useState<AssertionResult[]>([])
const [selectedEntity, setSelectedEntity] = useState(entities[0]?.id ?? '')
const [showAdd, setShowAdd] = useState(false)
const [newName, setNewName] = useState('')
const [newKind, setNewKind] = useState('range')
const [newRule, setNewRule] = useState('')
const [newSeverity, setNewSeverity] = useState('warning')
const loadAssertions = async (entityId: string) => {
setSelectedEntity(entityId)
const list = await WailsApp.ListAssertions(entityId) as unknown as Assertion[]
setAssertions(list || [])
setResults([])
}
const handleAdd = async () => {
if (!newName || !newRule) return
const input: AssertionInput = {
entity_id: selectedEntity,
name: newName,
kind: newKind,
rule: newRule,
severity: newSeverity,
description: '',
}
await WailsApp.AddAssertion(input as never)
setShowAdd(false)
setNewName('')
setNewRule('')
loadAssertions(selectedEntity)
}
const handleEval = async () => {
const res = await WailsApp.EvalAssertions(selectedEntity) as unknown as AssertionResult[]
setResults(res || [])
}
const handleDelete = async (id: string) => {
await WailsApp.DeleteAssertion(id)
loadAssertions(selectedEntity)
}
const inputStyle = { background: 'var(--input)', color: 'var(--foreground)', border: '1px solid var(--border)' }
return (
<Card className="mt-3">
<CardHeader className="flex flex-row items-center justify-between py-3">
<CardTitle className="text-base">Assertions</CardTitle>
<div className="flex gap-2">
<SimpleSelect
value={selectedEntity}
onValueChange={loadAssertions}
placeholder="Select entity..."
className="w-48"
options={entities.map(e => ({ value: e.id, label: e.name }))}
/>
<Button size="sm" onClick={handleEval} disabled={!selectedEntity}>
<Play size={14} className="mr-1" /> Eval
</Button>
<Button size="sm" variant="outline" onClick={() => setShowAdd(!showAdd)} disabled={!selectedEntity}>
<Plus size={14} />
</Button>
</div>
</CardHeader>
{showAdd && (
<div className="px-4 pb-3 flex gap-2 items-end">
<div className="flex-1">
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Name</label>
<input value={newName} onChange={e => setNewName(e.target.value)} className="w-full px-2 py-1 rounded text-sm" style={inputStyle} />
</div>
<div className="w-24">
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Kind</label>
<SimpleSelect
value={newKind}
onValueChange={setNewKind}
options={[
{ value: 'range', label: 'range' },
{ value: 'null', label: 'null' },
{ value: 'statistical', label: 'statistical' },
{ value: 'consistency', label: 'consistency' },
{ value: 'freshness', label: 'freshness' },
]}
/>
</div>
<div className="w-24">
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Severity</label>
<SimpleSelect
value={newSeverity}
onValueChange={setNewSeverity}
options={[
{ value: 'critical', label: 'critical' },
{ value: 'warning', label: 'warning' },
{ value: 'info', label: 'info' },
]}
/>
</div>
<div className="flex-1">
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Rule (SQL expr)</label>
<input value={newRule} onChange={e => setNewRule(e.target.value)} className="w-full px-2 py-1 rounded text-sm" style={inputStyle} placeholder="risk_score > 70" />
</div>
<button onClick={handleAdd} className="px-3 py-1 rounded text-sm" style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>Add</button>
</div>
)}
<CardContent className="p-0">
<table className="w-full text-sm">
<thead>
<tr className="border-b" style={{ borderColor: 'var(--border)' }}>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Name</th>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Kind</th>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Rule</th>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Severity</th>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Result</th>
<th className="text-right px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Actions</th>
</tr>
</thead>
<tbody>
{assertions.map(a => {
const result = results.find(r => r.assertion_id === a.id)
return (
<tr key={a.id} className="border-b" style={{ borderColor: 'var(--border)' }}>
<td className="px-4 py-2">{a.name}</td>
<td className="px-4 py-2" style={{ color: 'var(--muted-foreground)' }}>{a.kind}</td>
<td className="px-4 py-2 font-mono text-xs">{a.rule}</td>
<td className="px-4 py-2">
<span style={{ color: a.severity === 'critical' ? 'var(--destructive)' : a.severity === 'warning' ? 'var(--chart-3, #f59e0b)' : 'var(--muted-foreground)' }}>
{a.severity}
</span>
</td>
<td className="px-4 py-2">
{result ? (
<span style={{ color: result.status === 'pass' ? 'var(--success)' : 'var(--destructive)' }}>
{result.status}
</span>
) : '—'}
</td>
<td className="px-4 py-2 text-right">
<button onClick={() => handleDelete(a.id)} className="p-1 rounded" style={{ color: 'var(--destructive)' }}>
<Trash2 size={14} />
</button>
</td>
</tr>
)
})}
{assertions.length === 0 && (
<tr><td colSpan={6} className="px-4 py-8 text-center" style={{ color: 'var(--muted-foreground)' }}>
{selectedEntity ? 'No assertions for this entity' : 'Select an entity to view assertions'}
</td></tr>
)}
</tbody>
</table>
</CardContent>
</Card>
)
}
+91
View File
@@ -0,0 +1,91 @@
import type { Entity, Relation } from '../types'
import { X, ExternalLink } from 'lucide-react'
interface Props {
entity: Entity
relations: Relation[]
onClose: () => void
onUpdate: () => void
}
export function EntityDetail({ entity, relations, onClose }: Props) {
const directRelations = relations.filter(r => r.from_entity === entity.id || r.to_entity === entity.id)
return (
<aside className="w-72 border-l overflow-y-auto" style={{ borderColor: 'var(--border)', background: 'var(--card)' }}>
<div className="p-3 flex items-center justify-between border-b" style={{ borderColor: 'var(--border)' }}>
<h3 className="text-sm font-semibold truncate">{entity.name}</h3>
<button onClick={onClose} className="p-1">
<X size={14} style={{ color: 'var(--muted-foreground)' }} />
</button>
</div>
<div className="p-3 space-y-3 text-sm">
<Section label="Type">{entity.type_ref.replace(/_go_cybersecurity$/, '').replace(/^osint_/, '')}</Section>
<Section label="Status">{entity.status}</Section>
{entity.description && <Section label="Description">{entity.description}</Section>}
{entity.metadata && Object.keys(entity.metadata).length > 0 && (
<div>
<label className="block text-xs font-semibold mb-1" style={{ color: 'var(--muted-foreground)' }}>Metadata</label>
<div className="space-y-1">
{Object.entries(entity.metadata).map(([k, v]) => (
<div key={k} className="flex justify-between">
<span style={{ color: 'var(--muted-foreground)' }}>{k}</span>
<span className="font-mono text-xs">{String(v)}</span>
</div>
))}
</div>
</div>
)}
{entity.notes && (
<div>
<label className="block text-xs font-semibold mb-1" style={{ color: 'var(--muted-foreground)' }}>Notes</label>
<p className="text-xs whitespace-pre-wrap" style={{ color: 'var(--foreground)' }}>{entity.notes}</p>
</div>
)}
{entity.tags && entity.tags.length > 0 && (
<div>
<label className="block text-xs font-semibold mb-1" style={{ color: 'var(--muted-foreground)' }}>Tags</label>
<div className="flex flex-wrap gap-1">
{entity.tags.map(t => (
<span key={t} className="px-1.5 py-0.5 rounded text-xs" style={{ background: 'var(--secondary)', color: 'var(--secondary-foreground)' }}>{t}</span>
))}
</div>
</div>
)}
{directRelations.length > 0 && (
<div>
<label className="block text-xs font-semibold mb-1" style={{ color: 'var(--muted-foreground)' }}>
Relations ({directRelations.length})
</label>
<div className="space-y-1">
{directRelations.map(r => {
const isFrom = r.from_entity === entity.id
return (
<div key={r.id} className="flex items-center gap-1 text-xs">
<ExternalLink size={10} style={{ color: 'var(--muted-foreground)' }} />
<span>{isFrom ? '' : '<-'} {r.name} {isFrom ? '->' : ''}</span>
<span className="font-medium">{isFrom ? r.to_entity : r.from_entity}</span>
</div>
)
})}
</div>
</div>
)}
</div>
</aside>
)
}
function Section({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div>
<label className="block text-xs font-semibold" style={{ color: 'var(--muted-foreground)' }}>{label}</label>
<span>{children}</span>
</div>
)
}
+160
View File
@@ -0,0 +1,160 @@
import { useState, useEffect } from 'react'
import type { Entity, EntityTypePreset, EntityInput } from '../types'
import { SimpleSelect } from '@fn_library'
import { X } from 'lucide-react'
interface Props {
presets: EntityTypePreset[]
entity: Entity | null // null = create, non-null = edit
onSubmit: (input: EntityInput) => void
onClose: () => void
}
export function EntityDialog({ presets, entity, onSubmit, onClose }: Props) {
const [name, setName] = useState(entity?.name ?? '')
const [typeRef, setTypeRef] = useState(entity?.type_ref ?? presets[0]?.type_ref ?? '')
const [description, setDescription] = useState(entity?.description ?? '')
const [notes, setNotes] = useState(entity?.notes ?? '')
const [tagsStr, setTagsStr] = useState((entity?.tags ?? []).join(', '))
const [metadata, setMetadata] = useState<Record<string, string>>(() => {
const m: Record<string, string> = {}
if (entity?.metadata) {
for (const [k, v] of Object.entries(entity.metadata)) {
m[k] = String(v ?? '')
}
}
return m
})
const currentPreset = presets.find(p => p.type_ref === typeRef)
const metadataFields = currentPreset?.metadata_fields ?? []
// When type changes, reset metadata fields to match new type
useEffect(() => {
if (!entity) {
const m: Record<string, string> = {}
for (const f of metadataFields) {
m[f] = metadata[f] ?? ''
}
setMetadata(m)
}
}, [typeRef]) // eslint-disable-line react-hooks/exhaustive-deps
const handleSubmit = () => {
const cleanMeta: Record<string, unknown> = {}
for (const [k, v] of Object.entries(metadata)) {
if (v.trim()) {
// Try parsing as number
const num = Number(v)
if (!isNaN(num) && v.trim() !== '') {
cleanMeta[k] = num
} else if (v === 'true') {
cleanMeta[k] = true
} else if (v === 'false') {
cleanMeta[k] = false
} else {
cleanMeta[k] = v.trim()
}
}
}
onSubmit({
name: name.trim(),
type_ref: typeRef,
description: description.trim(),
tags: tagsStr.split(',').map(t => t.trim()).filter(Boolean),
metadata: cleanMeta,
notes: notes.trim(),
})
}
const inputStyle = {
background: 'var(--input)',
color: 'var(--foreground)',
border: '1px solid var(--border)',
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center" style={{ background: 'rgba(0,0,0,0.6)' }}>
<div className="w-[520px] max-h-[85vh] overflow-y-auto rounded-lg p-5" style={{ background: 'var(--card)', border: '1px solid var(--border)' }}>
<div className="flex items-center justify-between mb-4">
<h3 className="text-base font-semibold">{entity ? 'Edit Entity' : 'New Entity'}</h3>
<button onClick={onClose} className="p-1"><X size={16} style={{ color: 'var(--muted-foreground)' }} /></button>
</div>
<div className="space-y-3">
<div>
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Name</label>
<input value={name} onChange={e => setName(e.target.value)} className="w-full px-3 py-1.5 rounded text-sm" style={inputStyle} />
</div>
<div>
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Type</label>
<SimpleSelect
value={typeRef}
onValueChange={setTypeRef}
options={presets.map(p => ({ value: p.type_ref, label: p.label }))}
/>
</div>
<div>
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Description</label>
<input value={description} onChange={e => setDescription(e.target.value)} className="w-full px-3 py-1.5 rounded text-sm" style={inputStyle} />
</div>
<div>
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Tags (comma separated)</label>
<input value={tagsStr} onChange={e => setTagsStr(e.target.value)} className="w-full px-3 py-1.5 rounded text-sm" style={inputStyle} placeholder="osint, high-risk" />
</div>
{metadataFields.length > 0 && (
<div>
<label className="block text-xs mb-1 font-semibold" style={{ color: 'var(--muted-foreground)' }}>
Metadata ({currentPreset?.label})
</label>
<div className="space-y-2">
{metadataFields.map(field => (
<div key={field} className="flex items-center gap-2">
<span className="text-xs w-28 text-right" style={{ color: 'var(--muted-foreground)' }}>{field}</span>
<input
value={metadata[field] ?? ''}
onChange={e => setMetadata(prev => ({ ...prev, [field]: e.target.value }))}
className="flex-1 px-2 py-1 rounded text-sm"
style={inputStyle}
/>
</div>
))}
</div>
</div>
)}
<div>
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Notes</label>
<textarea
value={notes}
onChange={e => setNotes(e.target.value)}
rows={3}
className="w-full px-3 py-1.5 rounded text-sm resize-none"
style={inputStyle}
placeholder="Operational notes..."
/>
</div>
</div>
<div className="flex justify-end gap-2 mt-4">
<button onClick={onClose} className="px-3 py-1.5 rounded text-sm" style={{ background: 'var(--secondary)', color: 'var(--secondary-foreground)' }}>
Cancel
</button>
<button
onClick={handleSubmit}
disabled={!name.trim()}
className="px-3 py-1.5 rounded text-sm font-medium disabled:opacity-40"
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}
>
{entity ? 'Update' : 'Create'}
</button>
</div>
</div>
</div>
)
}
+107
View File
@@ -0,0 +1,107 @@
import { useState } from 'react'
import type { Entity, EntityTypePreset, EntityInput } from '../types'
import { EntityDialog } from './EntityDialog'
import { Plus, Pencil, Trash2 } from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent } from '@fn_library'
import { Badge } from '@fn_library'
import { Button } from '@fn_library'
import { AddEntity, UpdateEntity, DeleteEntity } from '../wailsjs/go/main/App'
interface Props {
entities: Entity[]
presets: EntityTypePreset[]
onRefresh: () => void
}
export function EntityTable({ entities, presets, onRefresh }: Props) {
const [dialogOpen, setDialogOpen] = useState(false)
const [editEntity, setEditEntity] = useState<Entity | null>(null)
const presetMap = Object.fromEntries(presets.map(p => [p.type_ref, p]))
const handleAdd = async (input: EntityInput) => {
await AddEntity(input as never)
setDialogOpen(false)
onRefresh()
}
const handleUpdate = async (input: EntityInput) => {
if (editEntity) {
await UpdateEntity(editEntity.id, input as never)
setEditEntity(null)
onRefresh()
}
}
const handleDelete = async (id: string) => {
await DeleteEntity(id)
onRefresh()
}
return (
<Card className="mt-3">
<CardHeader className="flex flex-row items-center justify-between py-3">
<CardTitle className="text-base">Entities</CardTitle>
<Button size="sm" onClick={() => setDialogOpen(true)}>
<Plus size={14} className="mr-1" /> Add Entity
</Button>
</CardHeader>
<CardContent className="p-0">
<table className="w-full text-sm">
<thead>
<tr className="border-b" style={{ borderColor: 'var(--border)' }}>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Name</th>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Type</th>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Status</th>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Notes</th>
<th className="text-right px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Actions</th>
</tr>
</thead>
<tbody>
{entities.map(e => {
const preset = presetMap[e.type_ref]
return (
<tr key={e.id} className="border-b hover:opacity-90" style={{ borderColor: 'var(--border)' }}>
<td className="px-4 py-2 font-medium">{e.name}</td>
<td className="px-4 py-2">
<Badge style={{ backgroundColor: preset?.color ?? 'var(--muted-foreground)', color: 'var(--primary-foreground)' }}>
{preset?.label ?? e.type_ref}
</Badge>
</td>
<td className="px-4 py-2">
<span className="text-xs" style={{ color: e.status === 'active' ? 'var(--success)' : 'var(--muted-foreground)' }}>
{e.status}
</span>
</td>
<td className="px-4 py-2 max-w-48 truncate" style={{ color: 'var(--muted-foreground)' }}>
{e.notes || '—'}
</td>
<td className="px-4 py-2 text-right">
<button onClick={() => setEditEntity(e)} className="p-1 mr-1 rounded hover:opacity-80" style={{ color: 'var(--primary)' }}>
<Pencil size={14} />
</button>
<button onClick={() => handleDelete(e.id)} className="p-1 rounded hover:opacity-80" style={{ color: 'var(--destructive)' }}>
<Trash2 size={14} />
</button>
</td>
</tr>
)
})}
{entities.length === 0 && (
<tr><td colSpan={5} className="px-4 py-8 text-center" style={{ color: 'var(--muted-foreground)' }}>No entities yet</td></tr>
)}
</tbody>
</table>
</CardContent>
{(dialogOpen || editEntity) && (
<EntityDialog
presets={presets}
entity={editEntity}
onSubmit={editEntity ? handleUpdate : handleAdd}
onClose={() => { setDialogOpen(false); setEditEntity(null) }}
/>
)}
</Card>
)
}
+67
View File
@@ -0,0 +1,67 @@
import { GraphContainer } from '@graph'
import type { GraphData as LibGraphData } from '@graph'
import type { GraphData, EntityTypePreset } from '../types'
interface Props {
data: GraphData
presets: EntityTypePreset[]
onNodeClick: (nodeId: string) => void
onNodeDoubleClick: (nodeId: string) => void
}
export function GraphView({ data, presets, onNodeClick, onNodeDoubleClick }: Props) {
// Map our GraphData to the library's format (they're compatible but need the cast)
const libData: LibGraphData = {
nodes: data.nodes.map(n => ({
id: n.id,
label: n.label,
type: n.type,
color: n.color,
size: n.size,
x: n.x,
y: n.y,
})),
edges: data.edges.map(e => ({
id: e.id,
source: e.source,
target: e.target,
label: e.label,
color: e.color,
size: e.size,
type: e.type as 'arrow' | 'line',
})),
}
const nodeTypes = presets.map(p => ({
type: p.type_ref,
color: p.color,
label: p.label,
}))
if (data.nodes.length === 0) {
return (
<div className="flex items-center justify-center h-full">
<p className="text-sm" style={{ color: 'var(--muted-foreground)' }}>
No data to display. Add entities and relations to build the graph.
</p>
</div>
)
}
return (
<GraphContainer
data={libData}
layout="organic"
showToolbar
showLegend
showMinimap
nodeTypes={nodeTypes}
onNodeClick={n => onNodeClick(n.id)}
onNodeDoubleClick={n => onNodeDoubleClick(n.id)}
enableSelection
selectionMode="multiple"
theme={{ nodeSize: 8, edgeSize: 1 }}
height="100%"
/>
)
}
+103
View File
@@ -0,0 +1,103 @@
import { useState } from 'react'
import type { ProjectInfo } from '../types'
import { Plus, Trash2, FolderOpen } from 'lucide-react'
interface Props {
projects: ProjectInfo[]
current: string
onSwitch: (name: string) => void
onCreate: (name: string) => void
onDelete: (name: string) => void
}
export function ProjectSidebar({ projects, current, onSwitch, onCreate, onDelete }: Props) {
const [newName, setNewName] = useState('')
const [showInput, setShowInput] = useState(false)
console.log('[ProjectSidebar] render: projects=', projects.length, 'current=', current, 'projects data:', JSON.stringify(projects))
const handleCreate = () => {
const name = newName.trim()
console.log('[ProjectSidebar] handleCreate: name=', JSON.stringify(name))
if (name) {
onCreate(name)
setNewName('')
setShowInput(false)
} else {
console.log('[ProjectSidebar] handleCreate: empty name, skipping')
}
}
return (
<aside className="w-56 flex flex-col border-r" style={{ borderColor: 'var(--border)', background: 'var(--card)' }}>
<div className="p-3 flex items-center justify-between border-b" style={{ borderColor: 'var(--border)' }}>
<span className="text-xs font-bold uppercase tracking-wider" style={{ color: 'var(--muted-foreground)' }}>
Projects
</span>
<button
onClick={() => { console.log('[ProjectSidebar] toggling input'); setShowInput(!showInput) }}
className="p-1 rounded hover:opacity-80"
style={{ color: 'var(--primary)' }}
>
<Plus size={16} />
</button>
</div>
{showInput && (
<div className="p-2 border-b" style={{ borderColor: 'var(--border)' }}>
<input
value={newName}
onChange={e => setNewName(e.target.value)}
onKeyDown={e => {
console.log('[ProjectSidebar] keyDown:', e.key)
if (e.key === 'Enter') handleCreate()
}}
placeholder="Project name..."
autoFocus
className="w-full px-2 py-1 rounded text-sm"
style={{
background: 'var(--input)',
color: 'var(--foreground)',
border: '1px solid var(--border)',
}}
/>
</div>
)}
<div className="flex-1 overflow-y-auto">
{projects.map(p => (
<button
key={p.name}
onClick={() => { console.log('[ProjectSidebar] switching to:', p.name); onSwitch(p.name) }}
className="flex w-full items-center gap-2 px-3 py-2 cursor-pointer group text-left"
style={{
background: p.name === current ? 'var(--accent)' : 'transparent',
color: p.name === current ? 'var(--accent-foreground)' : 'var(--foreground)',
}}
>
<FolderOpen size={14} style={{ color: 'var(--muted-foreground)' }} />
<div className="flex-1 min-w-0">
<div className="text-sm truncate">{p.name}</div>
<div className="text-xs" style={{ color: 'var(--muted-foreground)' }}>
{p.entity_count}E / {p.relation_count}R
</div>
</div>
<button
onClick={e => { e.stopPropagation(); console.log('[ProjectSidebar] deleting:', p.name); onDelete(p.name) }}
className="opacity-0 group-hover:opacity-100 p-1 rounded"
style={{ color: 'var(--destructive)' }}
>
<Trash2 size={12} />
</button>
</button>
))}
{projects.length === 0 && (
<p className="p-3 text-xs" style={{ color: 'var(--muted-foreground)' }}>
No projects yet
</p>
)}
</div>
</aside>
)
}
+107
View File
@@ -0,0 +1,107 @@
import { useState } from 'react'
import type { Entity, RelationInputDTO } from '../types'
import { SimpleSelect } from '@fn_library'
import { X } from 'lucide-react'
interface Props {
entities: Entity[]
relationPresets: string[]
onSubmit: (input: RelationInputDTO) => void
onClose: () => void
}
export function RelationDialog({ entities, relationPresets, onSubmit, onClose }: Props) {
const [name, setName] = useState(relationPresets[0] ?? '')
const [fromEntity, setFromEntity] = useState(entities[0]?.id ?? '')
const [toEntity, setToEntity] = useState(entities[1]?.id ?? entities[0]?.id ?? '')
const [description, setDescription] = useState('')
const [weight, setWeight] = useState('1.0')
const [notes, setNotes] = useState('')
const handleSubmit = () => {
const w = parseFloat(weight)
onSubmit({
name,
from_entity: fromEntity,
to_entity: toEntity,
description,
weight: isNaN(w) ? null : w,
tags: [],
notes,
})
}
const inputStyle = {
background: 'var(--input)',
color: 'var(--foreground)',
border: '1px solid var(--border)',
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center" style={{ background: 'rgba(0,0,0,0.6)' }}>
<div className="w-[480px] rounded-lg p-5" style={{ background: 'var(--card)', border: '1px solid var(--border)' }}>
<div className="flex items-center justify-between mb-4">
<h3 className="text-base font-semibold">New Relation</h3>
<button onClick={onClose} className="p-1"><X size={16} style={{ color: 'var(--muted-foreground)' }} /></button>
</div>
<div className="space-y-3">
<div>
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Relation Type</label>
<SimpleSelect
value={name}
onValueChange={setName}
options={relationPresets.map(p => ({ value: p, label: p }))}
/>
</div>
<div className="flex gap-3">
<div className="flex-1">
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>From</label>
<SimpleSelect
value={fromEntity}
onValueChange={setFromEntity}
options={entities.map(e => ({ value: e.id, label: e.name }))}
/>
</div>
<div className="flex-1">
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>To</label>
<SimpleSelect
value={toEntity}
onValueChange={setToEntity}
options={entities.map(e => ({ value: e.id, label: e.name }))}
/>
</div>
</div>
<div>
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Description</label>
<input value={description} onChange={e => setDescription(e.target.value)} className="w-full px-3 py-1.5 rounded text-sm" style={inputStyle} />
</div>
<div>
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Weight (0.0 - 1.0)</label>
<input value={weight} onChange={e => setWeight(e.target.value)} type="number" step="0.1" min="0" max="1" className="w-full px-3 py-1.5 rounded text-sm" style={inputStyle} />
</div>
<div>
<label className="block text-xs mb-1" style={{ color: 'var(--muted-foreground)' }}>Notes</label>
<textarea value={notes} onChange={e => setNotes(e.target.value)} rows={2} className="w-full px-3 py-1.5 rounded text-sm resize-none" style={inputStyle} />
</div>
</div>
<div className="flex justify-end gap-2 mt-4">
<button onClick={onClose} className="px-3 py-1.5 rounded text-sm" style={{ background: 'var(--secondary)', color: 'var(--secondary-foreground)' }}>Cancel</button>
<button
onClick={handleSubmit}
disabled={!name || fromEntity === toEntity}
className="px-3 py-1.5 rounded text-sm font-medium disabled:opacity-40"
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}
>
Create
</button>
</div>
</div>
</div>
)
}
+82
View File
@@ -0,0 +1,82 @@
import { useState } from 'react'
import type { Relation, Entity, RelationInputDTO } from '../types'
import { RelationDialog } from './RelationDialog'
import { Plus, Trash2 } from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent } from '@fn_library'
import { Button } from '@fn_library'
import { AddRelation, DeleteRelation } from '../wailsjs/go/main/App'
interface Props {
relations: Relation[]
entities: Entity[]
relationPresets: string[]
onRefresh: () => void
}
export function RelationTable({ relations, entities, relationPresets, onRefresh }: Props) {
const [dialogOpen, setDialogOpen] = useState(false)
const entityMap = Object.fromEntries(entities.map(e => [e.id, e.name]))
const handleAdd = async (input: RelationInputDTO) => {
await AddRelation(input as never)
setDialogOpen(false)
onRefresh()
}
const handleDelete = async (id: string) => {
await DeleteRelation(id)
onRefresh()
}
return (
<Card className="mt-3">
<CardHeader className="flex flex-row items-center justify-between py-3">
<CardTitle className="text-base">Relations</CardTitle>
<Button size="sm" onClick={() => setDialogOpen(true)} disabled={entities.length < 2}>
<Plus size={14} className="mr-1" /> Add Relation
</Button>
</CardHeader>
<CardContent className="p-0">
<table className="w-full text-sm">
<thead>
<tr className="border-b" style={{ borderColor: 'var(--border)' }}>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>From</th>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Relation</th>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>To</th>
<th className="text-left px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Weight</th>
<th className="text-right px-4 py-2 font-medium" style={{ color: 'var(--muted-foreground)' }}>Actions</th>
</tr>
</thead>
<tbody>
{relations.map(r => (
<tr key={r.id} className="border-b" style={{ borderColor: 'var(--border)' }}>
<td className="px-4 py-2">{entityMap[r.from_entity] ?? r.from_entity}</td>
<td className="px-4 py-2 font-medium">{r.name}</td>
<td className="px-4 py-2">{entityMap[r.to_entity] ?? r.to_entity}</td>
<td className="px-4 py-2" style={{ color: 'var(--muted-foreground)' }}>{r.weight?.toFixed(2) ?? '—'}</td>
<td className="px-4 py-2 text-right">
<button onClick={() => handleDelete(r.id)} className="p-1 rounded hover:opacity-80" style={{ color: 'var(--destructive)' }}>
<Trash2 size={14} />
</button>
</td>
</tr>
))}
{relations.length === 0 && (
<tr><td colSpan={5} className="px-4 py-8 text-center" style={{ color: 'var(--muted-foreground)' }}>No relations yet</td></tr>
)}
</tbody>
</table>
</CardContent>
{dialogOpen && (
<RelationDialog
entities={entities}
relationPresets={relationPresets}
onSubmit={handleAdd}
onClose={() => setDialogOpen(false)}
/>
)}
</Card>
)
}
+37
View File
@@ -0,0 +1,37 @@
import { useState, useRef, useEffect } from 'react'
import { Search, X } from 'lucide-react'
interface Props {
onSearch: (query: string) => void
}
export function SearchBar({ onSearch }: Props) {
const [query, setQuery] = useState('')
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
useEffect(() => {
if (timerRef.current) clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => {
onSearch(query)
}, 300)
return () => { if (timerRef.current) clearTimeout(timerRef.current) }
}, [query, onSearch])
return (
<div className="flex-1 flex items-center gap-2 px-2 py-1 rounded" style={{ background: 'var(--input)', border: '1px solid var(--border)' }}>
<Search size={14} style={{ color: 'var(--muted-foreground)' }} />
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Search entities..."
className="flex-1 bg-transparent text-sm outline-none"
style={{ color: 'var(--foreground)' }}
/>
{query && (
<button onClick={() => setQuery('')} className="p-0.5">
<X size={12} style={{ color: 'var(--muted-foreground)' }} />
</button>
)}
</div>
)
}
+61
View File
@@ -0,0 +1,61 @@
declare module '@fn_library' {
export const Tabs: React.FC<{ value: string; onValueChange: (v: string) => void; className?: string; children: React.ReactNode }>
export const TabsList: React.FC<{ className?: string; children: React.ReactNode }>
export const TabsTrigger: React.FC<{ value: string; children: React.ReactNode }>
export const TabsContent: React.FC<{ value: string; className?: string; children: React.ReactNode }>
export const Card: React.FC<{ className?: string; children: React.ReactNode }>
export const CardHeader: React.FC<{ className?: string; children: React.ReactNode }>
export const CardTitle: React.FC<{ className?: string; children: React.ReactNode }>
export const CardContent: React.FC<{ className?: string; children: React.ReactNode }>
export const Badge: React.FC<{ className?: string; style?: React.CSSProperties; children: React.ReactNode }>
export const Button: React.FC<{ size?: string; variant?: string; onClick?: () => void; disabled?: boolean; className?: string; children: React.ReactNode }>
export interface SimpleSelectOption { value: string; label: string; disabled?: boolean }
export function SimpleSelect(props: { value: string; onValueChange: (value: string) => void; options: SimpleSelectOption[]; placeholder?: string; disabled?: boolean; size?: 'sm' | 'default'; className?: string }): React.ReactElement
export function SearchBar(props: { onSearch: (query: string) => void; placeholder?: string; debounceMs?: number; className?: string }): React.ReactElement
}
declare module '@graph' {
export interface GraphNode {
id: string
label?: string
type?: string
color?: string
size?: number
x?: number
y?: number
[key: string]: unknown
}
export interface GraphEdge {
id: string
source: string
target: string
label?: string
color?: string
size?: number
type?: 'arrow' | 'line'
[key: string]: unknown
}
export interface GraphData {
nodes: GraphNode[]
edges: GraphEdge[]
}
export const GraphContainer: React.FC<{
data: GraphData
layout?: string
showToolbar?: boolean
showLegend?: boolean
showMinimap?: boolean
nodeTypes?: Array<{ type: string; color: string; label: string }>
onNodeClick?: (node: GraphNode) => void
onNodeDoubleClick?: (node: GraphNode) => void
enableSelection?: boolean
selectionMode?: string
theme?: Record<string, unknown>
height?: string | number
}>
}
declare module '*.css' {}
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './app.css'
import App from './App'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
+121
View File
@@ -0,0 +1,121 @@
export interface ProjectInfo {
name: string
description: string
entity_count: number
relation_count: number
}
export interface Entity {
id: string
name: string
type_ref: string
status: string
description: string
domain: string
tags: string[]
source: string
metadata: Record<string, unknown>
notes: string
created_at: string
updated_at: string
}
export interface Relation {
id: string
name: string
from_entity: string
to_entity: string
via: string
description: string
purity: string
direction: string
weight: number | null
status: string
tags: string[]
notes: string
created_at: string
updated_at: string
}
export interface EntityTypePreset {
type_ref: string
label: string
color: string
metadata_fields: string[]
}
export interface GraphData {
nodes: GraphNode[]
edges: GraphEdge[]
}
export interface GraphNode {
id: string
label: string
type: string
color: string
size: number
x: number
y: number
extra?: Record<string, unknown>
}
export interface GraphEdge {
id: string
source: string
target: string
label: string
color: string
size: number
type: string
}
export interface EntityInput {
name: string
type_ref: string
description: string
tags: string[]
metadata: Record<string, unknown>
notes: string
}
export interface RelationInputDTO {
name: string
from_entity: string
to_entity: string
description: string
weight: number | null
tags: string[]
notes: string
}
export interface Assertion {
id: string
entity_id: string
name: string
kind: string
rule: string
severity: string
description: string
active: boolean
created_at: string
}
export interface AssertionResult {
id: string
assertion_id: string
execution_id: string
status: string
value: Record<string, unknown>
message: string
evaluated_at: string
}
export interface AssertionInput {
entity_id: string
name: string
kind: string
rule: string
severity: string
description: string
}
+54
View File
@@ -0,0 +1,54 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {main} from '../models';
import {fn_operations} from '../models';
export function AddAssertion(arg1:main.AssertionInput):Promise<string>;
export function AddEntity(arg1:main.EntityInput):Promise<string>;
export function AddRelation(arg1:main.RelationInputDTO):Promise<string>;
export function CreateProject(arg1:string):Promise<main.ProjectInfo>;
export function DeleteAssertion(arg1:string):Promise<void>;
export function DeleteEntity(arg1:string):Promise<void>;
export function DeleteProject(arg1:string):Promise<void>;
export function DeleteRelation(arg1:string):Promise<void>;
export function EvalAssertions(arg1:string):Promise<Array<fn_operations.AssertionResult>>;
export function GetCurrentProject():Promise<string>;
export function GetEntity(arg1:string):Promise<fn_operations.Entity>;
export function GetEntityNeighbors(arg1:string,arg2:number):Promise<main.GraphData>;
export function GetEntityPresets():Promise<Array<main.EntityTypePreset>>;
export function GetFilteredGraph(arg1:Array<string>):Promise<main.GraphData>;
export function GetGraphData():Promise<main.GraphData>;
export function GetRelationPresets():Promise<Array<string>>;
export function ListAssertions(arg1:string):Promise<Array<fn_operations.Assertion>>;
export function ListEntities():Promise<Array<fn_operations.Entity>>;
export function ListProjects():Promise<Array<main.ProjectInfo>>;
export function ListRelations():Promise<Array<fn_operations.Relation>>;
export function SearchEntities(arg1:string):Promise<Array<fn_operations.Entity>>;
export function SearchGraph(arg1:string):Promise<main.GraphData>;
export function SwitchProject(arg1:string):Promise<void>;
export function UpdateEntity(arg1:string,arg2:main.EntityInput):Promise<void>;
export function UpdateRelation(arg1:string,arg2:main.RelationInputDTO):Promise<void>;
+103
View File
@@ -0,0 +1,103 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function AddAssertion(arg1) {
return window['go']['main']['App']['AddAssertion'](arg1);
}
export function AddEntity(arg1) {
return window['go']['main']['App']['AddEntity'](arg1);
}
export function AddRelation(arg1) {
return window['go']['main']['App']['AddRelation'](arg1);
}
export function CreateProject(arg1) {
return window['go']['main']['App']['CreateProject'](arg1);
}
export function DeleteAssertion(arg1) {
return window['go']['main']['App']['DeleteAssertion'](arg1);
}
export function DeleteEntity(arg1) {
return window['go']['main']['App']['DeleteEntity'](arg1);
}
export function DeleteProject(arg1) {
return window['go']['main']['App']['DeleteProject'](arg1);
}
export function DeleteRelation(arg1) {
return window['go']['main']['App']['DeleteRelation'](arg1);
}
export function EvalAssertions(arg1) {
return window['go']['main']['App']['EvalAssertions'](arg1);
}
export function GetCurrentProject() {
return window['go']['main']['App']['GetCurrentProject']();
}
export function GetEntity(arg1) {
return window['go']['main']['App']['GetEntity'](arg1);
}
export function GetEntityNeighbors(arg1, arg2) {
return window['go']['main']['App']['GetEntityNeighbors'](arg1, arg2);
}
export function GetEntityPresets() {
return window['go']['main']['App']['GetEntityPresets']();
}
export function GetFilteredGraph(arg1) {
return window['go']['main']['App']['GetFilteredGraph'](arg1);
}
export function GetGraphData() {
return window['go']['main']['App']['GetGraphData']();
}
export function GetRelationPresets() {
return window['go']['main']['App']['GetRelationPresets']();
}
export function ListAssertions(arg1) {
return window['go']['main']['App']['ListAssertions'](arg1);
}
export function ListEntities() {
return window['go']['main']['App']['ListEntities']();
}
export function ListProjects() {
return window['go']['main']['App']['ListProjects']();
}
export function ListRelations() {
return window['go']['main']['App']['ListRelations']();
}
export function SearchEntities(arg1) {
return window['go']['main']['App']['SearchEntities'](arg1);
}
export function SearchGraph(arg1) {
return window['go']['main']['App']['SearchGraph'](arg1);
}
export function SwitchProject(arg1) {
return window['go']['main']['App']['SwitchProject'](arg1);
}
export function UpdateEntity(arg1, arg2) {
return window['go']['main']['App']['UpdateEntity'](arg1, arg2);
}
export function UpdateRelation(arg1, arg2) {
return window['go']['main']['App']['UpdateRelation'](arg1, arg2);
}
+408
View File
@@ -0,0 +1,408 @@
export namespace fn_operations {
export class Assertion {
id: string;
entity_id: string;
name: string;
kind: string;
rule: string;
severity: string;
description: string;
active: boolean;
// Go type: time
created_at: any;
static createFrom(source: any = {}) {
return new Assertion(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.entity_id = source["entity_id"];
this.name = source["name"];
this.kind = source["kind"];
this.rule = source["rule"];
this.severity = source["severity"];
this.description = source["description"];
this.active = source["active"];
this.created_at = this.convertValues(source["created_at"], null);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class AssertionResult {
id: string;
assertion_id: string;
execution_id: string;
status: string;
value: Record<string, any>;
message: string;
// Go type: time
evaluated_at: any;
static createFrom(source: any = {}) {
return new AssertionResult(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.assertion_id = source["assertion_id"];
this.execution_id = source["execution_id"];
this.status = source["status"];
this.value = source["value"];
this.message = source["message"];
this.evaluated_at = this.convertValues(source["evaluated_at"], null);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class Entity {
id: string;
name: string;
type_ref: string;
status: string;
description: string;
domain: string;
tags: string[];
source: string;
metadata: Record<string, any>;
notes: string;
// Go type: time
created_at: any;
// Go type: time
updated_at: any;
static createFrom(source: any = {}) {
return new Entity(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.name = source["name"];
this.type_ref = source["type_ref"];
this.status = source["status"];
this.description = source["description"];
this.domain = source["domain"];
this.tags = source["tags"];
this.source = source["source"];
this.metadata = source["metadata"];
this.notes = source["notes"];
this.created_at = this.convertValues(source["created_at"], null);
this.updated_at = this.convertValues(source["updated_at"], null);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class Relation {
id: string;
name: string;
from_entity: string;
to_entity: string;
via: string;
description: string;
purity: string;
direction: string;
weight?: number;
status: string;
// Go type: time
started_at?: any;
// Go type: time
ended_at?: any;
order?: number;
tags: string[];
notes: string;
// Go type: time
created_at: any;
// Go type: time
updated_at: any;
static createFrom(source: any = {}) {
return new Relation(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.name = source["name"];
this.from_entity = source["from_entity"];
this.to_entity = source["to_entity"];
this.via = source["via"];
this.description = source["description"];
this.purity = source["purity"];
this.direction = source["direction"];
this.weight = source["weight"];
this.status = source["status"];
this.started_at = this.convertValues(source["started_at"], null);
this.ended_at = this.convertValues(source["ended_at"], null);
this.order = source["order"];
this.tags = source["tags"];
this.notes = source["notes"];
this.created_at = this.convertValues(source["created_at"], null);
this.updated_at = this.convertValues(source["updated_at"], null);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
export namespace main {
export class AssertionInput {
entity_id: string;
name: string;
kind: string;
rule: string;
severity: string;
description: string;
static createFrom(source: any = {}) {
return new AssertionInput(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.entity_id = source["entity_id"];
this.name = source["name"];
this.kind = source["kind"];
this.rule = source["rule"];
this.severity = source["severity"];
this.description = source["description"];
}
}
export class EntityInput {
name: string;
type_ref: string;
description: string;
tags: string[];
metadata: Record<string, any>;
notes: string;
static createFrom(source: any = {}) {
return new EntityInput(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.type_ref = source["type_ref"];
this.description = source["description"];
this.tags = source["tags"];
this.metadata = source["metadata"];
this.notes = source["notes"];
}
}
export class EntityTypePreset {
type_ref: string;
label: string;
color: string;
metadata_fields: string[];
static createFrom(source: any = {}) {
return new EntityTypePreset(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.type_ref = source["type_ref"];
this.label = source["label"];
this.color = source["color"];
this.metadata_fields = source["metadata_fields"];
}
}
export class GraphEdge {
id: string;
source: string;
target: string;
label: string;
color: string;
size: number;
type: string;
static createFrom(source: any = {}) {
return new GraphEdge(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.source = source["source"];
this.target = source["target"];
this.label = source["label"];
this.color = source["color"];
this.size = source["size"];
this.type = source["type"];
}
}
export class GraphNode {
id: string;
label: string;
type: string;
color: string;
size: number;
x: number;
y: number;
extra?: Record<string, any>;
static createFrom(source: any = {}) {
return new GraphNode(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.label = source["label"];
this.type = source["type"];
this.color = source["color"];
this.size = source["size"];
this.x = source["x"];
this.y = source["y"];
this.extra = source["extra"];
}
}
export class GraphData {
nodes: GraphNode[];
edges: GraphEdge[];
static createFrom(source: any = {}) {
return new GraphData(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.nodes = this.convertValues(source["nodes"], GraphNode);
this.edges = this.convertValues(source["edges"], GraphEdge);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class ProjectInfo {
name: string;
description: string;
entity_count: number;
relation_count: number;
static createFrom(source: any = {}) {
return new ProjectInfo(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.description = source["description"];
this.entity_count = source["entity_count"];
this.relation_count = source["relation_count"];
}
}
export class RelationInputDTO {
name: string;
from_entity: string;
to_entity: string;
description: string;
weight?: number;
tags: string[];
notes: string;
static createFrom(source: any = {}) {
return new RelationInputDTO(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.from_entity = source["from_entity"];
this.to_entity = source["to_entity"];
this.description = source["description"];
this.weight = source["weight"];
this.tags = source["tags"];
this.notes = source["notes"];
}
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@wailsapp/runtime",
"version": "2.0.0",
"description": "Wails Javascript runtime library",
"main": "runtime.js",
"types": "runtime.d.ts",
"scripts": {
},
"repository": {
"type": "git",
"url": "git+https://github.com/wailsapp/wails.git"
},
"keywords": [
"Wails",
"Javascript",
"Go"
],
"author": "Lea Anthony <lea.anthony@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/wails/issues"
},
"homepage": "https://github.com/wailsapp/wails#readme"
}
+249
View File
@@ -0,0 +1,249 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export interface Position {
x: number;
y: number;
}
export interface Size {
w: number;
h: number;
}
export interface Screen {
isCurrent: boolean;
isPrimary: boolean;
width : number
height : number
}
// Environment information such as platform, buildtype, ...
export interface EnvironmentInfo {
buildType: string;
platform: string;
arch: string;
}
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
// emits the given event. Optional data may be passed with the event.
// This will trigger any event listeners.
export function EventsEmit(eventName: string, ...data: any): void;
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
// sets up a listener for the given event name, but will only trigger a given number times.
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
// sets up a listener for the given event name, but will only trigger once.
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
// unregisters the listener for the given event name.
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
// unregisters all listeners.
export function EventsOffAll(): void;
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
// logs the given message as a raw message
export function LogPrint(message: string): void;
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
// logs the given message at the `trace` log level.
export function LogTrace(message: string): void;
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
// logs the given message at the `debug` log level.
export function LogDebug(message: string): void;
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
// logs the given message at the `error` log level.
export function LogError(message: string): void;
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
// logs the given message at the `fatal` log level.
// The application will quit after calling this method.
export function LogFatal(message: string): void;
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
// logs the given message at the `info` log level.
export function LogInfo(message: string): void;
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
// logs the given message at the `warning` log level.
export function LogWarning(message: string): void;
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
// Forces a reload by the main application as well as connected browsers.
export function WindowReload(): void;
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
// Reloads the application frontend.
export function WindowReloadApp(): void;
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
// Sets the window AlwaysOnTop or not on top.
export function WindowSetAlwaysOnTop(b: boolean): void;
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
// *Windows only*
// Sets window theme to system default (dark/light).
export function WindowSetSystemDefaultTheme(): void;
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
// *Windows only*
// Sets window to light theme.
export function WindowSetLightTheme(): void;
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
// *Windows only*
// Sets window to dark theme.
export function WindowSetDarkTheme(): void;
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
// Centers the window on the monitor the window is currently on.
export function WindowCenter(): void;
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
// Sets the text in the window title bar.
export function WindowSetTitle(title: string): void;
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
// Makes the window full screen.
export function WindowFullscreen(): void;
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
// Restores the previous window dimensions and position prior to full screen.
export function WindowUnfullscreen(): void;
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
export function WindowIsFullscreen(): Promise<boolean>;
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window.
export function WindowSetSize(width: number, height: number): void;
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
// Gets the width and height of the window.
export function WindowGetSize(): Promise<Size>;
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMaxSize(width: number, height: number): void;
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMinSize(width: number, height: number): void;
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
// Sets the window position relative to the monitor the window is currently on.
export function WindowSetPosition(x: number, y: number): void;
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
// Gets the window position relative to the monitor the window is currently on.
export function WindowGetPosition(): Promise<Position>;
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
// Hides the window.
export function WindowHide(): void;
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
// Shows the window, if it is currently hidden.
export function WindowShow(): void;
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
// Maximises the window to fill the screen.
export function WindowMaximise(): void;
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
// Toggles between Maximised and UnMaximised.
export function WindowToggleMaximise(): void;
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
// Restores the window to the dimensions and position prior to maximising.
export function WindowUnmaximise(): void;
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
// Returns the state of the window, i.e. whether the window is maximised or not.
export function WindowIsMaximised(): Promise<boolean>;
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
// Minimises the window.
export function WindowMinimise(): void;
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
// Restores the window to the dimensions and position prior to minimising.
export function WindowUnminimise(): void;
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
// Returns the state of the window, i.e. whether the window is minimised or not.
export function WindowIsMinimised(): Promise<boolean>;
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
// Returns the state of the window, i.e. whether the window is normal or not.
export function WindowIsNormal(): Promise<boolean>;
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
export function ScreenGetAll(): Promise<Screen[]>;
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
// Opens the given URL in the system browser.
export function BrowserOpenURL(url: string): void;
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
// Returns information about the environment
export function Environment(): Promise<EnvironmentInfo>;
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
// Quits the application.
export function Quit(): void;
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
// Hides the application.
export function Hide(): void;
// [Show](https://wails.io/docs/reference/runtime/intro#show)
// Shows the application.
export function Show(): void;
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
// Returns the current text stored on clipboard
export function ClipboardGetText(): Promise<string>;
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
// Sets a text on the clipboard
export function ClipboardSetText(text: string): Promise<boolean>;
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
// OnFileDropOff removes the drag and drop listeners and handlers.
export function OnFileDropOff() :void
// Check if the file path resolver is available
export function CanResolveFilePaths(): boolean;
// Resolves file paths for an array of files
export function ResolveFilePaths(files: File[]): void
+242
View File
@@ -0,0 +1,242 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export function LogPrint(message) {
window.runtime.LogPrint(message);
}
export function LogTrace(message) {
window.runtime.LogTrace(message);
}
export function LogDebug(message) {
window.runtime.LogDebug(message);
}
export function LogInfo(message) {
window.runtime.LogInfo(message);
}
export function LogWarning(message) {
window.runtime.LogWarning(message);
}
export function LogError(message) {
window.runtime.LogError(message);
}
export function LogFatal(message) {
window.runtime.LogFatal(message);
}
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
}
export function EventsOn(eventName, callback) {
return EventsOnMultiple(eventName, callback, -1);
}
export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}
export function EventsOffAll() {
return window.runtime.EventsOffAll();
}
export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
export function EventsEmit(eventName) {
let args = [eventName].slice.call(arguments);
return window.runtime.EventsEmit.apply(null, args);
}
export function WindowReload() {
window.runtime.WindowReload();
}
export function WindowReloadApp() {
window.runtime.WindowReloadApp();
}
export function WindowSetAlwaysOnTop(b) {
window.runtime.WindowSetAlwaysOnTop(b);
}
export function WindowSetSystemDefaultTheme() {
window.runtime.WindowSetSystemDefaultTheme();
}
export function WindowSetLightTheme() {
window.runtime.WindowSetLightTheme();
}
export function WindowSetDarkTheme() {
window.runtime.WindowSetDarkTheme();
}
export function WindowCenter() {
window.runtime.WindowCenter();
}
export function WindowSetTitle(title) {
window.runtime.WindowSetTitle(title);
}
export function WindowFullscreen() {
window.runtime.WindowFullscreen();
}
export function WindowUnfullscreen() {
window.runtime.WindowUnfullscreen();
}
export function WindowIsFullscreen() {
return window.runtime.WindowIsFullscreen();
}
export function WindowGetSize() {
return window.runtime.WindowGetSize();
}
export function WindowSetSize(width, height) {
window.runtime.WindowSetSize(width, height);
}
export function WindowSetMaxSize(width, height) {
window.runtime.WindowSetMaxSize(width, height);
}
export function WindowSetMinSize(width, height) {
window.runtime.WindowSetMinSize(width, height);
}
export function WindowSetPosition(x, y) {
window.runtime.WindowSetPosition(x, y);
}
export function WindowGetPosition() {
return window.runtime.WindowGetPosition();
}
export function WindowHide() {
window.runtime.WindowHide();
}
export function WindowShow() {
window.runtime.WindowShow();
}
export function WindowMaximise() {
window.runtime.WindowMaximise();
}
export function WindowToggleMaximise() {
window.runtime.WindowToggleMaximise();
}
export function WindowUnmaximise() {
window.runtime.WindowUnmaximise();
}
export function WindowIsMaximised() {
return window.runtime.WindowIsMaximised();
}
export function WindowMinimise() {
window.runtime.WindowMinimise();
}
export function WindowUnminimise() {
window.runtime.WindowUnminimise();
}
export function WindowSetBackgroundColour(R, G, B, A) {
window.runtime.WindowSetBackgroundColour(R, G, B, A);
}
export function ScreenGetAll() {
return window.runtime.ScreenGetAll();
}
export function WindowIsMinimised() {
return window.runtime.WindowIsMinimised();
}
export function WindowIsNormal() {
return window.runtime.WindowIsNormal();
}
export function BrowserOpenURL(url) {
window.runtime.BrowserOpenURL(url);
}
export function Environment() {
return window.runtime.Environment();
}
export function Quit() {
window.runtime.Quit();
}
export function Hide() {
window.runtime.Hide();
}
export function Show() {
window.runtime.Show();
}
export function ClipboardGetText() {
return window.runtime.ClipboardGetText();
}
export function ClipboardSetText(text) {
return window.runtime.ClipboardSetText(text);
}
/**
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
*
* @export
* @callback OnFileDropCallback
* @param {number} x - x coordinate of the drop
* @param {number} y - y coordinate of the drop
* @param {string[]} paths - A list of file paths.
*/
/**
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
*
* @export
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
*/
export function OnFileDrop(callback, useDropTarget) {
return window.runtime.OnFileDrop(callback, useDropTarget);
}
/**
* OnFileDropOff removes the drag and drop listeners and handlers.
*/
export function OnFileDropOff() {
return window.runtime.OnFileDropOff();
}
export function CanResolveFilePaths() {
return window.runtime.CanResolveFilePaths();
}
export function ResolveFilePaths(files) {
return window.runtime.ResolveFilePaths(files);
}
+28
View File
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@wails/*": ["./src/wailsjs/*"],
"@fn_library": ["./src/declarations.d.ts"],
"@graph": ["./src/declarations.d.ts"]
}
},
"include": ["src"]
}
+1
View File
@@ -0,0 +1 @@
{"root":["./src/App.tsx","./src/declarations.d.ts","./src/main.tsx","./src/types.ts","./src/components/AssertionPanel.tsx","./src/components/EntityDetail.tsx","./src/components/EntityDialog.tsx","./src/components/EntityTable.tsx","./src/components/GraphView.tsx","./src/components/ProjectSidebar.tsx","./src/components/RelationDialog.tsx","./src/components/RelationTable.tsx","./src/components/SearchBar.tsx","./src/lib/utils.ts","./src/wailsjs/go/models.ts","./src/wailsjs/go/main/App.d.ts","./src/wailsjs/runtime/runtime.d.ts"],"version":"5.9.3"}
+20
View File
@@ -0,0 +1,20 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import { resolve } from 'path'
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'@wails': resolve(__dirname, './src/wailsjs'),
'@fn_library': resolve(__dirname, '../../../frontend/functions/ui'),
'@graph': resolve(__dirname, '../../../frontend/functions/ui/graph'),
},
dedupe: ['react', 'react-dom'],
},
server: {
port: 5174,
},
})