archsight 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -0
  3. data/chart/archsight/Chart.yaml +6 -0
  4. data/chart/archsight/README.md +160 -0
  5. data/chart/archsight/templates/NOTES.txt +22 -0
  6. data/chart/archsight/templates/_helpers.tpl +62 -0
  7. data/chart/archsight/templates/deployment.yaml +114 -0
  8. data/chart/archsight/templates/ingress.yaml +56 -0
  9. data/chart/archsight/templates/resources-configmap.yaml +10 -0
  10. data/chart/archsight/templates/resources-pvc.yaml +23 -0
  11. data/chart/archsight/templates/service.yaml +15 -0
  12. data/chart/archsight/templates/serviceaccount.yaml +12 -0
  13. data/chart/archsight/values.yaml +162 -0
  14. data/lib/archsight/analysis/executor.rb +0 -10
  15. data/lib/archsight/annotations/annotation.rb +85 -36
  16. data/lib/archsight/annotations/architecture_annotations.rb +1 -34
  17. data/lib/archsight/annotations/computed.rb +1 -1
  18. data/lib/archsight/annotations/generated_annotations.rb +9 -2
  19. data/lib/archsight/annotations/git_annotations.rb +8 -4
  20. data/lib/archsight/annotations/interface_annotations.rb +35 -0
  21. data/lib/archsight/cli.rb +3 -1
  22. data/lib/archsight/editor/content_hasher.rb +37 -0
  23. data/lib/archsight/editor/file_writer.rb +79 -0
  24. data/lib/archsight/editor.rb +237 -0
  25. data/lib/archsight/import/handlers/github.rb +14 -6
  26. data/lib/archsight/import/handlers/gitlab.rb +14 -6
  27. data/lib/archsight/import/handlers/repository.rb +3 -1
  28. data/lib/archsight/import/team_matcher.rb +111 -61
  29. data/lib/archsight/mcp/execute_analysis_tool.rb +100 -0
  30. data/lib/archsight/mcp.rb +1 -0
  31. data/lib/archsight/resources/analysis.rb +1 -17
  32. data/lib/archsight/resources/application_interface.rb +1 -5
  33. data/lib/archsight/resources/base.rb +14 -14
  34. data/lib/archsight/resources/business_actor.rb +1 -1
  35. data/lib/archsight/resources/technology_interface.rb +1 -1
  36. data/lib/archsight/resources/technology_service.rb +5 -0
  37. data/lib/archsight/version.rb +1 -1
  38. data/lib/archsight/web/application.rb +8 -0
  39. data/lib/archsight/web/doc/import.md +10 -2
  40. data/lib/archsight/web/editor/form_builder.rb +100 -0
  41. data/lib/archsight/web/editor/routes.rb +293 -0
  42. data/lib/archsight/web/public/css/editor.css +863 -0
  43. data/lib/archsight/web/public/css/instance.css +6 -0
  44. data/lib/archsight/web/public/js/editor.js +421 -0
  45. data/lib/archsight/web/public/js/lexical-editor.js +308 -0
  46. data/lib/archsight/web/views/partials/editor/_field.haml +80 -0
  47. data/lib/archsight/web/views/partials/editor/_form.haml +131 -0
  48. data/lib/archsight/web/views/partials/editor/_relations.haml +39 -0
  49. data/lib/archsight/web/views/partials/editor/_yaml_output.haml +33 -0
  50. data/lib/archsight/web/views/partials/instance/_analysis_detail.haml +4 -11
  51. data/lib/archsight/web/views/partials/instance/_detail.haml +4 -0
  52. data/lib/archsight/web/views/partials/layout/_content.haml +8 -2
  53. data/lib/archsight/web/views/partials/layout/_head.haml +2 -0
  54. metadata +26 -1
@@ -383,10 +383,16 @@
383
383
  margin: 0;
384
384
  white-space: pre;
385
385
  overflow-x: auto;
386
+ min-height: 300px;
387
+ max-height: 600px;
388
+ overflow-y: auto;
386
389
  }
387
390
 
388
391
  .analysis-script pre.code code {
389
392
  white-space: pre;
393
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
394
+ font-size: 0.9em;
395
+ line-height: 1.5;
390
396
  }
