archsight 0.3.3 → 0.4.1

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1
  3. data/docs/archimate.md +21 -1
  4. data/docs/configuration.md +1 -0
  5. data/docs/diagram.md +15 -3
  6. data/docs/docker.md +4 -1
  7. data/docs/icons.md +8 -2
  8. data/docs/import.md +178 -55
  9. data/docs/index.md.erb +24 -18
  10. data/docs/modeling.md +645 -34
  11. data/docs/pages.md +46 -15
  12. data/docs/search.md +21 -1
  13. data/docs/togaf.md +2 -1
  14. data/lib/archsight/analysis/result.rb +8 -2
  15. data/lib/archsight/analysis/sandbox.rb +8 -0
  16. data/lib/archsight/analysis/typst_renderer.rb +245 -0
  17. data/lib/archsight/analysis.rb +1 -0
  18. data/lib/archsight/annotations/annotation.rb +30 -3
  19. data/lib/archsight/annotations/architecture_annotations.rb +47 -0
  20. data/lib/archsight/annotations/link_annotations.rb +15 -0
  21. data/lib/archsight/annotations/resource_reference.rb +12 -0
  22. data/lib/archsight/cli.rb +124 -23
  23. data/lib/archsight/database.rb +40 -0
  24. data/lib/archsight/diagram/cli.rb +50 -2
  25. data/lib/archsight/diagram/formatter.rb +112 -0
  26. data/lib/archsight/diagram/routing/edge_router/orthogonal_path.rb +38 -10
  27. data/lib/archsight/diagram/routing/edge_router.rb +29 -4
  28. data/lib/archsight/diagram/routing/edge_routing.rb +61 -5
  29. data/lib/archsight/diagram/style/theme.rb +7 -2
  30. data/lib/archsight/documentation.rb +4 -4
  31. data/lib/archsight/editor/page_source.rb +13 -1
  32. data/lib/archsight/export/confluence/analysis_output.rb +100 -0
  33. data/lib/archsight/export/confluence/client.rb +13 -0
  34. data/lib/archsight/export/confluence/diagram_links.rb +1 -1
  35. data/lib/archsight/export/confluence/exporter.rb +43 -14
  36. data/lib/archsight/export/confluence/labels.rb +25 -0
  37. data/lib/archsight/export/confluence/page_properties.rb +6 -9
  38. data/lib/archsight/export/confluence/storage.rb +63 -14
  39. data/lib/archsight/export/confluence/tables.rb +2 -3
  40. data/lib/archsight/git_source.rb +81 -0
  41. data/lib/archsight/helpers/asset_images.rb +3 -4
  42. data/lib/archsight/helpers/embeds.rb +18 -7
  43. data/lib/archsight/helpers/resource_resolver.rb +25 -0
  44. data/lib/archsight/import/executor.rb +140 -16
  45. data/lib/archsight/import/handler.rb +55 -6
  46. data/lib/archsight/import/handler_loader.rb +64 -0
  47. data/lib/archsight/import/handlers/cache_pruner.rb +30 -7
  48. data/lib/archsight/import/handlers/command.rb +120 -0
  49. data/lib/archsight/import/handlers/github.rb +4 -1
  50. data/lib/archsight/import/handlers/gitlab.rb +4 -1
  51. data/lib/archsight/import/handlers/go_dep_resolver.rb +10 -2
  52. data/lib/archsight/import/handlers/go_grapher.rb +102 -5
  53. data/lib/archsight/import/handlers/go_module_parser.rb +88 -0
  54. data/lib/archsight/import/handlers/grapher.rb +40 -1
  55. data/lib/archsight/import/handlers/repository.rb +3 -0
  56. data/lib/archsight/import/handlers/rest_api_index.rb +3 -0
  57. data/lib/archsight/import/registry.rb +22 -1
  58. data/lib/archsight/import.rb +89 -1
  59. data/lib/archsight/linter/structure.rb +239 -0
  60. data/lib/archsight/linter.rb +123 -16
  61. data/lib/archsight/markdown.rb +48 -0
  62. data/lib/archsight/openapi.rb +169 -0
  63. data/lib/archsight/page_loader.rb +12 -1
  64. data/lib/archsight/page_tree.rb +2 -3
  65. data/lib/archsight/query/evaluator.rb +13 -6
  66. data/lib/archsight/query/parser.rb +73 -6
  67. data/lib/archsight/query.rb +1 -1
  68. data/lib/archsight/references.rb +3 -3
  69. data/lib/archsight/renderer.rb +80 -23
  70. data/lib/archsight/resources/analysis.rb +3 -2
  71. data/lib/archsight/resources/application_component.rb +75 -7
  72. data/lib/archsight/resources/application_interface.rb +12 -0
  73. data/lib/archsight/resources/application_service.rb +12 -1
  74. data/lib/archsight/resources/base.rb +58 -5
  75. data/lib/archsight/resources/business_actor.rb +5 -0
  76. data/lib/archsight/resources/business_contract.rb +72 -0
  77. data/lib/archsight/resources/business_product.rb +12 -0
  78. data/lib/archsight/resources/business_role.rb +1 -0
  79. data/lib/archsight/resources/implementation_deliverable.rb +1 -0
  80. data/lib/archsight/resources/implementation_plateau.rb +8 -2
  81. data/lib/archsight/resources/implementation_work_package.rb +2 -1
  82. data/lib/archsight/resources/import.rb +37 -11
  83. data/lib/archsight/resources/location.rb +56 -0
  84. data/lib/archsight/resources/motivation_requirement.rb +6 -0
  85. data/lib/archsight/resources/motivation_stakeholder.rb +14 -0
  86. data/lib/archsight/resources/page.rb +4 -7
  87. data/lib/archsight/resources/strategy_capability.rb +9 -0
  88. data/lib/archsight/resources/technology_artifact.rb +11 -4
  89. data/lib/archsight/resources/technology_communication_network.rb +91 -0
  90. data/lib/archsight/resources/technology_device.rb +88 -0
  91. data/lib/archsight/resources/technology_equipment.rb +75 -0
  92. data/lib/archsight/resources/technology_facility.rb +70 -0
  93. data/lib/archsight/resources/technology_node.rb +50 -0
  94. data/lib/archsight/resources/technology_service.rb +33 -0
  95. data/lib/archsight/resources/technology_system_software.rb +28 -0
  96. data/lib/archsight/resources.rb +12 -0
  97. data/lib/archsight/version.rb +1 -1
  98. data/lib/archsight/web/api/analysis_helpers.rb +55 -0
  99. data/lib/archsight/web/api/docs.rb +2 -1
  100. data/lib/archsight/web/api/json_helpers.rb +3 -37
  101. data/lib/archsight/web/api/openapi/spec.yaml +146 -35
  102. data/lib/archsight/web/api/page_helpers.rb +1 -8
  103. data/lib/archsight/web/api/routes.rb +9 -4
  104. data/lib/archsight/web/application.rb +40 -4
  105. data/lib/archsight/web/editor/routes.rb +2 -1
  106. data/lib/archsight/web/public/vendor/redoc/LICENSE +22 -0
  107. data/lib/archsight/web/public/vendor/redoc/VERSION +1 -0
  108. data/lib/archsight/web/public/vendor/redoc/api.html +27 -0
  109. data/lib/archsight/web/public/vendor/redoc/api.js +19 -0
  110. data/lib/archsight/web/public/vendor/redoc/redoc.standalone.js +1851 -0
  111. data/lib/archsight/web/public/vendor/redoc/redoc.standalone.js.LICENSE.txt +102 -0
  112. data/lib/archsight/web/public/vue/DocPage-C2ubTXH7.js +1 -0
  113. data/lib/archsight/web/public/vue/EditorPage-Dso6PLMn.js +35 -0
  114. data/lib/archsight/web/public/vue/ErrorPage-DnZ6YJLi.js +2 -0
  115. data/lib/archsight/web/public/vue/GraphView-CTl03Yre.js +1 -0
  116. data/lib/archsight/web/public/vue/HomePage-C339nihQ.js +2 -0
  117. data/lib/archsight/web/public/vue/InstanceRouter-DFW7tOrD.js +1 -0
  118. data/lib/archsight/web/public/vue/InstanceRouter-ZaCcjlIX.css +1 -0
  119. data/lib/archsight/web/public/vue/KindList-C0oaM3CF.js +1 -0
  120. data/lib/archsight/web/public/vue/KindList-yYcRT2BV.css +1 -0
  121. data/lib/archsight/web/public/vue/PageView-B93woeN3.css +1 -0
  122. data/lib/archsight/web/public/vue/PageView-OZB6G9Wu.js +1 -0
  123. data/lib/archsight/web/public/vue/QueryError-C2xLkCEm.js +1 -0
  124. data/lib/archsight/web/public/vue/ResourceList-D75O87zG.css +1 -0
  125. data/lib/archsight/web/public/vue/ResourceList-DooWFwQP.js +2 -0
  126. data/lib/archsight/web/public/vue/SearchResults-BK-HjFnv.js +1 -0
  127. data/lib/archsight/web/public/vue/{SearchResults-BewvsfOc.css → SearchResults-BV6UHriJ.css} +1 -1
  128. data/lib/archsight/web/public/vue/WikiPage-B2FYmeHL.js +3 -0
  129. data/lib/archsight/web/public/vue/WikiPage-TpmM1VAF.css +1 -0
  130. data/lib/archsight/web/public/vue/architecture-WOLXFQ4H-BGN-TV5r.js +1 -0
  131. data/lib/archsight/web/public/vue/cynefin-EF2NZ3EQ-CZd6g_tq.js +1 -0
  132. data/lib/archsight/web/public/vue/eventmodeling-K75KTNOO-CYnbEx8O.js +1 -0
  133. data/lib/archsight/web/public/vue/gitGraph-VSP46ZUC-DialfQSk.js +1 -0
  134. data/lib/archsight/web/public/vue/graphviz-1lWBhpEX.js +3 -0
  135. data/lib/archsight/web/public/vue/index-BLSE5DSv.css +1 -0
  136. data/lib/archsight/web/public/vue/index-CLuLTE5I.js +3 -0
  137. data/lib/archsight/web/public/vue/info-OHQRW6UA-BuhOW7ss.js +1 -0
  138. data/lib/archsight/web/public/vue/katex-B_JcIwqj.js +257 -0
  139. data/lib/archsight/web/public/vue/mermaid-C3bOamfn.js +3999 -0
  140. data/lib/archsight/web/public/vue/packet-JDAUHWVQ-CUeRz7w4.js +1 -0
  141. data/lib/archsight/web/public/vue/pie-XZMESJXO-DnuewJI6.js +1 -0
  142. data/lib/archsight/web/public/vue/radar-ABXABTNO-Bi9l8bcv.js +1 -0
  143. data/lib/archsight/web/public/vue/railroad-I3PHUGI6-DSlxFbxk.js +1 -0
  144. data/lib/archsight/web/public/vue/railroad-abnf-LNEFI6M7-D6Kqu8wu.js +1 -0
  145. data/lib/archsight/web/public/vue/railroad-ebnf-DWYD2UWJ-MR7uCYT0.js +1 -0
  146. data/lib/archsight/web/public/vue/railroad-peg-JR7BVNR7-BL7aKIc-.js +1 -0
  147. data/lib/archsight/web/public/vue/rolldown-runtime-COiEfhsA.js +1 -0
  148. data/lib/archsight/web/public/vue/treeView-D4JQ5SDB-CamtB04D.js +1 -0
  149. data/lib/archsight/web/public/vue/treemap-SAJKECNS-DeQxu_HM.js +1 -0
  150. data/lib/archsight/web/public/vue/{useGraphviz-DlSnFeCL.js → useGraphviz-Dnm2pkon.js} +2 -2
  151. data/lib/archsight/web/public/vue/useHighlight-BH32C7yt.js +10 -0
  152. data/lib/archsight/web/public/vue/usePanZoom-HxgEsqq2.js +12 -0
  153. data/lib/archsight/web/public/vue/wardley-7MLQ67FV-BvKIB6Tv.js +1 -0
  154. data/lib/archsight/web/public/vue.html +5 -5
  155. metadata +68 -41
  156. data/lib/archsight/web/public/vue/ApiDocsPage-BgqnQgwa.js +0 -1
  157. data/lib/archsight/web/public/vue/ApiDocsPage-DZ0fa5-h.css +0 -1
  158. data/lib/archsight/web/public/vue/DocPage-CNO71nBH.js +0 -1
  159. data/lib/archsight/web/public/vue/EditorPage-DR0FCNTz.js +0 -35
  160. data/lib/archsight/web/public/vue/ErrorPage-BfYArv6s.js +0 -2
  161. data/lib/archsight/web/public/vue/GraphView-BVTW30Ak.js +0 -1
  162. data/lib/archsight/web/public/vue/HomePage-Wk9P4Pma.js +0 -2
  163. data/lib/archsight/web/public/vue/InstanceRouter-BV8ycydz.js +0 -1
  164. data/lib/archsight/web/public/vue/InstanceRouter-jeavM6PW.css +0 -1
  165. data/lib/archsight/web/public/vue/KindList-C8yMsN5J.js +0 -1
  166. data/lib/archsight/web/public/vue/PageView-BaN6TyJB.js +0 -1
  167. data/lib/archsight/web/public/vue/QueryError-D790wH-a.js +0 -1
  168. data/lib/archsight/web/public/vue/ResourceList-B0FLaFR3.css +0 -1
  169. data/lib/archsight/web/public/vue/ResourceList-D-66nas2.js +0 -2
  170. data/lib/archsight/web/public/vue/SearchResults-CHyqIerQ.js +0 -1
  171. data/lib/archsight/web/public/vue/WikiPage-D2svpk6B.js +0 -13
  172. data/lib/archsight/web/public/vue/WikiPage-DoFJ0dbW.css +0 -1
  173. data/lib/archsight/web/public/vue/architecture-7GRP2DOG-BfA1TPxQ.js +0 -1
  174. data/lib/archsight/web/public/vue/cynefin-OW5HDTMX-BmgdUKVD.js +0 -1
  175. data/lib/archsight/web/public/vue/eventmodeling-NTZA5JFV-DbXDvAWF.js +0 -1
  176. data/lib/archsight/web/public/vue/gitGraph-4MIJSDKK-BkVhrKS1.js +0 -1
  177. data/lib/archsight/web/public/vue/graphviz-SXWTgZH5.js +0 -3
  178. data/lib/archsight/web/public/vue/index-BXXkUK1V.js +0 -3
  179. data/lib/archsight/web/public/vue/index-Cov1SnzY.css +0 -1
  180. data/lib/archsight/web/public/vue/info-A6RAGUB7-BIlCVuUY.js +0 -1
  181. data/lib/archsight/web/public/vue/katex-CXMH3UgJ.js +0 -257
  182. data/lib/archsight/web/public/vue/mermaid-BjEi5URd.js +0 -3334
  183. data/lib/archsight/web/public/vue/packet-AYTQ26CC-eoQTjY3j.js +0 -1
  184. data/lib/archsight/web/public/vue/pie-WAS4IAKB-DEOotGhh.js +0 -1
  185. data/lib/archsight/web/public/vue/radar-RG4KPBEZ-C2yucuUN.js +0 -1
  186. data/lib/archsight/web/public/vue/railroad-74A4TZTK-CzONK5VY.js +0 -1
  187. data/lib/archsight/web/public/vue/railroad-abnf-HS5TGJTU-9zRPsDsx.js +0 -1
  188. data/lib/archsight/web/public/vue/railroad-ebnf-LZEXJU2U-BJ4dkz9l.js +0 -1
  189. data/lib/archsight/web/public/vue/railroad-peg-WCYAUIDC-DFy_rqAj.js +0 -1
  190. data/lib/archsight/web/public/vue/treeView-Q6P3EWNA-2cq5CyD2.js +0 -1
  191. data/lib/archsight/web/public/vue/treemap-WGGIJYW6-DK-e9Ez4.js +0 -1
  192. data/lib/archsight/web/public/vue/useHighlight-D5Gf8DLl.js +0 -10
  193. data/lib/archsight/web/public/vue/wardley-WFR3VGLG-BQwqUqfB.js +0 -1
