FossilRepo

fossilrepo / templates / fossil / explorer.html
1
{% extends "base.html" %}
2
{% block title %}Explorer — {{ project.name }} — Fossilrepo{% endblock %}
3
4
{% block content %}
5
<h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1>
6
{% include "fossil/_project_nav.html" %}
7
8
<div class="flex items-center justify-between mb-6">
9
<div>
10
<h2 class="text-xl font-bold text-gray-100">SQLite Schema Explorer</h2>
11
<p class="text-sm text-gray-500 mt-1">
12
Explore the .fossil SQLite database structure. {{ tables|length }} table{{ tables|length|pluralize }} found.
13
</p>
14
</div>
15
<a href="{% url 'fossil:explorer_query' slug=project.slug %}"
16
class="inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md bg-brand text-white hover:bg-brand-hover">
17
Run Query
18
</a>
19
</div>
20
21
<!-- Schema Relationship Visualization -->
22
<div class="rounded-lg bg-gray-800 border border-gray-700 p-4 mb-6" x-data="{ showViz: true }">
23
<div class="flex items-center justify-between mb-3">
24
<h3 class="text-sm font-medium text-gray-300">Schema Map</h3>
25
<button @click="showViz = !showViz" class="text-xs text-gray-500 hover:text-gray-300">
26
<span x-text="showViz ? 'Hide' : 'Show'"></span>
27
</button>
28
</div>
29
<div x-show="showViz" x-transition>
30
<!-- Category legend -->
31
<div class="flex flex-wrap gap-3 mb-4 text-xs">
32
<span class="flex items-center gap-1.5">
33
<span class="w-2.5 h-2.5 rounded-full bg-blue-500"></span>
34
<span class="text-gray-400">VCS Core</span>
35
</span>
36
<span class="flex items-center gap-1.5">
37
<span class="w-2.5 h-2.5 rounded-full bg-indigo-500"></span>
38
<span class="text-gray-400">Tags / Branches</span>
39
</span>
40
<span class="flex items-center gap-1.5">
41
<span class="w-2.5 h-2.5 rounded-full bg-green-500"></span>
42
<span class="text-gray-400">Tickets</span>
43
</span>
44
<span class="flex items-center gap-1.5">
45
<span class="w-2.5 h-2.5 rounded-full bg-purple-500"></span>
46
<span class="text-gray-400">Wiki</span>
47
</span>
48
<span class="flex items-center gap-1.5">
49
<span class="w-2.5 h-2.5 rounded-full bg-orange-500"></span>
50
<span class="text-gray-400">Forum</span>
51
</span>
52
<span class="flex items-center gap-1.5">
53
<span class="w-2.5 h-2.5 rounded-full bg-gray-500"></span>
54
<span class="text-gray-400">Other</span>
55
</span>
56
</div>
57
58
<!-- SVG relationship diagram -->
59
<div class="overflow-x-auto">
60
<svg id="schema-viz" class="w-full" style="min-height: 280px;"></svg>
61
</div>
62
</div>
63
</div>
64
65
<!-- Table grid -->
66
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 mb-6" x-data="{ selected: '' }">
67
{% for table in tables %}
68
<button
69
@click="selected = selected === '{{ table.name }}' ? '' : '{{ table.name }}'"
70
hx-get="{% url 'fossil:explorer_table' slug=project.slug table_name=table.name %}"
71
hx-target="#table-detail"
72
hx-swap="innerHTML"
73
class="text-left rounded-lg border p-3 transition-colors cursor-pointer
74
{% if table.category == 'blue' %}border-blue-800/50 hover:border-blue-600{% elif table.category == 'indigo' %}border-indigo-800/50 hover:border-indigo-600{% elif table.category == 'green' %}border-green-800/50 hover:border-green-600{% elif table.category == 'purple' %}border-purple-800/50 hover:border-purple-600{% elif table.category == 'orange' %}border-orange-800/50 hover:border-orange-600{% else %}border-gray-700 hover:border-gray-500{% endif %}"
75
:class="selected === '{{ table.name }}' ? 'bg-gray-700 ring-1 ring-brand' : 'bg-gray-800'">
76
<div class="flex items-center justify-between">
77
<span class="text-sm font-mono font-medium
78
{% if table.category == 'blue' %}text-blue-300{% elif table.category == 'indigo' %}text-indigo-300{% elif table.category == 'green' %}text-green-300{% elif table.category == 'purple' %}text-purple-300{% elif table.category == 'orange' %}text-orange-300{% else %}text-gray-300{% endif %}">
79
{{ table.name }}
80
</span>
81
<span class="text-xs text-gray-500 font-mono">{{ table.count }}</span>
82
</div>
83
</button>
84
{% endfor %}
85
</div>
86
87
<!-- Table detail panel (populated by HTMX) -->
88
<div id="table-detail"></div>
89
90
<script>
91
(function() {
92
var tables = {{ tables_json|safe }};
93
var rels = {{ relationships_json|safe }};
94
95
var categoryColor = {
96
blue: '#3b82f6', indigo: '#6366f1', green: '#22c55e',
97
purple: '#a855f7', orange: '#f97316', gray: '#6b7280'
98
};
99
100
var svg = document.getElementById('schema-viz');
101
if (!svg || tables.length === 0) return;
102
103
// Layout: arrange tables in a grid, center the important ones.
104
var cols = Math.min(tables.length, 8);
105
var cellW = 130, cellH = 50, padX = 20, padY = 30;
106
var totalW = cols * cellW + (cols - 1) * padX;
107
var rows = Math.ceil(tables.length / cols);
108
var totalH = rows * cellH + (rows - 1) * padY;
109
110
svg.setAttribute('viewBox', '0 0 ' + (totalW + 40) + ' ' + (totalH + 40));
111
svg.style.minHeight = Math.max(200, totalH + 40) + 'px';
112
113
// Position each table.
114
var positions = {};
115
tables.forEach(function(t, i) {
116
var col = i % cols;
117
var row = Math.floor(i / cols);
118
var x = 20 + col * (cellW + padX);
119
var y = 20 + row * (cellH + padY);
120
positions[t.name] = { x: x + cellW / 2, y: y + cellH / 2 };
121
122
// Draw table box.
123
var color = categoryColor[t.category] || categoryColor.gray;
124
var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
125
rect.setAttribute('x', x);
126
rect.setAttribute('y', y);
127
rect.setAttribute('width', cellW);
128
rect.setAttribute('height', cellH);
129
rect.setAttribute('rx', 6);
130
rect.setAttribute('fill', '#1f2937');
131
rect.setAttribute('stroke', color);
132
rect.setAttribute('stroke-width', 1.5);
133
rect.setAttribute('stroke-opacity', 0.6);
134
svg.appendChild(rect);
135
136
// Table name.
137
var text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
138
text.setAttribute('x', x + cellW / 2);
139
text.setAttribute('y', y + 22);
140
text.setAttribute('text-anchor', 'middle');
141
text.setAttribute('fill', color);
142
text.setAttribute('font-size', 11);
143
text.setAttribute('font-family', 'ui-monospace, monospace');
144
text.setAttribute('font-weight', 600);
145
text.textContent = t.name;
146
svg.appendChild(text);
147
148
// Row count.
149
var cnt = document.createElementNS('http://www.w3.org/2000/svg', 'text');
150
cnt.setAttribute('x', x + cellW / 2);
151
cnt.setAttribute('y', y + 38);
152
cnt.setAttribute('text-anchor', 'middle');
153
cnt.setAttribute('fill', '#6b7280');
154
cnt.setAttribute('font-size', 9);
155
cnt.setAttribute('font-family', 'ui-monospace, monospace');
156
cnt.textContent = t.count.toLocaleString() + ' rows';
157
svg.appendChild(cnt);
158
});
159
160
// Draw relationship lines.
161
rels.forEach(function(r) {
162
var from = positions[r[0]];
163
var to = positions[r[1]];
164
if (!from || !to) return;
165
166
var line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
167
line.setAttribute('x1', from.x);
168
line.setAttribute('y1', from.y);
169
line.setAttribute('x2', to.x);
170
line.setAttribute('y2', to.y);
171
line.setAttribute('stroke', '#4b5563');
172
line.setAttribute('stroke-width', 1);
173
line.setAttribute('stroke-opacity', 0.5);
174
line.setAttribute('stroke-dasharray', '4,3');
175
svg.insertBefore(line, svg.firstChild);
176
177
// Midpoint label.
178
var mx = (from.x + to.x) / 2;
179
var my = (from.y + to.y) / 2;
180
var label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
181
label.setAttribute('x', mx);
182
label.setAttribute('y', my - 4);
183
label.setAttribute('text-anchor', 'middle');
184
label.setAttribute('fill', '#6b7280');
185
label.setAttribute('font-size', 8);
186
label.setAttribute('font-family', 'ui-monospace, monospace');
187
label.textContent = r[2];
188
svg.insertBefore(label, svg.firstChild);
189
});
190
})();
191
</script>
192
{% endblock %}
193

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button