391
397
 
392
398
  .analysis-execution header {
@@ -0,0 +1,421 @@
1
+ // Editor JavaScript - Form handling, relation management, and markdown editor
2
+
3
+ // Import Lexical editor module (only when needed)
4
+ let lexicalModule = null;
5
+ let lexicalInitialized = false;
6
+ let currentTextareaId = null;
7
+
8
+ // Lazy load Lexical module
9
+ async function loadLexicalModule() {
10
+ if (!lexicalModule) {
11
+ lexicalModule = await import('./lexical-editor.js');
12
+ }
13
+ return lexicalModule;
14
+ }
15
+
16
+ // Open the markdown editor overlay
17
+ async function openMarkdownEditor(textareaId) {
18
+ const overlay = document.getElementById('markdown-editor-overlay');
19
+ const textarea = document.getElementById(textareaId);
20
+
21
+ if (!overlay || !textarea) return;
22
+
23
+ currentTextareaId = textareaId;
24
+
25
+ // Load Lexical module if not already loaded
26
+ const { initLexicalEditor, setLexicalMarkdown } = await loadLexicalModule();
27
+
28
+ // Initialize Lexical if not already done
29
+ if (!lexicalInitialized) {
30
+ const editorRoot = document.getElementById('lexical-editor-root');
31
+ if (editorRoot) {
32
+ initLexicalEditor(editorRoot);
33
+ lexicalInitialized = true;
34
+ }
35
+ }
36
+
37
+ // Load content from textarea
38
+ setLexicalMarkdown(textarea.value);
39
+
40
+ // Show overlay
41
+ overlay.classList.remove('hidden');
42
+ document.body.style.overflow = 'hidden';
43
+ }
44
+
45
+ // Save content from Lexical editor back to textarea
46
+ async function saveMarkdownEditor() {
47
+ const textarea = document.getElementById(currentTextareaId);
48
+
49
+ if (textarea && lexicalModule) {
50
+ const markdown = await lexicalModule.getLexicalMarkdown();
51
+ textarea.value = markdown;
52
+ }
53
+
54
+ closeMarkdownEditor();
55
+ }
56
+
57
+ // Close the markdown editor overlay
58
+ function closeMarkdownEditor() {
59
+ const overlay = document.getElementById('markdown-editor-overlay');
60
+ if (overlay) {
61
+ overlay.classList.add('hidden');
62
+ }
63
+ document.body.style.overflow = '';
64
+ currentTextareaId = null;
65
+ }
66
+
67
+ // Format text (bold, italic, etc.)
68
+ function formatLexical(format) {
69
+ if (lexicalModule) {
70
+ lexicalModule.formatText(format);
71
+ }
72
+ }
73
+
74
+ // Format block (headings, lists, quotes)
75
+ function formatLexicalBlock(blockType) {
76
+ if (lexicalModule) {
77
+ lexicalModule.formatBlock(blockType);
78
+ }
79
+ }
80
+
81
+ // Insert a code block
82
+ function insertCodeBlock() {
83
+ if (lexicalModule) {
84
+ lexicalModule.insertCodeBlock('');
85
+ // Show language selector after inserting
86
+ setTimeout(() => updateCodeLanguageSelector(), 50);
87
+ }
88
+ }
89
+
90
+ // Apply language from the selector dropdown
91
+ function applyCodeLanguage(language) {
92
+ if (lexicalModule && language) {
93
+ lexicalModule.setCodeBlockLanguage(language);
94
+ }
95
+ hideCodeLanguageSelector();
96
+ }
97
+
98
+ // Show/hide the code language selector in toolbar
99
+ function updateCodeLanguageSelector() {
100
+ const picker = document.getElementById('code-language-picker');
101
+ if (!picker || !lexicalModule) return;
102
+
103
+ const currentLang = lexicalModule.getCurrentCodeLanguage();
104
+
105
+ if (currentLang !== null) {
106
+ // We're in a code block - show selector and set current value
107
+ picker.classList.remove('hidden');
108
+ picker.value = currentLang || '';
109
+ } else {
110
+ picker.classList.add('hidden');
111
+ }
112
+ }
113
+
114
+ function hideCodeLanguageSelector() {
115
+ const picker = document.getElementById('code-language-picker');
116
+ if (picker) {
117
+ picker.classList.add('hidden');
118
+ }
119
+ }
120
+
121
+ // Insert a link
122
+ function insertLink() {
123
+ if (lexicalModule) {
124
+ lexicalModule.insertLink();
125
+ }
126
+ }
127
+
128
+
129
+ // Expose markdown editor functions to global scope for onclick handlers
130
+ window.openMarkdownEditor = openMarkdownEditor;
131
+ window.saveMarkdownEditor = saveMarkdownEditor;
132
+ window.closeMarkdownEditor = closeMarkdownEditor;
133
+ window.formatLexical = formatLexical;
134
+ window.formatLexicalBlock = formatLexicalBlock;
135
+ window.insertCodeBlock = insertCodeBlock;
136
+ window.applyCodeLanguage = applyCodeLanguage;
137
+ window.insertLink = insertLink;
138
+
139
+ // Add a new relation from the inline form
140
+ function addRelation() {
141
+ const verbKindSelect = document.getElementById('new-relation-verb-kind');
142
+ const instanceSelect = document.getElementById('new-relation-instance');
143
+
144
+ if (!verbKindSelect || !instanceSelect) return;
145
+
146
+ const verbKind = verbKindSelect.value;
147
+ const instance = instanceSelect.value;
148
+
149
+ // Validate all fields are selected
150
+ if (!verbKind || !instance) return;
151
+
152
+ // Parse verb:kind format
153
+ const [verb, kind] = verbKind.split(':', 2);
154
+ if (!verb || !kind) return;
155
+
156
+ // Create relation item with hidden inputs
157
+ const item = document.createElement('div');
158
+ item.className = 'relation-item';
159
+ item.innerHTML = `
160
+ <span class="relation-text">
161
+ <strong>${escapeHtml(verb)}</strong> &rarr; ${escapeHtml(kind)}: <em>${escapeHtml(instance)}</em>
162
+ </span>
163
+ <input type="hidden" name="relations[][verb]" value="${escapeHtml(verb)}">
164
+ <input type="hidden" name="relations[][kind]" value="${escapeHtml(kind)}">
165
+ <input type="hidden" name="relations[][name]" value="${escapeHtml(instance)}">
166
+ <button class="btn-remove" type="button" onclick="removeRelation(this)" title="Remove">
167
+ <i class="iconoir-xmark"></i>
168
+ </button>
169
+ `;
170
+
171
+ // Add to list
172
+ const relationsList = document.getElementById('relations-list');
173
+ if (relationsList) {
174
+ relationsList.appendChild(item);
175
+ sortRelations();
176
+ }
177
+
178
+ // Clear the form
179
+ clearAddForm();
180
+ }
181
+
182
+ // Remove a relation from the list
183
+ function removeRelation(button) {
184
+ const item = button.closest('.relation-item');
185
+ if (item) {
186
+ item.remove();
187
+ }
188
+ }
189
+
190
+ // Sort relations alphabetically by verb, kind, instance
191
+ function sortRelations() {
192
+ const relationsList = document.getElementById('relations-list');
193
+ if (!relationsList) return;
194
+
195
+ const items = Array.from(relationsList.querySelectorAll('.relation-item'));
196
+ if (items.length === 0) return;
197
+
198
+ items.sort((a, b) => {
199
+ const getValues = (el) => {
200
+ const verb = el.querySelector('input[name="relations[][verb]"]')?.value || '';
201
+ const kind = el.querySelector('input[name="relations[][kind]"]')?.value || '';
202
+ const name = el.querySelector('input[name="relations[][name]"]')?.value || '';
203
+ return [verb, kind, name];
204
+ };
205
+
206
+ const [aVerb, aKind, aName] = getValues(a);
207
+ const [bVerb, bKind, bName] = getValues(b);
208
+
209
+ return aVerb.localeCompare(bVerb) ||
210
+ aKind.localeCompare(bKind) ||
211
+ aName.localeCompare(bName);
212
+ });
213
+
214
+ // Re-append in sorted order
215
+ items.forEach(item => relationsList.appendChild(item));
216
+ }
217
+
218
+ // Clear the add relation form
219
+ function clearAddForm() {
220
+ const verbKindSelect = document.getElementById('new-relation-verb-kind');
221
+ const instanceSelect = document.getElementById('new-relation-instance');
222
+
223
+ if (verbKindSelect) verbKindSelect.value = '';
224
+ if (instanceSelect) {
225
+ instanceSelect.innerHTML = '<option value="">Select instance...</option>';
226
+ }
227
+ }
228
+
229
+ // Update instance dropdown based on selected verb:kind
230
+ // Uses embedded data instead of HTMX API calls
231
+ function updateInstanceOptions() {
232
+ const verbKindSelect = document.getElementById('new-relation-verb-kind');
233
+ const instanceSelect = document.getElementById('new-relation-instance');
234
+ const editor = document.querySelector('.relations-editor');
235
+
236
+ if (!verbKindSelect || !instanceSelect || !editor) return;
237
+
238
+ // Clear and reset
239
+ instanceSelect.innerHTML = '<option value="">Select instance...</option>';
240
+
241
+ const verbKind = verbKindSelect.value;
242
+ if (!verbKind) return;
243
+
244
+ // Parse kind from verb:kind
245
+ const parts = verbKind.split(':');
246
+ if (parts.length < 2) return;
247
+ const kind = parts.slice(1).join(':'); // Handle kinds that might contain colons
248
+
249
+ // Get instances from embedded data
250
+ const instancesData = JSON.parse(editor.dataset.instances || '{}');
251
+ const instances = instancesData[kind] || [];
252
+
253
+ // Populate dropdown
254
+ instances.forEach(name => {
255
+ const option = document.createElement('option');
256
+ option.value = name;
257
+ option.textContent = name;
258
+ instanceSelect.appendChild(option);
259
+ });
260
+ }
261
+
262
+ // Escape HTML to prevent XSS
263
+ function escapeHtml(text) {
264
+ const div = document.createElement('div');
265
+ div.textContent = text;
266
+ return div.innerHTML;
267
+ }
268
+
269
+ // Expose relation functions to global scope for onclick handlers
270
+ window.addRelation = addRelation;
271
+ window.removeRelation = removeRelation;
272
+ window.updateInstanceOptions = updateInstanceOptions;
273
+
274
+ // Copy YAML to clipboard
275
+ document.addEventListener('DOMContentLoaded', function() {
276
+ const copyButton = document.getElementById('copy-yaml');
277
+ if (copyButton) {
278
+ copyButton.addEventListener('click', function() {
279
+ const yamlContent = document.getElementById('yaml-content');
280
+ if (!yamlContent) return;
281
+
282
+ const yaml = yamlContent.textContent;
283
+
284
+ navigator.clipboard.writeText(yaml).then(function() {
285
+ // Show success feedback
286
+ const originalText = copyButton.innerHTML;
287
+ copyButton.innerHTML = '<i class="iconoir-check"></i> Copied!';
288
+ copyButton.classList.add('copied');
289
+
290
+ setTimeout(function() {
291
+ copyButton.innerHTML = originalText;
292
+ copyButton.classList.remove('copied');
293
+ }, 2000);
294
+ }).catch(function(err) {
295
+ console.error('Failed to copy YAML:', err);
296
+ // Fallback for older browsers
297
+ const textarea = document.createElement('textarea');
298
+ textarea.value = yaml;
299
+ textarea.style.position = 'fixed';
300
+ textarea.style.opacity = '0';
301
+ document.body.appendChild(textarea);
302
+ textarea.select();
303
+ document.execCommand('copy');
304
+ document.body.removeChild(textarea);
305
+
306
+ const originalText = copyButton.innerHTML;
307
+ copyButton.innerHTML = '<i class="iconoir-check"></i> Copied!';
308
+ copyButton.classList.add('copied');
309
+
310
+ setTimeout(function() {
311
+ copyButton.innerHTML = originalText;
312
+ copyButton.classList.remove('copied');
313
+ }, 2000);
314
+ });
315
+ });
316
+ }
317
+
318
+ // Save YAML to source file (inline edit)
319
+ const saveButton = document.getElementById('save-yaml');
320
+ if (saveButton) {
321
+ saveButton.addEventListener('click', async function() {
322
+ const yamlContent = document.getElementById('yaml-content');
323
+ if (!yamlContent) return;
324
+
325
+ const yaml = yamlContent.textContent;
326
+ const kind = this.dataset.kind;
327
+ const name = this.dataset.name;
328
+ const contentHash = this.dataset.contentHash;
329
+ const originalText = this.innerHTML;
330
+
331
+ this.disabled = true;
332
+ this.innerHTML = '<i class="iconoir-refresh"></i> Saving...';
333
+
334
+ try {
335
+ const response = await fetch(`/api/v1/editor/kinds/${kind}/instances/${name}/save`, {
336
+ method: 'POST',
337
+ headers: { 'Content-Type': 'application/json' },
338
+ body: JSON.stringify({ yaml: yaml, content_hash: contentHash })
339
+ });
340
+
341
+ const result = await response.json();
342
+ if (result.success) {
343
+ this.innerHTML = '<i class="iconoir-check"></i> Saved!';
344
+ this.classList.add('saved');
345
+ setTimeout(() => {
346
+ this.innerHTML = originalText;
347
+ this.classList.remove('saved');
348
+ this.disabled = false;
349
+ }, 2000);
350
+ } else if (result.conflict) {
351
+ // Handle conflict with user-friendly message and reload option
352
+ this.innerHTML = originalText;
353
+ this.disabled = false;
354
+ showConflictError(result.error);
355
+ } else {
356
+ alert('Save failed: ' + result.error);
357
+ this.innerHTML = originalText;
358
+ this.disabled = false;
359
+ }
360
+ } catch (err) {
361
+ alert('Save failed: ' + err.message);
362
+ this.innerHTML = originalText;
363
+ this.disabled = false;
364
+ }
365
+ });
366
+ }
367
+
368
+ // Show conflict error
369
+ function showConflictError(message) {
370
+ const yamlOutput = document.querySelector('.yaml-output');
371
+ if (!yamlOutput) {
372
+ alert(message);
373
+ return;
374
+ }
375
+
376
+ // Check if error banner already exists
377
+ let errorBanner = document.getElementById('conflict-error');
378
+ if (!errorBanner) {
379
+ errorBanner = document.createElement('div');
380
+ errorBanner.id = 'conflict-error';
381
+ errorBanner.className = 'conflict-error';
382
+ // Insert before the article, not inside it
383
+ yamlOutput.parentNode.insertBefore(errorBanner, yamlOutput);
384
+ }
385
+
386
+ errorBanner.innerHTML = `
387
+ <i class="iconoir-warning-triangle"></i>
388
+ <span>${escapeHtml(message)}</span>
389
+ `;
390
+ }
391
+
392
+ // Close overlay when pressing Escape
393
+ document.addEventListener('keydown', function(event) {
394
+ if (event.key === 'Escape') {
395
+ const overlay = document.getElementById('markdown-editor-overlay');
396
+ if (overlay && !overlay.classList.contains('hidden')) {
397
+ closeMarkdownEditor();
398
+ }
399
+ }
400
+ });
401
+
402
+ // Track clicks in editor to show/hide code language selector
403
+ const editorRoot = document.getElementById('lexical-editor-root');
404
+ if (editorRoot) {
405
+ editorRoot.addEventListener('click', function(event) {
406
+ // Delay to let Lexical update selection
407
+ setTimeout(() => {
408
+ if (lexicalModule) {
409
+ updateCodeLanguageSelector();
410
+ }
411
+ }, 10);
412
+ });
413
+
414
+ // Also track selection changes
415
+ document.addEventListener('selectionchange', function() {
416
+ if (lexicalModule && !document.querySelector('#markdown-editor-overlay.hidden')) {
417
+ updateCodeLanguageSelector();
418
+ }
419
+ });
420
+ }
421
+ });