archsight 0.1.5 → 0.2.0
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.
- checksums.yaml +4 -4
- data/Dockerfile +5 -4
- data/README.md +17 -65
- data/chart/archsight/README.md +1 -158
- data/docs/architecture.md +39 -0
- data/docs/docker.md +49 -0
- data/{lib/archsight/web/doc → docs}/index.md.erb +3 -1
- data/docs/kubernetes.md +149 -0
- data/docs/licenses.md +307 -0
- data/lib/archsight/cli.rb +1 -1
- data/lib/archsight/graph.rb +1 -51
- data/lib/archsight/helpers.rb +0 -20
- data/lib/archsight/import/handlers/github.rb +3 -1
- data/lib/archsight/import/handlers/gitlab.rb +15 -5
- data/lib/archsight/import/handlers/repository.rb +53 -5
- data/lib/archsight/import/handlers/rest_api.rb +13 -1
- data/lib/archsight/import/license_analyzer.rb +650 -0
- data/lib/archsight/linter.rb +1 -1
- data/lib/archsight/mcp/base.rb +11 -0
- data/lib/archsight/renderer.rb +4 -4
- data/lib/archsight/resources/business_actor.rb +17 -2
- data/lib/archsight/resources/technology_artifact.rb +48 -0
- data/lib/archsight/version.rb +1 -1
- data/lib/archsight/web/api/docs.rb +37 -2
- data/lib/archsight/web/api/json_helpers.rb +79 -13
- data/lib/archsight/web/api/openapi/spec.yaml +699 -0
- data/lib/archsight/web/api/routes.rb +23 -0
- data/lib/archsight/web/application.rb +40 -128
- data/lib/archsight/web/editor/helpers.rb +150 -0
- data/lib/archsight/web/editor/routes.rb +85 -212
- data/lib/archsight/web/public/vue/ApiDocsPage-B1RqTNqh.js +1 -0
- data/lib/archsight/web/public/vue/ApiDocsPage-DhNTOH4o.css +1 -0
- data/lib/archsight/web/public/vue/DocPage-DzwBgBd4.js +1 -0
- data/lib/archsight/web/public/vue/EditorPage-D_miHSv4.js +34 -0
- data/lib/archsight/web/public/vue/EditorPage-Dq0MuTnp.css +1 -0
- data/lib/archsight/web/public/vue/ErrorPage-CQQtPey3.js +2 -0
- data/lib/archsight/web/public/vue/ErrorPage-CwPT3JUr.css +1 -0
- data/lib/archsight/web/public/vue/GraphView-DRcIqAiR.css +1 -0
- data/lib/archsight/web/public/vue/GraphView-T9jFH_qg.js +1 -0
- data/lib/archsight/web/public/vue/InstanceRouter-1Sm-CRhf.js +2 -0
- data/lib/archsight/web/public/vue/InstanceRouter-BJkDRXZY.css +1 -0
- data/lib/archsight/web/public/vue/KindList-JA_L_-Cz.js +1 -0
- data/lib/archsight/web/public/vue/ResourceList-8iqavWdg.js +1 -0
- data/lib/archsight/web/public/vue/ResourceList-DP-z-j71.css +1 -0
- data/lib/archsight/web/public/vue/SearchResults-BGHbg48-.css +1 -0
- data/lib/archsight/web/public/vue/SearchResults-BdgFeHcm.js +1 -0
- data/lib/archsight/web/public/vue/_basePickBy-CVgieyx-.js +1 -0
- data/lib/archsight/web/public/vue/_baseUniq-BNfrOSaP.js +1 -0
- data/lib/archsight/web/public/vue/architectureDiagram-VXUJARFQ-CJXNpTr5.js +36 -0
- data/lib/archsight/web/public/vue/blockDiagram-VD42YOAC-B5488Hes.js +122 -0
- data/lib/archsight/web/public/vue/c4Diagram-YG6GDRKO-eYY3hprM.js +10 -0
- data/lib/archsight/web/public/vue/chunk-4BX2VUAB-ZoXeL4D1.js +1 -0
- data/lib/archsight/web/public/vue/chunk-55IACEB6-rNtQYnu_.js +1 -0
- data/lib/archsight/web/public/vue/chunk-B4BG7PRW-DolAeVV9.js +165 -0
- data/lib/archsight/web/public/vue/chunk-DI55MBZ5-DnN0f_hj.js +220 -0
- data/lib/archsight/web/public/vue/chunk-FMBD7UC4-BQWOCMuR.js +15 -0
- data/lib/archsight/web/public/vue/chunk-QN33PNHL-DId301Kb.js +1 -0
- data/lib/archsight/web/public/vue/chunk-QZHKN3VN-xbY0NLgv.js +1 -0
- data/lib/archsight/web/public/vue/chunk-TZMSLE5B-CgF9_37b.js +1 -0
- data/lib/archsight/web/public/vue/classDiagram-2ON5EDUG-jGlvI-Za.js +1 -0
- data/lib/archsight/web/public/vue/classDiagram-v2-WZHVMYZB-jGlvI-Za.js +1 -0
- data/lib/archsight/web/public/vue/clone-6iRPe1-W.js +1 -0
- data/lib/archsight/web/public/vue/cose-bilkent-S5V4N54A-CB9Zfu50.js +1 -0
- data/lib/archsight/web/public/vue/cytoscape.esm-5J0xJHOV.js +321 -0
- data/lib/archsight/web/public/vue/dagre-6UL2VRFP-BqkmE-LI.js +4 -0
- data/lib/archsight/web/public/vue/diagram-PSM6KHXK-CKBfqtw3.js +24 -0
- data/lib/archsight/web/public/vue/diagram-QEK2KX5R-B78rOlvK.js +43 -0
- data/lib/archsight/web/public/vue/diagram-S2PKOQOG-BlXC6Cia.js +24 -0
- data/lib/archsight/web/public/vue/erDiagram-Q2GNP2WA-BnliyziJ.js +60 -0
- data/lib/archsight/web/public/vue/flowDiagram-NV44I4VS-wuqPowTd.js +162 -0
- data/lib/archsight/web/public/vue/ganttDiagram-JELNMOA3-GSffAIH3.js +267 -0
- data/lib/archsight/web/public/vue/gitGraphDiagram-V2S2FVAM-OA7VyugW.js +65 -0
- data/lib/archsight/web/public/vue/graph-BXHAtA0S.js +1 -0
- data/lib/archsight/web/public/vue/graphviz-CJms5bxZ.js +13 -0
- data/lib/archsight/web/public/vue/index-DsEsN0_K.js +2 -0
- data/lib/archsight/web/public/vue/index-Tiu4C-Sb.css +1 -0
- data/lib/archsight/web/public/vue/infoDiagram-HS3SLOUP-nlVe2qgv.js +2 -0
- data/lib/archsight/web/public/vue/journeyDiagram-XKPGCS4Q-CtTIcKwf.js +139 -0
- data/lib/archsight/web/public/vue/kanban-definition-3W4ZIXB7-837KX0sW.js +89 -0
- data/lib/archsight/web/public/vue/katex-C-M49wc6.js +261 -0
- data/lib/archsight/web/public/vue/layout-DtE0QdL6.js +1 -0
- data/lib/archsight/web/public/vue/mermaid-DpPHPFQh.js +250 -0
- data/lib/archsight/web/public/vue/mindmap-definition-VGOIOE7T-9gLF2AoY.js +68 -0
- data/lib/archsight/web/public/vue/pieDiagram-ADFJNKIX-CyCNgw3u.js +30 -0
- data/lib/archsight/web/public/vue/quadrantDiagram-AYHSOK5B-CkPh8g02.js +7 -0
- data/lib/archsight/web/public/vue/requirementDiagram-UZGBJVZJ-Dkt6OSlY.js +64 -0
- data/lib/archsight/web/public/vue/sankeyDiagram-TZEHDZUN-BqprTk8x.js +10 -0
- data/lib/archsight/web/public/vue/sequenceDiagram-WL72ISMW-CTmTe1FQ.js +145 -0
- data/lib/archsight/web/public/vue/stateDiagram-FKZM4ZOC-CphqmkEU.js +1 -0
- data/lib/archsight/web/public/vue/stateDiagram-v2-4FDKWEC3-CxaDW5sW.js +1 -0
- data/lib/archsight/web/public/vue/timeline-definition-IT6M3QCI-CSQUZkyE.js +61 -0
- data/lib/archsight/web/public/vue/treemap-GDKQZRPO-DTojm7Yr.js +162 -0
- data/lib/archsight/web/public/{css/graph.css → vue/useGraphviz-A5s4h76R.js} +2 -1
- data/lib/archsight/web/public/vue/useHighlight-C6Kb5G3l.js +10 -0
- data/lib/archsight/web/public/vue/useMermaid-DqxTrLRB.js +1 -0
- data/lib/archsight/web/public/vue/usePanZoom-BybZ_rfh.js +11 -0
- data/lib/archsight/web/public/vue/xychartDiagram-PRI3JC2R-B1ZJZtDC.js +7 -0
- data/lib/archsight/web/public/vue.html +15 -0
- data/media/artifact.jpg +0 -0
- data/media/service.jpg +0 -0
- metadata +86 -84
- data/lib/archsight/web/public/css/artifact.css +0 -995
- data/lib/archsight/web/public/css/base.css +0 -201
- data/lib/archsight/web/public/css/editor.css +0 -863
- data/lib/archsight/web/public/css/highlight.min.css +0 -10
- data/lib/archsight/web/public/css/iconoir.css +0 -22
- data/lib/archsight/web/public/css/instance.css +0 -824
- data/lib/archsight/web/public/css/layout.css +0 -421
- data/lib/archsight/web/public/css/mermaid-layers.css +0 -188
- data/lib/archsight/web/public/css/pico.min.css +0 -4
- data/lib/archsight/web/public/img/archimate.png +0 -0
- data/lib/archsight/web/public/img/togaf-high-level.png +0 -0
- data/lib/archsight/web/public/js/editor.js +0 -421
- data/lib/archsight/web/public/js/graph-zoom.js +0 -18
- data/lib/archsight/web/public/js/highlight.min.js +0 -3899
- data/lib/archsight/web/public/js/htmx.min.js +0 -1
- data/lib/archsight/web/public/js/lexical-editor.js +0 -308
- data/lib/archsight/web/public/js/mermaid-init.js +0 -88
- data/lib/archsight/web/public/js/mermaid.min.js +0 -2811
- data/lib/archsight/web/public/js/sparkline.js +0 -42
- data/lib/archsight/web/public/js/svg-pan-zoom.min.js +0 -3
- data/lib/archsight/web/public/js/svg-zoom-controls.js +0 -93
- data/lib/archsight/web/views/api_docs.erb +0 -19
- data/lib/archsight/web/views/index.haml +0 -12
- data/lib/archsight/web/views/partials/artifact/_activity.haml +0 -55
- data/lib/archsight/web/views/partials/artifact/_agentic.haml +0 -25
- data/lib/archsight/web/views/partials/artifact/_deployment.haml +0 -29
- data/lib/archsight/web/views/partials/artifact/_git_info.haml +0 -16
- data/lib/archsight/web/views/partials/artifact/_language_stats.haml +0 -53
- data/lib/archsight/web/views/partials/artifact/_links.haml +0 -24
- data/lib/archsight/web/views/partials/artifact/_project_estimate.haml +0 -32
- data/lib/archsight/web/views/partials/artifact/_repositories.haml +0 -55
- data/lib/archsight/web/views/partials/artifact/_team.haml +0 -83
- data/lib/archsight/web/views/partials/artifact/_workflow.haml +0 -69
- data/lib/archsight/web/views/partials/components/_activity.haml +0 -37
- data/lib/archsight/web/views/partials/components/_git.haml +0 -17
- data/lib/archsight/web/views/partials/components/_jira.haml +0 -18
- data/lib/archsight/web/views/partials/components/_languages.haml +0 -29
- data/lib/archsight/web/views/partials/components/_owner.haml +0 -15
- data/lib/archsight/web/views/partials/components/_repositories.haml +0 -37
- data/lib/archsight/web/views/partials/components/_status.haml +0 -23
- data/lib/archsight/web/views/partials/editor/_field.haml +0 -80
- data/lib/archsight/web/views/partials/editor/_form.haml +0 -131
- data/lib/archsight/web/views/partials/editor/_relations.haml +0 -39
- data/lib/archsight/web/views/partials/editor/_yaml_output.haml +0 -33
- data/lib/archsight/web/views/partials/instance/_analysis_detail.haml +0 -67
- data/lib/archsight/web/views/partials/instance/_analysis_result.haml +0 -64
- data/lib/archsight/web/views/partials/instance/_detail.haml +0 -107
- data/lib/archsight/web/views/partials/instance/_graph.haml +0 -6
- data/lib/archsight/web/views/partials/instance/_import_detail.haml +0 -87
- data/lib/archsight/web/views/partials/instance/_list.haml +0 -84
- data/lib/archsight/web/views/partials/instance/_relations.haml +0 -43
- data/lib/archsight/web/views/partials/instance/_requirements.haml +0 -41
- data/lib/archsight/web/views/partials/instance/_view_detail.haml +0 -57
- data/lib/archsight/web/views/partials/layout/_content.haml +0 -50
- data/lib/archsight/web/views/partials/layout/_error.haml +0 -22
- data/lib/archsight/web/views/partials/layout/_head.haml +0 -26
- data/lib/archsight/web/views/partials/layout/_navigation.haml +0 -21
- data/lib/archsight/web/views/partials/layout/_sidebar.haml +0 -27
- data/lib/archsight/web/views/search.haml +0 -53
- /data/{lib/archsight/web/doc → docs}/archimate.md +0 -0
- /data/{lib/archsight/web/doc → docs}/computed_annotations.md +0 -0
- /data/{lib/archsight/web/doc → docs}/icons.md +0 -0
- /data/{lib/archsight/web/doc → docs}/import.md +0 -0
- /data/{lib/archsight/web/doc → docs}/modeling.md +0 -0
- /data/{lib/archsight/web/doc → docs}/search.md +0 -0
- /data/{lib/archsight/web/doc → docs}/togaf.md +0 -0
- /data/{lib/archsight/web/doc → docs}/tool.md +0 -0
|
@@ -42,6 +42,15 @@ module Archsight::Web::API::Routes
|
|
|
42
42
|
json_response(build_list_response(kind, pagination, resources))
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# GET /api/v1/kinds/:kind/filters - Get filterable annotations with values
|
|
46
|
+
get "/api/v1/kinds/:kind/filters" do
|
|
47
|
+
kind = params[:kind]
|
|
48
|
+
klass = Archsight::Resources[kind]
|
|
49
|
+
json_error("Kind '#{kind}' not found", status: 404, error_type: "NotFound") unless klass
|
|
50
|
+
|
|
51
|
+
json_response(build_filters_response(kind))
|
|
52
|
+
end
|
|
53
|
+
|
|
45
54
|
# GET /api/v1/kinds/:kind/instances/:name - Get instance details with relations
|
|
46
55
|
get "/api/v1/kinds/:kind/instances/:name" do
|
|
47
56
|
kind = params[:kind]
|
|
@@ -79,6 +88,20 @@ module Archsight::Web::API::Routes
|
|
|
79
88
|
end
|
|
80
89
|
end
|
|
81
90
|
|
|
91
|
+
# POST /api/v1/kinds/Analysis/instances/:name/execute - Execute an analysis
|
|
92
|
+
post "/api/v1/kinds/Analysis/instances/:name/execute" do
|
|
93
|
+
require "archsight/analysis"
|
|
94
|
+
|
|
95
|
+
name = params[:name]
|
|
96
|
+
analysis = db.instance_by_kind("Analysis", name)
|
|
97
|
+
json_error("Analysis '#{name}' not found", status: 404, error_type: "NotFound") unless analysis
|
|
98
|
+
|
|
99
|
+
executor = Archsight::Analysis::Executor.new(db)
|
|
100
|
+
result = executor.execute(analysis)
|
|
101
|
+
|
|
102
|
+
json_response(build_analysis_result(result))
|
|
103
|
+
end
|
|
104
|
+
|
|
82
105
|
# GET /api/v1/openapi.yaml - OpenAPI specification
|
|
83
106
|
get "/api/v1/openapi.yaml" do
|
|
84
107
|
content_type "text/yaml"
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "sinatra/base"
|
|
4
4
|
require "kramdown"
|
|
5
|
-
require "haml"
|
|
6
5
|
require "erb"
|
|
7
6
|
require "fast_mcp"
|
|
8
7
|
|
|
@@ -55,9 +54,7 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
55
54
|
end
|
|
56
55
|
|
|
57
56
|
configure do
|
|
58
|
-
set :views, File.join(__dir__, "views")
|
|
59
57
|
set :public_folder, File.join(__dir__, "public")
|
|
60
|
-
set :haml, format: :html5
|
|
61
58
|
set :server, :puma
|
|
62
59
|
set :reload_enabled, true
|
|
63
60
|
set :inline_edit_enabled, false
|
|
@@ -83,7 +80,7 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
83
80
|
localhost_only: false
|
|
84
81
|
end
|
|
85
82
|
|
|
86
|
-
helpers Archsight::
|
|
83
|
+
helpers Archsight::GraphvisRenderer, Archsight::Helpers
|
|
87
84
|
|
|
88
85
|
# Register API modules
|
|
89
86
|
register Archsight::Web::API::Routes
|
|
@@ -156,168 +153,83 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
156
153
|
def render_analysis_section(section)
|
|
157
154
|
Archsight::Helpers.render_analysis_section(section, markdown_renderer: method(:markdown))
|
|
158
155
|
end
|
|
156
|
+
|
|
157
|
+
# Serve the Vue SPA shell (built by Vite)
|
|
158
|
+
def serve_vue
|
|
159
|
+
vue_path = File.join(settings.public_folder, "vue.html")
|
|
160
|
+
content_type :html
|
|
161
|
+
if File.exist?(vue_path)
|
|
162
|
+
File.read(vue_path)
|
|
163
|
+
else
|
|
164
|
+
"<!DOCTYPE html><html><body><p>Vue frontend not built. Run: cd frontend && npm run build</p></body></html>"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
159
167
|
end
|
|
160
168
|
|
|
161
169
|
get "/" do
|
|
162
|
-
|
|
170
|
+
serve_vue
|
|
163
171
|
end
|
|
164
172
|
|
|
165
173
|
get "/reload" do
|
|
166
174
|
halt 404, "Reload is disabled" unless settings.reload_enabled
|
|
167
175
|
|
|
168
176
|
Archsight::Web::Application.reload!
|
|
169
|
-
if
|
|
177
|
+
if request.env["HTTP_ACCEPT"]&.include?("application/json") || request.xhr?
|
|
178
|
+
content_type :json
|
|
179
|
+
JSON.generate({ ok: true })
|
|
180
|
+
elsif params["redirect"]&.start_with?("/")
|
|
170
181
|
redirect params["redirect"]
|
|
171
182
|
else
|
|
172
183
|
redirect "/"
|
|
173
184
|
end
|
|
174
185
|
rescue Archsight::ResourceError => e
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
begin
|
|
185
|
-
content = Archsight::Documentation.generate(kind_name)
|
|
186
|
-
@doc_content = markdown(content)
|
|
187
|
-
rescue StandardError
|
|
188
|
-
halt 404, "Documentation not found"
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
if request.env["HTTP_HX_REQUEST"]
|
|
192
|
-
"<article>#{@doc_content}</article>"
|
|
186
|
+
if request.env["HTTP_ACCEPT"]&.include?("application/json") || request.xhr?
|
|
187
|
+
content_type :json
|
|
188
|
+
status 422
|
|
189
|
+
JSON.generate({
|
|
190
|
+
error: e.message,
|
|
191
|
+
path: relative_error_path(e.ref.path),
|
|
192
|
+
line_no: e.ref.line_no,
|
|
193
|
+
context: error_context_lines(e.ref.path, e.ref.line_no)
|
|
194
|
+
})
|
|
193
195
|
else
|
|
194
|
-
|
|
196
|
+
content_type :html
|
|
197
|
+
path = ERB::Util.html_escape(relative_error_path(e.ref.path))
|
|
198
|
+
msg = ERB::Util.html_escape(e.message)
|
|
199
|
+
"<!DOCTYPE html><html><body><h3>Error: #{msg}</h3><p>#{path} line #{e.ref.line_no}</p><a href='/'>Back</a></body></html>"
|
|
195
200
|
end
|
|
196
201
|
end
|
|
197
202
|
|
|
198
|
-
get "/doc/:filename" do
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
# Check for ERB template first, then plain markdown
|
|
202
|
-
erb_path = File.join(settings.views, "..", "doc", "#{filename}.md.erb")
|
|
203
|
-
md_path = File.join(settings.views, "..", "doc", "#{filename}.md")
|
|
204
|
-
|
|
205
|
-
content = if File.exist?(erb_path)
|
|
206
|
-
template = ERB.new(File.read(erb_path))
|
|
207
|
-
template.result(binding)
|
|
208
|
-
elsif File.exist?(md_path)
|
|
209
|
-
File.read(md_path)
|
|
210
|
-
else
|
|
211
|
-
halt 404, "Documentation not found"
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
@doc_content = markdown(content)
|
|
215
|
-
|
|
216
|
-
if request.env["HTTP_HX_REQUEST"]
|
|
217
|
-
"<article>#{@doc_content}</article>"
|
|
218
|
-
else
|
|
219
|
-
haml :index
|
|
220
|
-
end
|
|
203
|
+
get "/doc/resources/:filename" do
|
|
204
|
+
serve_vue
|
|
221
205
|
end
|
|
222
206
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
start_time = Time.now
|
|
226
|
-
if (@q = params["q"])
|
|
227
|
-
@instances = db.query(@q)
|
|
228
|
-
elsif (@tag = params["tag"]) && (@value = params["value"])
|
|
229
|
-
@method = params["method"] || "=="
|
|
230
|
-
# Build query string - quote value for string operators, leave unquoted for numeric
|
|
231
|
-
quoted_value = if %w[> < >= <=].include?(@method)
|
|
232
|
-
@value # Numeric comparison, no quotes
|
|
233
|
-
else
|
|
234
|
-
"\"#{@value.gsub('"', '\\"')}\"" # String comparison, quote it
|
|
235
|
-
end
|
|
236
|
-
@q = "#{@tag} #{@method} #{quoted_value}"
|
|
237
|
-
@instances = db.query(@q)
|
|
238
|
-
else
|
|
239
|
-
@instances = []
|
|
240
|
-
end
|
|
241
|
-
if (@kind = params["kind"])
|
|
242
|
-
@instances = @instances.select { |i| i.kind == @kind } if @kind
|
|
243
|
-
end
|
|
244
|
-
@search_time_ms = ((Time.now - start_time) * 1000).round(2)
|
|
245
|
-
rescue Archsight::Query::QueryError => e
|
|
246
|
-
@query_error = e
|
|
247
|
-
@search_time_ms = ((Time.now - start_time) * 1000).round(2)
|
|
248
|
-
@q = params["q"] || "#{params["tag"]} #{params["method"] || "=="} \"#{params["value"]}\""
|
|
207
|
+
get "/doc/:filename" do
|
|
208
|
+
serve_vue
|
|
249
209
|
end
|
|
250
210
|
|
|
251
|
-
# GET /search - for direct URL access,
|
|
211
|
+
# GET /search - for direct URL access, Vue SPA handles rendering
|
|
252
212
|
get "/search" do
|
|
253
|
-
|
|
254
|
-
haml :index
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
# POST /search - for HTMX requests
|
|
258
|
-
post "/search" do
|
|
259
|
-
perform_search
|
|
260
|
-
haml :search
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
get "/svg" do
|
|
264
|
-
content_type :svg
|
|
265
|
-
create_graph_all(db, :draw_svg)
|
|
213
|
+
serve_vue
|
|
266
214
|
end
|
|
267
215
|
|
|
268
216
|
get "/dot" do
|
|
269
217
|
content_type "text/plain"
|
|
270
|
-
create_graph_all(db
|
|
218
|
+
create_graph_all(db)
|
|
271
219
|
end
|
|
272
220
|
|
|
273
221
|
get "/kinds/:kind" do
|
|
274
|
-
|
|
275
|
-
haml :index
|
|
222
|
+
serve_vue
|
|
276
223
|
end
|
|
277
224
|
|
|
278
225
|
get "/kinds/:kind/instances/:instance" do
|
|
279
|
-
|
|
280
|
-
@instance = params["instance"]
|
|
281
|
-
haml :index
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
get "/kinds/:kind/instances/:instance/svg" do
|
|
285
|
-
@kind = params["kind"]
|
|
286
|
-
@instance = params["instance"]
|
|
287
|
-
content_type :svg
|
|
288
|
-
create_graph_one(db, @kind, @instance, :draw_svg)
|
|
226
|
+
serve_vue
|
|
289
227
|
end
|
|
290
228
|
|
|
291
229
|
get "/kinds/:kind/instances/:instance/dot" do
|
|
292
230
|
@kind = params["kind"]
|
|
293
231
|
@instance = params["instance"]
|
|
294
232
|
content_type "text/plain"
|
|
295
|
-
create_graph_one(db, @kind, @instance
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
# Execute an Analysis and return HTML results
|
|
299
|
-
post "/kinds/Analysis/instances/:instance/execute" do
|
|
300
|
-
require "archsight/analysis"
|
|
301
|
-
|
|
302
|
-
@instance = params["instance"]
|
|
303
|
-
analysis = db.instance_by_kind("Analysis", @instance)
|
|
304
|
-
|
|
305
|
-
unless analysis
|
|
306
|
-
return haml_inline('.analysis-error
|
|
307
|
-
%i.iconoir-warning-triangle
|
|
308
|
-
Analysis not found: #{@instance}')
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
executor = Archsight::Analysis::Executor.new(db)
|
|
312
|
-
result = executor.execute(analysis)
|
|
313
|
-
|
|
314
|
-
haml :"partials/instance/_analysis_result", locals: { result: result }
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
private
|
|
318
|
-
|
|
319
|
-
# Helper for inline HAML rendering
|
|
320
|
-
def haml_inline(template)
|
|
321
|
-
haml template
|
|
233
|
+
create_graph_one(db, @kind, @instance)
|
|
322
234
|
end
|
|
323
235
|
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../editor"
|
|
4
|
+
require_relative "form_builder"
|
|
5
|
+
|
|
6
|
+
module Archsight
|
|
7
|
+
module Web
|
|
8
|
+
module Editor
|
|
9
|
+
# Shared helpers for editor routes
|
|
10
|
+
module Helpers
|
|
11
|
+
# Get form fields for a resource kind
|
|
12
|
+
def editor_fields(kind)
|
|
13
|
+
FormBuilder.fields_for(kind)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Get available relations for a resource kind
|
|
17
|
+
def available_relations(kind)
|
|
18
|
+
Archsight::Editor.available_relations(kind)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Extract annotations from JSON body or form params
|
|
22
|
+
def extract_annotations(params)
|
|
23
|
+
annotations = params["annotations"] || {}
|
|
24
|
+
annotations.transform_values { |v| v.is_a?(String) ? v.strip : v }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Parse relations from JSON array format
|
|
28
|
+
# Accepts: [{ verb, kind, names: [...] }] or [{ verb, kind, name }]
|
|
29
|
+
def parse_json_relations(relations_data)
|
|
30
|
+
return [] unless relations_data.is_a?(Array)
|
|
31
|
+
|
|
32
|
+
relations_data.each_with_object([]) do |rel, result|
|
|
33
|
+
next unless rel.is_a?(Hash)
|
|
34
|
+
|
|
35
|
+
parsed = parse_single_relation(rel)
|
|
36
|
+
next unless parsed
|
|
37
|
+
|
|
38
|
+
merge_relation(result, parsed)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Validate content hash for optimistic locking
|
|
43
|
+
def validate_content_hash(instance, expected_hash)
|
|
44
|
+
Archsight::Editor::ContentHasher.validate(
|
|
45
|
+
path: instance.path_ref.path,
|
|
46
|
+
start_line: instance.path_ref.line_no,
|
|
47
|
+
expected_hash: expected_hash
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Build form metadata response for a kind
|
|
52
|
+
def build_form_metadata(kind, klass)
|
|
53
|
+
{
|
|
54
|
+
kind: kind,
|
|
55
|
+
icon: klass.icon,
|
|
56
|
+
layer: klass.layer,
|
|
57
|
+
fields: serialize_fields(kind),
|
|
58
|
+
relation_options: build_relation_options(kind),
|
|
59
|
+
instances_by_kind: build_instances_by_kind(kind),
|
|
60
|
+
inline_edit_enabled: settings.inline_edit_enabled
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Extract relations from an existing instance into form format
|
|
65
|
+
def extract_instance_relations(instance)
|
|
66
|
+
relations = []
|
|
67
|
+
|
|
68
|
+
instance.spec.each do |verb, relation_groups|
|
|
69
|
+
next unless relation_groups.is_a?(Hash)
|
|
70
|
+
|
|
71
|
+
relation_groups.each do |relation_name, targets|
|
|
72
|
+
next unless targets.is_a?(Array)
|
|
73
|
+
|
|
74
|
+
target_class = Archsight::Editor.target_class_for_relation(instance.kind, verb, relation_name)
|
|
75
|
+
next unless target_class
|
|
76
|
+
|
|
77
|
+
targets.each do |target|
|
|
78
|
+
target_name = target.respond_to?(:name) ? target.name : target.to_s
|
|
79
|
+
relations << { verb: verb, kind: target_class, name: target_name }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
relations
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def parse_single_relation(rel)
|
|
90
|
+
verb = (rel["verb"] || rel[:verb])&.to_s&.strip
|
|
91
|
+
kind = (rel["kind"] || rel[:kind])&.to_s&.strip
|
|
92
|
+
return if verb.nil? || verb.empty? || kind.nil? || kind.empty?
|
|
93
|
+
|
|
94
|
+
names = extract_relation_names(rel)
|
|
95
|
+
return if names.empty?
|
|
96
|
+
|
|
97
|
+
{ verb: verb, kind: kind, names: names }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def extract_relation_names(rel)
|
|
101
|
+
names = rel["names"] || rel[:names]
|
|
102
|
+
name = rel["name"] || rel[:name]
|
|
103
|
+
|
|
104
|
+
if names.is_a?(Array)
|
|
105
|
+
names.map(&:to_s).reject(&:empty?)
|
|
106
|
+
elsif name
|
|
107
|
+
[name.to_s.strip].reject(&:empty?)
|
|
108
|
+
else
|
|
109
|
+
[]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def merge_relation(relations, parsed)
|
|
114
|
+
existing = relations.find { |r| r[:verb] == parsed[:verb] && r[:kind] == parsed[:kind] }
|
|
115
|
+
if existing
|
|
116
|
+
parsed[:names].each { |n| existing[:names] << n unless existing[:names].include?(n) }
|
|
117
|
+
else
|
|
118
|
+
relations << parsed
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def serialize_fields(kind)
|
|
123
|
+
editor_fields(kind).map do |f|
|
|
124
|
+
{
|
|
125
|
+
key: f.key, title: f.title, description: f.description,
|
|
126
|
+
input_type: f.input_type.to_s, options: f.options, step: f.step,
|
|
127
|
+
required: f.required, code_language: f.code_language&.to_s
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def build_relation_options(kind)
|
|
133
|
+
available_relations(kind)
|
|
134
|
+
.map { |v, _, k| { combo: "#{v}:#{k}", verb: v.to_s, target_kind: k.to_s } }
|
|
135
|
+
.sort_by { |r| r[:combo] }
|
|
136
|
+
.uniq { |r| r[:combo] }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def build_instances_by_kind(kind)
|
|
140
|
+
target_kinds = build_relation_options(kind).map { |r| r[:target_kind] }.uniq
|
|
141
|
+
target_kinds.each_with_object({}) do |k, h|
|
|
142
|
+
h[k] = db.instances_by_kind(k).keys.sort
|
|
143
|
+
rescue StandardError
|
|
144
|
+
h[k] = []
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|