@@ -14,6 +14,12 @@ module Archsight
14
14
  "BusinessConstraint" => "MotivationConstraint"
15
15
  }.freeze
16
16
 
17
+ # Annotations that were renamed: the old key is rewritten to the new one when a document is loaded (see
18
+ # Database#accept_old_names) and reported as deprecated by `archsight lint`.
19
+ ANNOTATION_ALIASES = {
20
+ "page/confluence" => "link/confluence"
21
+ }.freeze
22
+
17
23
  # Relation keys (the plural kind names under `spec.<verb>`) that were renamed together with their kind
18
24
  RELATION_KEY_ALIASES = {
19
25
  "businessRequirements" => "motivationRequirements",
@@ -28,6 +34,12 @@ module Archsight
28
34
  DERIVED_VERBS = %i[mentions depicts].freeze
29
35
  DERIVED_KEY = :resources
30
36
 
37
+ # The layers a kind belongs to (Base.layer), top to bottom: why (motivation), what we want to become (strategy),
38
+ # how the change happens (implementation and migration), then the architecture it changes (business, application,
39
+ # technology). `other` holds the tool's own kinds (pages, views, imports, ...) and stays last. Everything that
40
+ # lists or orders layers uses this; the frontend repeats it in frontend/src/composables/useLayers.js.
41
+ LAYERS = %w[motivation strategy implementation business application technology other].freeze
42
+
31
43
  # The derived relations of every class, next to the ones it declares: [verb, key, kind]
32
44
  DERIVED_RELATIONS = DERIVED_VERBS.map { |verb| [verb, DERIVED_KEY, "Resource"] }.freeze
33
45
 
@@ -4,5 +4,5 @@
4
4
  # Do not edit manually.
5
5
 
6
6
  module Archsight
7
- VERSION = "0.3.3"
7
+ VERSION = "0.4.1"
8
8
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archsight; end
4
+ module Archsight::Web; end
5
+ module Archsight::Web::API; end
6
+
7
+ # JSON of `POST /api/v1/kinds/Analysis/instances/:name/execute`: the result and its sections
8
+ module Archsight::Web::API::AnalysisHelpers
9
+ def build_analysis_result(result)
10
+ data = {
11
+ name: result.name,
12
+ success: result.success?,
13
+ has_findings: result.has_findings?,
14
+ duration: result.duration,
15
+ sections: result.sections.map { |s| serialize_analysis_section(s) }
16
+ }
17
+
18
+ if result.failed?
19
+ data[:error] = result.error
20
+ data[:error_backtrace] = result.error_backtrace if result.error_backtrace&.any?
21
+ end
22
+
23
+ data
24
+ end
25
+
26
+ def serialize_analysis_section(section)
27
+ case section[:type]
28
+ when :heading
29
+ { type: "heading", level: section[:level], text: section[:text] }
30
+ when :text
31
+ { type: "text", content: markdown(section[:content]) }
32
+ # the *_html fields: the values as inline markdown with [[Kind/Name]] links, next to the raw values
33
+ when :message
34
+ { type: "message", level: section[:level].to_s, message: section[:message],
35
+ message_html: analysis_html(section[:message]) }
36
+ when :table
37
+ { type: "table", headers: section[:headers], rows: section[:rows],
38
+ rows_html: section[:rows].map { |row| row.map { |cell| analysis_html(cell) } } }
39
+ when :list
40
+ { type: "list", items: section[:items], items_html: section[:items].map { |item| analysis_html(item) } }
41
+ when :code
42
+ { type: "code", lang: section[:lang], content: section[:content] }
43
+ else
44
+ { type: section[:type].to_s }
45
+ end
46
+ end
47
+
48
+ # A value of a table cell, list item or message as html: HTML in it stays text (escaped first) except a line
49
+ # break (`<br>` or a newline, the usual way to break a line in a table cell), markdown and [[Kind/Name]] links are
50
+ # rendered; a value without markup is only escaped, so large tables stay cheap
51
+ def analysis_html(value)
52
+ escaped = Rack::Utils.escape_html(value.to_s).gsub(%r{&lt;br\s*/?&gt;}i, "<br>").gsub(/\r?\n/, "<br>")
53
+ escaped.match?(/[*_`\[{]|https?:/) ? inline_markdown(escaped) : escaped
54
+ end
55
+ end
@@ -11,8 +11,9 @@ module Archsight::Web::API; end
11
11
  module Archsight::Web::API::Docs
12
12
  extend Sinatra::Extension
13
13
 
14
+ # A page of its own, not the single page app: ReDoc must not get the app's CSS (Pico restyles every <code>)
14
15
  get "/docs/api" do
15
- serve_vue
16
+ send_file File.join(settings.public_folder, "vendor", "redoc", "api.html"), type: :html
16
17
  end
17
18
 
18
19
  # GET /api/v1/docs/resources/:filename — auto-generated resource docs
@@ -66,7 +66,8 @@ module Archsight::Web::API::JsonHelpers
66
66
  description: klass.description,
67
67
  layer: klass.layer,
68
68
  icon: klass.icon,
69
- instance_count: count
69
+ instance_count: count,
70
+ self_relations: Archsight::GraphvisRenderer.self_relations(klass).any?
70
71
  }
71
72
  end
72
73
  kinds.sort_by { |k| k[:kind] }
@@ -92,6 +93,7 @@ module Archsight::Web::API::JsonHelpers
92
93
  name: instance.name,
93
94
  metadata: { annotations: render_annotations(instance.annotations, base: Archsight::Assets.base_dir_for(instance, resources_dir: Archsight.resources_dir), kind: kind) },
94
95
  annotation_formats: Archsight::Resources[kind]&.annotation_formats(instance.annotations.keys) || {},
96
+ annotation_links: Archsight::Helpers::ResourceResolver.new(db).annotation_links(kind, instance.annotations),
95
97
  spec: serialize_spec(instance.spec),
96
98
  relations: extract_relations(instance),
97
99
  references: extract_references(instance),
@@ -193,42 +195,6 @@ module Archsight::Web::API::JsonHelpers
193
195
  end
194
196
  end
195
197
 
196
- def build_analysis_result(result)
197
- data = {
198
- name: result.name,
199
- success: result.success?,
200
- has_findings: result.has_findings?,
201
- duration: result.duration,
202
- sections: result.sections.map { |s| serialize_analysis_section(s) }
203
- }
204
-
205
- if result.failed?
206
- data[:error] = result.error
207
- data[:error_backtrace] = result.error_backtrace if result.error_backtrace&.any?
208
- end
209
-
210
- data
211
- end
212
-
213
- def serialize_analysis_section(section)
214
- case section[:type]
215
- when :heading
216
- { type: "heading", level: section[:level], text: section[:text] }
217
- when :text
218
- { type: "text", content: markdown(section[:content]) }
219
- when :message
220
- { type: "message", level: section[:level].to_s, message: section[:message] }
221
- when :table
222
- { type: "table", headers: section[:headers], rows: section[:rows] }
223
- when :list
224
- { type: "list", items: section[:items] }
225
- when :code
226
- { type: "code", lang: section[:lang], content: section[:content] }
227
- else
228
- { type: section[:type].to_s }
229
- end
230
- end
231
-
232
198
  def extract_references(instance)
233
199
  references = {}
234
200
 
@@ -254,7 +254,7 @@ paths:
254
254
  Returns one page with its metadata and its body rendered to HTML.
255
255
 
256
256
  **Metadata** comes from the frontmatter: `title` (falls back to the name), `status`, `author` and
257
- `owner` (`Name <email>`, split into `name` and `email`), `tags` and the `confluence` URL.
257
+ `owner` (`Name <email>`, split into `name` and `email`), `tags` and the `links` (name and URL, e.g. `confluence`).
258
258
 
259
259
  **`html`** is the markdown body rendered once per request:
260
260
  - GitHub-flavoured markdown (tables, fenced code, task lists).
@@ -309,7 +309,7 @@ paths:
309
309
  owner:
310
310
  name: Jane Doe
311
311
  email: jane.doe@example.com
312
- confluence: null
312
+ links: {}
313
313
  tags:
314
314
  - howto
315
315
  - wiki
@@ -623,12 +623,10 @@ paths:
623
623
  type: object
624
624
  properties:
625
625
  html:
626
- type: string
627
- nullable: true
626
+ type: [string, "null"]
628
627
  description: '`<figure class="asd-diagram">` with the SVG'
629
628
  error:
630
- type: string
631
- nullable: true
629
+ type: [string, "null"]
632
630
  '400':
633
631
  description: Body is not JSON with a `source` string
634
632
  content:
@@ -708,9 +706,11 @@ paths:
708
706
  summary: Requirements of a selection of resources
709
707
  description: |
710
708
  The requirements that the resources selected by `of` realize (`implemented`), partially realize
711
- (`partial`) or plan (`planned`), merged over all of them: one entry per requirement with the best status and
712
- the resources it comes from (`by`). Sorted by priority (must, should, may, none), then name. `story` is
713
- rendered HTML.
709
+ (`partial`) or plan (`planned`), merged over all of them: one entry per requirement, with the best status
710
+ any of the resources gives it and the resources it comes from (`by`, each with its own status). Sorted by
711
+ priority (must, should, may, requirements without a priority last), then name. `story` is rendered HTML.
712
+ `priority` and `status` filter the merged entries, so `status=partial` lists requirements whose best
713
+ status is partial.
714
714
  operationId: getRequirements
715
715
  tags:
716
716
  - Search
@@ -725,14 +725,14 @@ paths:
725
725
  - name: priority
726
726
  in: query
727
727
  required: false
728
- description: Comma separated list, only requirements with one of these priorities
728
+ description: Comma separated list, only requirements with one of these priorities (must, should, may)
729
729
  schema:
730
730
  type: string
731
731
  example: must,should
732
732
  - name: status
733
733
  in: query
734
734
  required: false
735
- description: Comma separated list, only requirements with one of these statuses
735
+ description: Comma separated list, only requirements with one of these statuses (implemented, partial, planned)
736
736
  schema:
737
737
  type: string
738
738
  example: implemented,partial
@@ -743,33 +743,42 @@ paths:
743
743
  application/json:
744
744
  schema:
745
745
  type: object
746
+ required: [query, total, query_time_ms, requirements]
746
747
  properties:
747
748
  query:
748
749
  type: string
750
+ description: The `of` query as given
749
751
  total:
750
752
  type: integer
753
+ description: Number of requirements returned
751
754
  query_time_ms:
752
755
  type: number
753
756
  requirements:
754
757
  type: array
755
758
  items:
756
759
  type: object
760
+ required: [name, status, priority, story, by]
757
761
  properties:
758
762
  name:
759
763
  type: string
764
+ description: Name of the MotivationRequirement
760
765
  status:
761
766
  type: string
762
767
  enum: [implemented, partial, planned]
768
+ description: The best status among the resources in `by`
763
769
  priority:
764
- type: string
765
- nullable: true
770
+ type: [string, "null"]
771
+ enum: [must, should, may, null]
772
+ description: Priority of the requirement, null if it has none
766
773
  story:
767
- type: string
768
- nullable: true
774
+ type: [string, "null"]
775
+ description: The requirement's story as rendered HTML, null if it has none
769
776
  by:
770
777
  type: array
778
+ description: The selected resources that realize, partially realize or plan the requirement, best status first
771
779
  items:
772
780
  type: object
781
+ required: [kind, name, status]
773
782
  properties:
774
783
  kind:
775
784
  type: string
@@ -778,12 +787,118 @@ paths:
778
787
  status:
779
788
  type: string
780
789
  enum: [implemented, partial, planned]
790
+ description: The status this resource gives the requirement
791
+ example:
792
+ query: 'ApplicationService: name =~ "Backup"'
793
+ total: 1
794
+ query_time_ms: 1.4
795
+ requirements:
796
+ - name: "Requirement:Restore"
797
+ status: partial
798
+ priority: must
799
+ story: "<p>As an operator I can restore a backup.</p>"
800
+ by:
801
+ - kind: ApplicationService
802
+ name: Backup
803
+ status: partial
781
804
  '400':
782
- description: Invalid query, or an unknown priority or status
805
+ description: |
806
+ `of` is missing or `priority` or `status` holds an unknown value (`BadRequest`), or `of` is not a valid
807
+ query (`QueryError`, which also returns the query)
783
808
  content:
784
809
  application/json:
785
810
  schema:
786
- $ref: '#/components/schemas/QueryError'
811
+ oneOf:
812
+ - $ref: '#/components/schemas/Error'
813
+ - $ref: '#/components/schemas/QueryError'
814
+
815
+ /api/v1/openapi.yaml:
816
+ get:
817
+ summary: This OpenAPI document
818
+ description: |
819
+ The OpenAPI 3.1 description of this API. The fixed endpoints are written by hand; the schema of every kind
820
+ (its annotations with type, enum and description, its `spec` and its relations), the list of kinds and the
821
+ instance detail are generated from the model, so they always describe the kinds this server has.
822
+ operationId: getOpenApi
823
+ tags:
824
+ - Operations
825
+ responses:
826
+ '200':
827
+ description: The OpenAPI document
828
+ content:
829
+ text/yaml:
830
+ schema:
831
+ type: string
832
+
833
+ /maintenance/restart:
834
+ post:
835
+ summary: Restart the server
836
+ description: |
837
+ Stops the server so that its supervisor (for example a Kubernetes Deployment) starts it again with fresh
838
+ data. Only available when the server was started with `--enable-restart` (or
839
+ `ARCHSIGHT_RESTART_ENABLED=true`); otherwise it answers 404. When a restart token is configured it must be
840
+ sent in the `X-Restart-Token` header.
841
+ operationId: restart
842
+ tags:
843
+ - Operations
844
+ parameters:
845
+ - name: X-Restart-Token
846
+ in: header
847
+ required: false
848
+ description: The configured restart token (required when the server has one)
849
+ schema:
850
+ type: string
851
+ responses:
852
+ '200':
853
+ description: The server shuts down
854
+ content:
855
+ application/json:
856
+ schema:
857
+ type: object
858
+ required: [ok, message]
859
+ properties:
860
+ ok:
861
+ type: boolean
862
+ message:
863
+ type: string
864
+ example:
865
+ ok: true
866
+ message: Server shutting down
867
+ '401':
868
+ description: The restart token is missing or wrong
869
+ content:
870
+ text/html:
871
+ schema:
872
+ type: string
873
+ '404':
874
+ description: The restart endpoint is not enabled
875
+ content:
876
+ text/html:
877
+ schema:
878
+ type: string
879
+
880
+ /kinds/{kind}/dot:
881
+ get:
882
+ summary: Get the graph of a kind as DOT
883
+ description: The GraphViz DOT graph of the resources of a kind and the relations between them.
884
+ operationId: getKindDot
885
+ tags:
886
+ - Graph
887
+ parameters:
888
+ - $ref: '#/components/parameters/kind'
889
+ responses:
890
+ '200':
891
+ description: GraphViz DOT source
892
+ content:
893
+ text/plain:
894
+ schema:
895
+ type: string
896
+ '404':
897
+ description: Unknown kind
898
+ content:
899
+ text/plain:
900
+ schema:
901
+ type: string
787
902
 
788
903
  /dot:
789
904
  get:
@@ -1343,13 +1458,11 @@ components:
1343
1458
  items:
1344
1459
  type: string
1345
1460
  format:
1346
- type: string
1347
- nullable: true
1461
+ type: [string, "null"]
1348
1462
  description: Rendering hint of the annotation (tag_word, tag_list, markdown, link, ...)
1349
1463
  example: tag_word
1350
1464
  type:
1351
- type: string
1352
- nullable: true
1465
+ type: [string, "null"]
1353
1466
  description: Short name of the value type when the annotation declares one (Integer, Float, Time, Person, ...)
1354
1467
  example: Integer
1355
1468
 
@@ -1489,7 +1602,7 @@ components:
1489
1602
  description: Whether the analysis executed successfully
1490
1603
  has_findings:
1491
1604
  type: boolean
1492
- description: Whether the analysis produced findings
1605
+ description: Whether the script produced any output (tables, lists, text, ...). Output is not a problem; errors and warnings the script reports are message sections
1493
1606
  duration:
1494
1607
  type: number
1495
1608
  format: float
@@ -1684,26 +1797,22 @@ components:
1684
1797
  type: object
1685
1798
  properties:
1686
1799
  yaml:
1687
- type: string
1688
- nullable: true
1800
+ type: [string, "null"]
1689
1801
  description: Generated source text, YAML or (for pages) markdown with frontmatter (null if validation failed)
1690
1802
  format:
1691
1803
  type: string
1692
1804
  enum: [yaml, markdown]
1693
1805
  description: Format of `yaml`. Pages (kind Page) are stored as markdown files and replaced as a whole file
1694
1806
  errors:
1695
- type: array
1696
- nullable: true
1807
+ type: [array, "null"]
1697
1808
  items:
1698
1809
  type: string
1699
1810
  description: Validation errors (null if valid)
1700
1811
  path_ref:
1701
- type: string
1702
- nullable: true
1812
+ type: [string, "null"]
1703
1813
  description: "File path reference (edit mode only, e.g., path/file.yaml:5; just the file for markdown pages)"
1704
1814
  content_hash:
1705
- type: string
1706
- nullable: true
1815
+ type: [string, "null"]
1707
1816
  description: Content hash (edit mode only, for optimistic locking)
1708
1817
 
1709
1818
  SaveRequest:
@@ -1869,7 +1978,7 @@ components:
1869
1978
 
1870
1979
  PageResponse:
1871
1980
  type: object
1872
- required: [name, title, status, author, owner, created, updated, properties, confluence, tags, toc, breadcrumb, html, backlinks]
1981
+ required: [name, title, status, author, owner, created, updated, properties, links, tags, toc, breadcrumb, html, backlinks]
1873
1982
  properties:
1874
1983
  name:
1875
1984
  type: string
@@ -1909,10 +2018,12 @@ components:
1909
2018
  html:
1910
2019
  type: string
1911
2020
  description: The value rendered as inline markdown (links, emphasis, `{macros}`, `[[wiki links]]`), without a paragraph around it
1912
- confluence:
1913
- type: [string, 'null']
1914
- format: uri
1915
- description: URL of the corresponding Confluence page
2021
+ links:
2022
+ type: object
2023
+ description: Links of the page to other systems, by name (`confluence`, `jira`, ...)
2024
+ additionalProperties:
2025
+ type: string
2026
+ format: uri
1916
2027
  tags:
1917
2028
  type: array
1918
2029
  items:
@@ -41,7 +41,7 @@ module Archsight::Web::API::PageHelpers
41
41
  created: annotations["page/created"],
42
42
  updated: annotations["page/updated"],
43
43
  properties: page.properties.map { |key, value| { key: key, value: value, html: inline_markdown(value, page) } },
44
- confluence: annotations["page/confluence"],
44
+ links: annotations.select { |key, _| key.start_with?("link/") }.transform_keys { |key| key.delete_prefix("link/") },
45
45
  tags: Archsight::Resources::Page.annotation_matching("page/tags")&.value_for(page) || [],
46
46
  toc: annotations["page/toc"] == "yes" ? Archsight::PageTree.toc(body) : [],
47
47
  breadcrumb: tree.breadcrumb(page),
@@ -50,13 +50,6 @@ module Archsight::Web::API::PageHelpers
50
50
  }
51
51
  end
52
52
 
53
- # A short text (a page property) rendered as inline markdown: links, emphasis, `{macros}`, [[wiki links]], without the
54
- # paragraph around it
55
- def inline_markdown(text, page)
56
- html = markdown(text.to_s, base: Archsight::Assets.base_dir_for(page, resources_dir: Archsight.resources_dir), page: page)
57
- html.strip.sub(%r{\A<p>(.*)</p>\z}m, '\1')
58
- end
59
-
60
53
  # The header already shows the title, so a leading "# Title" that repeats it is dropped
61
54
  # (Confluence exports and hand-written pages usually start with one).
62
55
  def strip_duplicate_title(body, page)
@@ -5,7 +5,9 @@ require "sinatra/extension"
5
5
  require_relative "json_helpers"
6
6
  require_relative "page_helpers"
7
7
  require_relative "requirements_helpers"
8
+ require_relative "analysis_helpers"
8
9
  require_relative "../../assets"
10
+ require_relative "../../openapi"
9
11
 
10
12
  module Archsight; end
11
13
  module Archsight::Web; end
@@ -18,6 +20,7 @@ module Archsight::Web::API::Routes
18
20
  helpers Archsight::Web::API::JsonHelpers
19
21
  helpers Archsight::Web::API::PageHelpers
20
22
  helpers Archsight::Web::API::RequirementsHelpers
23
+ helpers Archsight::Web::API::AnalysisHelpers
21
24
 
22
25
  # Rendering is CPU work, previews are typed, not pasted
23
26
  MAX_DIAGRAM_SOURCE = 100_000
@@ -67,10 +70,13 @@ module Archsight::Web::API::Routes
67
70
  klass = Archsight::Resources[kind]
68
71
  json_error("Kind '#{kind}' not found", status: 404, error_type: "NotFound") unless klass
69
72
 
70
- instance = db.instance_by_kind(kind, name)
73
+ # an alias finds the one resource that has it; the response carries the real name
74
+ instance = db.instance_by_kind(kind, name) || db.instance_by_alias(kind, name)
71
75
  json_error("Instance '#{name}' not found", status: 404, error_type: "NotFound") unless instance
72
76
 
73
- json_response(build_instance_response(kind, instance))
77
+ # graph_layers: the layers its dependency graph follows when the page opens
78
+ json_response(build_instance_response(kind, instance)
79
+ .merge(graph_layers: Archsight::GraphvisRenderer.default_graph_layers(instance.class.layer)))
74
80
  end
75
81
 
76
82
  # GET /api/v1/pages - Page tree built from PageMenu resources, page tags and filters and the home page
@@ -215,8 +221,7 @@ module Archsight::Web::API::Routes
215
221
  # GET /api/v1/openapi.yaml - OpenAPI specification
216
222
  get "/api/v1/openapi.yaml" do
217
223
  content_type "text/yaml"
218
- spec_path = File.join(__dir__, "openapi", "spec.yaml")
219
- File.read(spec_path)
224
+ Archsight::OpenAPI.document.to_yaml
220
225
  end
221
226
 
222
227
  # Convenience aliases with .json suffix
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sinatra/base"
4
- require "kramdown"
4
+ require_relative "../markdown"
5
5
  require "erb"
6
6
  require "fast_mcp"
7
7
 
@@ -25,17 +25,25 @@ class Archsight::Web::Application < Sinatra::Base
25
25
  attr_accessor :db
26
26
 
27
27
  def database
28
- @database ||= Archsight::Database.new(Archsight.resources_dir).tap(&:reload!)
28
+ @database ||= Archsight::Database.new(Archsight.resources_dir).tap(&:reload!).tap { |db| load_import_handlers(db) }
29
29
  end
30
30
 
31
31
  def reload!
32
32
  start = Time.new
33
33
  puts "== Reloading ..." if database.verbose
34
34
  database.reload!
35
+ load_import_handlers(database)
35
36
  dur = (Time.new - start) * 1000
36
37
  puts format("== done %0.2f ms", dur) if database.verbose
37
38
  end
38
39
 
40
+ # The import handlers, built-in and of the resources repository (import/handlerPath), so the editor knows the
41
+ # same handler names as `archsight lint`. A file that does not load is reported, the server keeps running.
42
+ def load_import_handlers(db)
43
+ require "archsight/import"
44
+ Archsight::Import::HandlerLoader.load(db).each { |problem| warn "Import handler: #{problem}" }
45
+ end
46
+
39
47
  # Configure application for the given environment
40
48
  # @param env [Symbol] :development or :production
41
49
  # @param logging [Boolean, nil] Override default logging setting
@@ -135,13 +143,22 @@ class Archsight::Web::Application < Sinatra::Base
135
143
  settings.environment == :development
136
144
  end
137
145
 
146
+ # A short text (a page property, an analysis table cell) rendered as inline markdown: links, emphasis,
147
+ # `{macros}`, [[wiki links]], without the paragraph around it
148
+ # @param page [Archsight::Resources::Page, nil] the page the text belongs to, for its relative images and macros
149
+ def inline_markdown(text, page = nil)
150
+ base = page && Archsight::Assets.base_dir_for(page, resources_dir: Archsight.resources_dir)
151
+ html = markdown(text.to_s, base: base, page: page)
152
+ html.strip.sub(%r{\A<p>(.*)</p>\z}m, '\1')
153
+ end
154
+
138
155
  # Render markdown to HTML with optional URL resolution for repository content
139
156
  # @param data [String] Markdown content
140
157
  # @param git_url [String, nil] Git URL for resolving relative paths (e.g., for README images)
141
158
  # @param base [String, nil] directory of the file the markdown comes from, relative to the resources
142
159
  # directory ("" for the root): its relative images become assets (see Archsight::Assets)
143
160
  def markdown(data, git_url: nil, base: nil, page: nil)
144
- html = Kramdown::Document.new(data, input: "GFM").to_html
161
+ html = Archsight::Markdown.document(data).to_html
145
162
  html = Archsight::Helpers::AssetImages.rewrite(html, base_dir: base, resources_dir: Archsight.resources_dir) if base
146
163
  # ```asd blocks become placeholders until the text passes below are done (see DiagramBlocks)
147
164
  html, diagrams = Archsight::Helpers::DiagramBlocks.extract(html, resolver: Archsight::Helpers::ResourceResolver.new(db))
@@ -282,14 +299,33 @@ class Archsight::Web::Application < Sinatra::Base
282
299
  serve_vue
283
300
  end
284
301
 
302
+ # The resources of a kind and the relations between them (see GraphvisRenderer#create_graph_kind)
303
+ get "/kinds/:kind/dot" do
304
+ halt 404, "kind #{params["kind"]} unknown" unless Archsight::Resources[params["kind"]]
305
+
306
+ content_type "text/plain"
307
+ create_graph_kind(db, params["kind"])
308
+ end
309
+
310
+ # An alias in the URL redirects to the resource's real name (`:` stays readable, the rest is escaped)
285
311
  get "/kinds/:kind/instances/:instance" do
312
+ kind = params["kind"]
313
+ name = params["instance"]
314
+ if Archsight::Resources[kind] && !db.instance_by_kind(kind, name) && (instance = db.instance_by_alias(kind, name))
315
+ redirect "/kinds/#{kind}/instances/#{ERB::Util.url_encode(instance.name).gsub("%3A", ":")}"
316
+ end
317
+
286
318
  serve_vue
287
319
  end
288
320
 
321
+ # `depth`: steps from the instance (a positive number; missing or `all`: every step), `layers`: the layers it
322
+ # follows, comma separated (missing or none known: every layer)
289
323
  get "/kinds/:kind/instances/:instance/dot" do
290
324
  @kind = params["kind"]
291
325
  @instance = params["instance"]
326
+ depth = params["depth"].to_s.match?(/\A[1-9]\d*\z/) ? params["depth"].to_i : nil
327
+ layers = params["layers"].to_s.split(",").map(&:strip) & Archsight::Resources::LAYERS
292
328
  content_type "text/plain"
293
- create_graph_one(db, @kind, @instance)
329
+ create_graph_one(db, @kind, @instance, max_depth: depth, layers: layers.empty? ? nil : layers)
294
330
  end
295
331
  end
@@ -57,7 +57,8 @@ module Archsight
57
57
  data = build_form_metadata(kind, klass)
58
58
  data[:mode] = "edit"
59
59
  data[:name] = instance.name
60
- data[:annotations] = instance.annotations.dup
60
+ # defaults are not in the file, a save leaves them out
61
+ data[:annotations] = instance.annotations.reject { |key, _| instance.defaulted?(key) }
61
62
  data[:relations] = extract_instance_relations(instance)
62
63
  data[:path_ref] = source_ref(instance)
63
64
  data[:content_hash] = source_content_hash(instance)
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-present, Rebilly, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1 @@
1
+ 2.5.4