init: fuzzygraph app from fn_registry
This commit is contained in:
+320
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
Vendored
+13
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
925e378ac695fa8339fd9576604d1d9f
|
||||
Generated
+1289
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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%"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Vendored
+61
@@ -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' {}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
@@ -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>,
|
||||
)
|
||||
@@ -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
@@ -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>;
|
||||
Executable
+103
@@ -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);
|
||||
}
|
||||
Executable
+408
@@ -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"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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"}
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user