archsight 0.1.4 → 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 +6 -3
  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
@@ -0,0 +1,308 @@
1
+ // Lexical Rich Text Editor Module for Markdown editing
2
+ // Uses ESM imports from esm.sh CDN
3
+
4
+ import { createEditor, $getRoot, $getSelection, $isRangeSelection, FORMAT_TEXT_COMMAND, $createTextNode } from 'https://esm.sh/lexical@0.20.0';
5
+ import { registerRichText, HeadingNode, QuoteNode, $createHeadingNode, $createQuoteNode } from 'https://esm.sh/@lexical/rich-text@0.20.0';
6
+ import { registerHistory, createEmptyHistoryState } from 'https://esm.sh/@lexical/history@0.20.0';
7
+ import { $convertFromMarkdownString, $convertToMarkdownString, TRANSFORMERS } from 'https://esm.sh/@lexical/markdown@0.20.0';
8
+ import { ListNode, ListItemNode, registerList, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, REMOVE_LIST_COMMAND } from 'https://esm.sh/@lexical/list@0.20.0';
9
+ import { CodeNode, CodeHighlightNode, registerCodeHighlighting, $createCodeNode, $isCodeNode, getCodeLanguages } from 'https://esm.sh/@lexical/code@0.20.0';
10
+ import { LinkNode, $createLinkNode, $isLinkNode } from 'https://esm.sh/@lexical/link@0.20.0';
11
+ import { $setBlocksType } from 'https://esm.sh/@lexical/selection@0.20.0';
12
+ import { $createParagraphNode } from 'https://esm.sh/lexical@0.20.0';
13
+
14
+ let lexicalEditor = null;
15
+
16
+ /**
17
+ * Initialize the Lexical editor in the given container element
18
+ * @param {HTMLElement} containerElement - The contenteditable element to use as editor root
19
+ * @returns {LexicalEditor} The initialized editor instance
20
+ */
21
+ export function initLexicalEditor(containerElement) {
22
+ const config = {
23
+ namespace: 'ArchsightMarkdownEditor',
24
+ nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode, CodeNode, CodeHighlightNode, LinkNode],
25
+ onError: (error) => {
26
+ console.error('Lexical Editor Error:', error);
27
+ },
28
+ theme: {
29
+ paragraph: 'lexical-paragraph',
30
+ heading: {
31
+ h1: 'lexical-h1',
32
+ h2: 'lexical-h2',
33
+ h3: 'lexical-h3',
34
+ },
35
+ list: {
36
+ ul: 'lexical-ul',
37
+ ol: 'lexical-ol',
38
+ listitem: 'lexical-li',
39
+ },
40
+ quote: 'lexical-quote',
41
+ code: 'lexical-code',
42
+ codeHighlight: {
43
+ atrule: 'tokenAttr',
44
+ attr: 'tokenAttr',
45
+ boolean: 'tokenProperty',
46
+ builtin: 'tokenSelector',
47
+ cdata: 'tokenComment',
48
+ char: 'tokenSelector',
49
+ class: 'tokenFunction',
50
+ 'class-name': 'tokenFunction',
51
+ comment: 'tokenComment',
52
+ constant: 'tokenProperty',
53
+ deleted: 'tokenProperty',
54
+ doctype: 'tokenComment',
55
+ entity: 'tokenOperator',
56
+ function: 'tokenFunction',
57
+ important: 'tokenVariable',
58
+ inserted: 'tokenSelector',
59
+ keyword: 'tokenAttr',
60
+ namespace: 'tokenVariable',
61
+ number: 'tokenProperty',
62
+ operator: 'tokenOperator',
63
+ prolog: 'tokenComment',
64
+ property: 'tokenProperty',
65
+ punctuation: 'tokenPunctuation',
66
+ regex: 'tokenVariable',
67
+ selector: 'tokenSelector',
68
+ string: 'tokenSelector',
69
+ symbol: 'tokenProperty',
70
+ tag: 'tokenProperty',
71
+ url: 'tokenOperator',
72
+ variable: 'tokenVariable',
73
+ },
74
+ link: 'lexical-link',
75
+ text: {
76
+ bold: 'lexical-bold',
77
+ italic: 'lexical-italic',
78
+ underline: 'lexical-underline',
79
+ strikethrough: 'lexical-strikethrough',
80
+ code: 'lexical-text-code',
81
+ },
82
+ },
83
+ };
84
+
85
+ lexicalEditor = createEditor(config);
86
+ lexicalEditor.setRootElement(containerElement);
87
+
88
+ // Register plugins
89
+ registerRichText(lexicalEditor);
90
+ registerList(lexicalEditor);
91
+ registerCodeHighlighting(lexicalEditor);
92
+ registerHistory(lexicalEditor, createEmptyHistoryState(), 300);
93
+
94
+ return lexicalEditor;
95
+ }
96
+
97
+ /**
98
+ * Set the editor content from markdown string
99
+ * @param {string} markdown - The markdown string to load
100
+ */
101
+ export function setLexicalMarkdown(markdown) {
102
+ if (!lexicalEditor) return;
103
+
104
+ lexicalEditor.update(() => {
105
+ // Clear existing content and convert markdown
106
+ const root = $getRoot();
107
+ root.clear();
108
+ $convertFromMarkdownString(markdown || '', TRANSFORMERS);
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Get the current editor content as markdown string
114
+ * @returns {Promise<string>} The markdown string
115
+ */
116
+ export function getLexicalMarkdown() {
117
+ return new Promise((resolve) => {
118
+ if (!lexicalEditor) {
119
+ resolve('');
120
+ return;
121
+ }
122
+
123
+ lexicalEditor.update(() => {
124
+ const markdown = $convertToMarkdownString(TRANSFORMERS);
125
+ resolve(markdown);
126
+ });
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Get the editor instance
132
+ * @returns {LexicalEditor|null} The editor instance
133
+ */
134
+ export function getEditor() {
135
+ return lexicalEditor;
136
+ }
137
+
138
+ /**
139
+ * Execute a text format command (bold, italic, etc.)
140
+ * @param {string} format - The format type ('bold', 'italic', 'underline', 'strikethrough', 'code')
141
+ */
142
+ export function formatText(format) {
143
+ if (!lexicalEditor) return;
144
+ lexicalEditor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
145
+ }
146
+
147
+ /**
148
+ * Format selected block as heading, list, quote, or paragraph
149
+ * @param {string} blockType - The block type ('h1', 'h2', 'h3', 'bullet', 'number', 'quote', 'paragraph')
150
+ */
151
+ export function formatBlock(blockType) {
152
+ if (!lexicalEditor) return;
153
+
154
+ if (blockType === 'bullet') {
155
+ lexicalEditor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
156
+ return;
157
+ }
158
+
159
+ if (blockType === 'number') {
160
+ lexicalEditor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
161
+ return;
162
+ }
163
+
164
+ lexicalEditor.update(() => {
165
+ const selection = $getSelection();
166
+ if ($isRangeSelection(selection)) {
167
+ if (blockType === 'h1' || blockType === 'h2' || blockType === 'h3') {
168
+ $setBlocksType(selection, () => $createHeadingNode(blockType));
169
+ } else if (blockType === 'quote') {
170
+ $setBlocksType(selection, () => $createQuoteNode());
171
+ } else if (blockType === 'paragraph') {
172
+ $setBlocksType(selection, () => $createParagraphNode());
173
+ }
174
+ }
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Focus the editor
180
+ */
181
+ export function focusEditor() {
182
+ if (!lexicalEditor) return;
183
+ lexicalEditor.focus();
184
+ }
185
+
186
+ /**
187
+ * Insert a code block at current selection
188
+ * @param {string} language - The programming language for the code block
189
+ */
190
+ export function insertCodeBlock(language = '') {
191
+ if (!lexicalEditor) return;
192
+
193
+ lexicalEditor.update(() => {
194
+ const selection = $getSelection();
195
+ if ($isRangeSelection(selection)) {
196
+ const codeNode = $createCodeNode(language);
197
+ selection.insertNodes([codeNode]);
198
+ codeNode.select();
199
+ }
200
+ });
201
+ }
202
+
203
+
204
+ /**
205
+ * Set the language for the currently selected code block
206
+ * @param {string} language - The programming language
207
+ * @returns {boolean} True if language was set, false otherwise
208
+ */
209
+ export function setCodeBlockLanguage(language) {
210
+ if (!lexicalEditor) return false;
211
+
212
+ let success = false;
213
+ lexicalEditor.update(() => {
214
+ const selection = $getSelection();
215
+ if ($isRangeSelection(selection)) {
216
+ const nodes = selection.getNodes();
217
+ for (const node of nodes) {
218
+ const parent = node.getParent();
219
+ if ($isCodeNode(parent)) {
220
+ parent.setLanguage(language);
221
+ success = true;
222
+ break;
223
+ }
224
+ if ($isCodeNode(node)) {
225
+ node.setLanguage(language);
226
+ success = true;
227
+ break;
228
+ }
229
+ }
230
+ }
231
+ });
232
+ return success;
233
+ }
234
+
235
+ /**
236
+ * Get the current code node's language if cursor is in a code block
237
+ * @returns {string|null} The language or null if not in a code block
238
+ */
239
+ export function getCurrentCodeLanguage() {
240
+ if (!lexicalEditor) return null;
241
+
242
+ let language = null;
243
+ lexicalEditor.getEditorState().read(() => {
244
+ const selection = $getSelection();
245
+ if ($isRangeSelection(selection)) {
246
+ const nodes = selection.getNodes();
247
+ for (const node of nodes) {
248
+ const parent = node.getParent();
249
+ if ($isCodeNode(parent)) {
250
+ language = parent.getLanguage();
251
+ break;
252
+ }
253
+ if ($isCodeNode(node)) {
254
+ language = node.getLanguage();
255
+ break;
256
+ }
257
+ }
258
+ }
259
+ });
260
+ return language;
261
+ }
262
+
263
+ /**
264
+ * Insert or toggle a link
265
+ * Prompts user for URL if creating a new link
266
+ */
267
+ export function insertLink() {
268
+ if (!lexicalEditor) return;
269
+
270
+ const url = prompt('Enter URL:', 'https://');
271
+ if (url === null) return; // User cancelled
272
+
273
+ lexicalEditor.update(() => {
274
+ const selection = $getSelection();
275
+ if (!$isRangeSelection(selection)) return;
276
+
277
+ if (url === '') {
278
+ // Remove link - unwrap link nodes
279
+ const nodes = selection.getNodes();
280
+ nodes.forEach(node => {
281
+ const parent = node.getParent();
282
+ if ($isLinkNode(parent)) {
283
+ const children = parent.getChildren();
284
+ for (const child of children) {
285
+ parent.insertBefore(child);
286
+ }
287
+ parent.remove();
288
+ }
289
+ });
290
+ } else {
291
+ // Create link by wrapping selected content
292
+ const selectedText = selection.getTextContent();
293
+ if (selectedText) {
294
+ // Has selection - wrap it in a link
295
+ const linkNode = $createLinkNode(url);
296
+ selection.insertNodes([linkNode]);
297
+ // Move selected text into link
298
+ linkNode.append($createTextNode(selectedText));
299
+ } else {
300
+ // No selection - insert link with URL as text
301
+ const linkNode = $createLinkNode(url);
302
+ linkNode.append($createTextNode(url));
303
+ selection.insertNodes([linkNode]);
304
+ }
305
+ }
306
+ });
307
+ }
308
+
@@ -0,0 +1,80 @@
1
+ .field-group{class: field.textarea? || field.code? ? "field-full-width" : ""}
2
+ .label-row
3
+ %label{for: field.key, title: field.description}
4
+ = field.title
5
+ - if field.required
6
+ %span.required *
7
+ - if field.markdown?
8
+ %button.btn-edit-markdown{type: "button",
9
+ onclick: "openMarkdownEditor('#{field.key}')",
10
+ title: "Open rich text editor"}
11
+ %i.iconoir-edit-pencil
12
+ Edit
13
+
14
+ - if field.select?
15
+ %select{id: field.key,
16
+ name: "annotations[#{field.key}]",
17
+ "aria-invalid": error ? "true" : nil}
18
+ %option{value: "", selected: value.nil? || value.empty?} Select...
19
+ - field.options.each do |opt|
20
+ %option{value: opt, selected: value == opt}= opt
21
+
22
+ - elsif field.textarea?
23
+ .markdown-field
24
+ %textarea{id: field.key,
25
+ name: "annotations[#{field.key}]",
26
+ "aria-invalid": error ? "true" : nil,
27
+ placeholder: field.markdown? ? "Enter markdown content..." : "One entry per line..."}= value
28
+ - if field.markdown?
29
+ %small.future-hint
30
+ %i.iconoir-info-circle
31
+ Supports Markdown formatting
32
+
33
+ - elsif field.code?
34
+ .code-field
35
+ %textarea{id: field.key,
36
+ name: "annotations[#{field.key}]",
37
+ "aria-invalid": error ? "true" : nil,
38
+ placeholder: "Enter #{field.code_language} code...",
39
+ spellcheck: "false"}= value
40
+ %small.future-hint
41
+ %i.iconoir-code
42
+ = "#{field.code_language.to_s.capitalize} code"
43
+
44
+ - elsif field.number?
45
+ %input{id: field.key,
46
+ type: "number",
47
+ name: "annotations[#{field.key}]",
48
+ value: value,
49
+ step: field.step,
50
+ "aria-invalid": error ? "true" : nil}
51
+
52
+ - elsif field.url?
53
+ %input{id: field.key,
54
+ type: "url",
55
+ name: "annotations[#{field.key}]",
56
+ value: value,
57
+ placeholder: "https://...",
58
+ "aria-invalid": error ? "true" : nil}
59
+
60
+ - elsif field.list?
61
+ %input{id: field.key,
62
+ type: "text",
63
+ name: "annotations[#{field.key}]",
64
+ value: value,
65
+ placeholder: "Comma-separated values",
66
+ "aria-invalid": error ? "true" : nil}
67
+
68
+ - else
69
+ %input{id: field.key,
70
+ type: "text",
71
+ name: "annotations[#{field.key}]",
72
+ value: value,
73
+ "aria-invalid": error ? "true" : nil}
74
+
75
+ - if field.description && !field.textarea? && !field.code?
76
+ %small.field-description= field.description
77
+
78
+ - if error
79
+ .field-error
80
+ = error.join(", ")
@@ -0,0 +1,131 @@
1
+ - action_url = @mode == :edit ? "/kinds/#{@kind}/instances/#{@instance_name}/generate" : "/kinds/#{@kind}/generate"
2
+ - title = @mode == :edit ? "Edit #{@kind}" : "New #{@kind}"
3
+ - back_url = @mode == :edit ? "/kinds/#{@kind}/instances/#{@instance_name}" : "/kinds/#{@kind}"
4
+
5
+ - if @generated_yaml
6
+ %article.yaml-output
7
+ %header
8
+ %h3
9
+ %i{class: "iconoir-#{@klass.icon} icon-#{@klass.layer}"}
10
+ = title
11
+ %a.btn-back{href: back_url}
12
+ %i.iconoir-arrow-left
13
+ Back
14
+ != haml :'partials/editor/_yaml_output', locals: { yaml: @generated_yaml, path_ref: @path_ref, kind: @kind, name: @name, content_hash: @content_hash }
15
+ - else
16
+ %form.editor-form{method: "post", action: action_url, "data-kind": @kind}
17
+ / Metadata
18
+ %article
19
+ %header
20
+ %h3
21
+ %i{class: "iconoir-#{@klass.icon} icon-#{@klass.layer}"}
22
+ = title
23
+ %a.btn-back{href: back_url}
24
+ %i.iconoir-arrow-left
25
+ Back
26
+ - if @mode == :edit
27
+ / Show name as disabled field in edit mode
28
+ .field-group
29
+ %input#name{type: "text",
30
+ name: "name_display",
31
+ placeholder: "Name",
32
+ value: @name,
33
+ disabled: true}
34
+ %input{type: "hidden", name: "name", value: @name}
35
+ - if @content_hash
36
+ %input{type: "hidden", name: "content_hash", value: @content_hash}
37
+ - else
38
+ / Editable name field for new resources
39
+ .field-group
40
+ %label{for: "name"}
41
+ Name
42
+ %span.required *
43
+ %input#name{type: "text",
44
+ name: "name",
45
+ value: @name,
46
+ required: true,
47
+ placeholder: "Enter resource name (no spaces)",
48
+ pattern: "\\S+",
49
+ "aria-invalid": @errors["name"] ? "true" : nil}
50
+ - if @errors["name"]
51
+ .field-error
52
+ = @errors["name"].join(", ")
53
+
54
+ / Annotations
55
+ %article
56
+ %header
57
+ %h3 Annotations
58
+ .annotations-grid
59
+ - @fields.each do |field|
60
+ != haml :'partials/editor/_field', locals: { field: field, value: @annotations[field.key], error: @errors[field.key] }
61
+
62
+ / Relations
63
+ %article
64
+ %header
65
+ %h3 Relations
66
+ != haml :'partials/editor/_relations', locals: { relations: @relations, kind: @kind }
67
+
68
+ / Submit
69
+ .form-actions
70
+ %button{type: "submit"}
71
+ %i.iconoir-code
72
+ Generate YAML
73
+ %a.secondary{role: "button", href: @mode == :edit ? "/kinds/#{@kind}/instances/#{@instance_name}" : "/kinds/#{@kind}"}
74
+ Cancel
75
+
76
+ / Markdown Editor Overlay
77
+ #markdown-editor-overlay.hidden
78
+ .markdown-editor-backdrop{onclick: "closeMarkdownEditor()"}
79
+ .markdown-editor-panel
80
+ .lexical-toolbar
81
+ %button.toolbar-btn{type: "button", onclick: "formatLexical('bold')", title: "Bold (Ctrl+B)"}
82
+ %i.iconoir-bold
83
+ %button.toolbar-btn{type: "button", onclick: "formatLexical('italic')", title: "Italic (Ctrl+I)"}
84
+ %i.iconoir-italic
85
+ %button.toolbar-btn{type: "button", onclick: "formatLexical('strikethrough')", title: "Strikethrough"}
86
+ %i.iconoir-strikethrough
87
+ %button.toolbar-btn{type: "button", onclick: "formatLexical('code')", title: "Inline Code"}
88
+ %i.iconoir-code
89
+ .toolbar-divider
90
+ %button.toolbar-btn{type: "button", onclick: "formatLexicalBlock('h1')", title: "Heading 1"}
91
+ H1
92
+ %button.toolbar-btn{type: "button", onclick: "formatLexicalBlock('h2')", title: "Heading 2"}
93
+ H2
94
+ %button.toolbar-btn{type: "button", onclick: "formatLexicalBlock('h3')", title: "Heading 3"}
95
+ H3
96
+ .toolbar-divider
97
+ %button.toolbar-btn{type: "button", onclick: "formatLexicalBlock('bullet')", title: "Bullet List"}
98
+ %i.iconoir-list
99
+ %button.toolbar-btn{type: "button", onclick: "formatLexicalBlock('number')", title: "Numbered List"}
100
+ %i.iconoir-numbered-list-left
101
+ %button.toolbar-btn{type: "button", onclick: "formatLexicalBlock('quote')", title: "Quote"}
102
+ %i.iconoir-quote
103
+ .toolbar-divider
104
+ %button.toolbar-btn{type: "button", onclick: "insertLink()", title: "Insert Link"}
105
+ %i.iconoir-link
106
+ .toolbar-divider
107
+ %button.toolbar-btn{type: "button", onclick: "insertCodeBlock()", title: "Code Block"}
108
+ %i.iconoir-code-brackets
109
+ %select#code-language-picker.toolbar-select.hidden{onchange: "applyCodeLanguage(this.value)", title: "Code Language"}
110
+ %option{value: ""} Language...
111
+ %option{value: "javascript"} JavaScript
112
+ %option{value: "typescript"} TypeScript
113
+ %option{value: "ruby"} Ruby
114
+ %option{value: "python"} Python
115
+ %option{value: "html"} HTML
116
+ %option{value: "css"} CSS
117
+ %option{value: "json"} JSON
118
+ %option{value: "yaml"} YAML
119
+ %option{value: "bash"} Bash
120
+ %option{value: "sql"} SQL
121
+ %option{value: "go"} Go
122
+ %option{value: "rust"} Rust
123
+ %option{value: "java"} Java
124
+ %option{value: "mermaid"} Mermaid
125
+ %option{value: "markdown"} Markdown
126
+ %option{value: "text"} Plain Text
127
+ .toolbar-spacer
128
+ %button.secondary.toolbar-action{type: "button", onclick: "closeMarkdownEditor()"} Cancel
129
+ %button.toolbar-action{type: "button", onclick: "saveMarkdownEditor()"} Save
130
+ .lexical-editor-container
131
+ #lexical-editor-root{contenteditable: "true"}
@@ -0,0 +1,39 @@
1
+ - verbs = relation_verbs(kind)
2
+ - all_relations = available_relations(kind)
3
+ - verb_kind_options = all_relations.map { |v, _, k| ["#{v}:#{k}", v, k] }.sort_by(&:first).uniq
4
+ - target_kinds = verb_kind_options.map { |_, _, k| k }.uniq
5
+ - instances_by_kind = target_kinds.each_with_object({}) { |k, h| h[k] = db.instances_by_kind(k).keys.sort rescue [] }
6
+
7
+ .relations-editor{"data-kind": kind, "data-instances": instances_by_kind.to_json}
8
+ - if verb_kind_options.empty?
9
+ %p.no-relations This resource type has no defined relations.
10
+ - else
11
+ / Existing relations list (read-only display with hidden inputs)
12
+ #relations-list
13
+ - if relations&.any?
14
+ - sorted_relations = relations.sort_by { |r| [r[:verb].to_s, r[:kind].to_s, r[:name].to_s] }
15
+ - sorted_relations.each do |rel|
16
+ .relation-item
17
+ %span.relation-text
18
+ %strong= rel[:verb]
19
+ \&rarr; #{rel[:kind]}:
20
+ %em= rel[:name]
21
+ %input{type: "hidden", name: "relations[][verb]", value: rel[:verb]}
22
+ %input{type: "hidden", name: "relations[][kind]", value: rel[:kind]}
23
+ %input{type: "hidden", name: "relations[][name]", value: rel[:name]}
24
+ %button.btn-remove{type: "button", onclick: "removeRelation(this)", title: "Remove"}
25
+ %i.iconoir-xmark
26
+
27
+ / Add relation form - inline with verb:kind and instance dropdowns
28
+ .add-relation-row
29
+ %select#new-relation-verb-kind{onchange: "updateInstanceOptions()"}
30
+ %option{value: ""} Select relation...
31
+ - verb_kind_options.each do |combo, verb, target_kind|
32
+ %option{value: combo, "data-verb": verb, "data-kind": target_kind}= "#{verb} \u2192 #{target_kind}"
33
+
34
+ %select#new-relation-instance
35
+ %option{value: ""} Select instance...
36
+
37
+ %button.secondary.btn-add{type: "button", onclick: "addRelation()"}
38
+ %i.iconoir-plus
39
+ Add
@@ -0,0 +1,33 @@
1
+ %p.yaml-success
2
+ %i.iconoir-check-circle
3
+ Generated YAML
4
+
5
+ %pre#yaml-content
6
+ %code.language-yaml= yaml
7
+
8
+ %footer
9
+ - if defined?(path_ref) && path_ref
10
+ %p.yaml-file-info
11
+ %i.iconoir-folder
12
+ %span
13
+ Source: #{path_ref.path}:#{path_ref.line_no}
14
+ - else
15
+ %p.yaml-instructions
16
+ %i.iconoir-info-circle
17
+ %span
18
+ Copy this YAML and save it to a
19
+ %code .yaml
20
+ file in your resources directory,
21
+ %br
22
+ then reload the application.
23
+ .yaml-actions
24
+ - if defined?(path_ref) && path_ref && inline_edit_enabled?
25
+ %button#save-yaml{type: "button", data: { kind: kind, name: name, "content-hash": content_hash }}
26
+ %i.iconoir-check-circle
27
+ Save to File
28
+ %button#copy-yaml{type: "button", "data-clipboard-target": "#yaml-content"}
29
+ %i.iconoir-copy
30
+ Copy to Clipboard
31
+ %a.secondary{role: "button", href: "javascript:history.back()"}
32
+ %i.iconoir-edit-pencil
33
+ Edit Again
@@ -4,7 +4,6 @@
4
4
  - description = analysis.annotations['analysis/description']
5
5
  - handler = analysis.annotations['analysis/handler'] || 'ruby'
6
6
  - timeout = analysis.annotations['analysis/timeout'] || '30s'
7
- - enabled = analysis.annotations['analysis/enabled'] != 'false'
8
7
 
9
8
  %article.analysis-header
10
9
  %header
@@ -13,8 +12,10 @@
13
12
  .instance-title-text
14
13
  %span.instance-name= @instance
15
14
  %span.instance-kind-subtitle= @kind
16
- - unless enabled
17
- %span.badge.badge-warning Disabled
15
+ .header-actions
16
+ %a.btn-header{href: "/kinds/#{@kind}/instances/#{@instance}/edit", title: "Edit this resource"}
17
+ %i.iconoir-edit-pencil
18
+ Edit
18
19
  - if description
19
20
  .analysis-description
20
21
  != markdown(description)
@@ -49,14 +50,6 @@
49
50
  %span.analysis-meta-item
50
51
  %i.iconoir-timer
51
52
  = timeout
52
- - if enabled
53
- %span.analysis-meta-item.status-enabled
54
- %i.iconoir-check-circle
55
- Enabled
56
- - else
57
- %span.analysis-meta-item.status-disabled
58
- %i.iconoir-xmark-circle
59
- Disabled
60
53
  .analysis-script
61
54
  .analysis-script-header
62
55
  %strong Script
@@ -28,6 +28,10 @@
28
28
  %span.generated-time
29
29
  generated
30
30
  = time_ago(instance.annotations['generated/at'])
31
+ - else
32
+ %a.btn-header{href: "/kinds/#{@kind}/instances/#{@instance}/edit", title: "Edit this resource"}
33
+ %i.iconoir-edit-pencil
34
+ Edit
31
35
  - if instance.has_relations?
32
36
  .graph-container
33
37
  #graphviz.canvas
@@ -2,6 +2,8 @@
2
2
  - if @doc_content
3
3
  %article
4
4
  != @doc_content
5
+ - elsif @editor_mode
6
+ != haml :'partials/editor/_form'
5
7
  - elsif @instances
6
8
  != haml :search
7
9
  - elsif @kind && @instance
@@ -37,8 +39,12 @@
37
39
  %h2
38
40
  %i{class: "iconoir-#{Archsight::Resources[@kind].icon} icon-#{Archsight::Resources[@kind].layer}"}
39
41
  = @kind
40
- %a.kind-help{href: "/doc/resources/#{kind_snake}", title: "Documentation for #{@kind}"}
41
- %i.iconoir-help-circle
42
+ .header-actions
43
+ %a.kind-help{href: "/doc/resources/#{kind_snake}", title: "Documentation for #{@kind}"}
44
+ %i.iconoir-help-circle
45
+ %a.btn-header{href: "/kinds/#{@kind}/new", title: "Create new #{@kind}"}
46
+ %i.iconoir-plus
47
+ New
42
48
  != haml :"partials/instance/_list", locals: { instances: db.instances_by_kind(@kind).values.sort_by(&:name), omit_kind: true }
43
49
  - elsif !@kind
44
50
  != haml :"partials/instance/_graph"
@@ -12,6 +12,7 @@
12
12
  %link{href: asset_path('/css/highlight.min.css'), rel: 'stylesheet'}/
13
13
  %link{href: asset_path('/css/mermaid-layers.css'), rel: 'stylesheet'}/
14
14
  %link{href: asset_path('/css/graph.css'), rel: 'stylesheet'}/
15
+ %link{href: asset_path('/css/editor.css'), rel: 'stylesheet'}/
15
16
  %script{src: asset_path('/js/htmx.min.js')}
16
17
  %script{src: asset_path('/js/mermaid.min.js')}
17
18
  %script{src: asset_path('/js/svg-pan-zoom.min.js')}
@@ -20,5 +21,6 @@
20
21
  %script{src: asset_path("/js/mermaid-init.js")}
21
22
  %script{ src: asset_path("/js/highlight.min.js") }
22
23
  %script{ src: asset_path("/js/sparkline.js") }
24
+ %script{ src: asset_path("/js/editor.js"), type: "module" }
23
25
  :javascript
24
26
  hljs.highlightAll();