archsight 0.4.0 → 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.
- checksums.yaml +4 -4
- data/docs/configuration.md +1 -1
- data/docs/modeling.md +8 -0
- data/docs/pages.md +2 -2
- data/lib/archsight/analysis/typst_renderer.rb +2 -3
- data/lib/archsight/export/confluence/analysis_output.rb +3 -4
- data/lib/archsight/export/confluence/exporter.rb +3 -1
- data/lib/archsight/export/confluence/page_properties.rb +2 -3
- data/lib/archsight/export/confluence/storage.rb +25 -12
- data/lib/archsight/export/confluence/tables.rb +2 -3
- data/lib/archsight/git_source.rb +81 -0
- data/lib/archsight/helpers/asset_images.rb +3 -4
- data/lib/archsight/import/executor.rb +1 -0
- data/lib/archsight/import.rb +27 -2
- data/lib/archsight/linter.rb +2 -2
- data/lib/archsight/markdown.rb +48 -0
- data/lib/archsight/openapi.rb +169 -0
- data/lib/archsight/page_tree.rb +2 -3
- data/lib/archsight/resources/application_interface.rb +12 -0
- data/lib/archsight/resources/technology_communication_network.rb +2 -0
- data/lib/archsight/version.rb +1 -1
- data/lib/archsight/web/api/docs.rb +2 -1
- data/lib/archsight/web/api/openapi/spec.yaml +136 -27
- data/lib/archsight/web/api/routes.rb +2 -2
- data/lib/archsight/web/application.rb +2 -2
- data/lib/archsight/web/public/vendor/redoc/LICENSE +22 -0
- data/lib/archsight/web/public/vendor/redoc/VERSION +1 -0
- data/lib/archsight/web/public/vendor/redoc/api.html +27 -0
- data/lib/archsight/web/public/vendor/redoc/api.js +19 -0
- data/lib/archsight/web/public/vendor/redoc/redoc.standalone.js +1851 -0
- data/lib/archsight/web/public/vendor/redoc/redoc.standalone.js.LICENSE.txt +102 -0
- data/lib/archsight/web/public/vue/{DocPage-D52OwaKc.js → DocPage-C2ubTXH7.js} +1 -1
- data/lib/archsight/web/public/vue/EditorPage-Dso6PLMn.js +35 -0
- data/lib/archsight/web/public/vue/ErrorPage-DnZ6YJLi.js +2 -0
- data/lib/archsight/web/public/vue/GraphView-CTl03Yre.js +1 -0
- data/lib/archsight/web/public/vue/HomePage-C339nihQ.js +2 -0
- data/lib/archsight/web/public/vue/InstanceRouter-DFW7tOrD.js +1 -0
- data/lib/archsight/web/public/vue/KindList-C0oaM3CF.js +1 -0
- data/lib/archsight/web/public/vue/PageView-OZB6G9Wu.js +1 -0
- data/lib/archsight/web/public/vue/QueryError-C2xLkCEm.js +1 -0
- data/lib/archsight/web/public/vue/ResourceList-DooWFwQP.js +2 -0
- data/lib/archsight/web/public/vue/SearchResults-BK-HjFnv.js +1 -0
- data/lib/archsight/web/public/vue/WikiPage-B2FYmeHL.js +3 -0
- data/lib/archsight/web/public/vue/architecture-WOLXFQ4H-BGN-TV5r.js +1 -0
- data/lib/archsight/web/public/vue/cynefin-EF2NZ3EQ-CZd6g_tq.js +1 -0
- data/lib/archsight/web/public/vue/eventmodeling-K75KTNOO-CYnbEx8O.js +1 -0
- data/lib/archsight/web/public/vue/gitGraph-VSP46ZUC-DialfQSk.js +1 -0
- data/lib/archsight/web/public/vue/graphviz-1lWBhpEX.js +3 -0
- data/lib/archsight/web/public/vue/{index-eUHmJ7K2.css → index-BLSE5DSv.css} +1 -1
- data/lib/archsight/web/public/vue/index-CLuLTE5I.js +3 -0
- data/lib/archsight/web/public/vue/info-OHQRW6UA-BuhOW7ss.js +1 -0
- data/lib/archsight/web/public/vue/katex-B_JcIwqj.js +257 -0
- data/lib/archsight/web/public/vue/mermaid-C3bOamfn.js +3999 -0
- data/lib/archsight/web/public/vue/packet-JDAUHWVQ-CUeRz7w4.js +1 -0
- data/lib/archsight/web/public/vue/pie-XZMESJXO-DnuewJI6.js +1 -0
- data/lib/archsight/web/public/vue/radar-ABXABTNO-Bi9l8bcv.js +1 -0
- data/lib/archsight/web/public/vue/railroad-I3PHUGI6-DSlxFbxk.js +1 -0
- data/lib/archsight/web/public/vue/railroad-abnf-LNEFI6M7-D6Kqu8wu.js +1 -0
- data/lib/archsight/web/public/vue/railroad-ebnf-DWYD2UWJ-MR7uCYT0.js +1 -0
- data/lib/archsight/web/public/vue/railroad-peg-JR7BVNR7-BL7aKIc-.js +1 -0
- data/lib/archsight/web/public/vue/rolldown-runtime-COiEfhsA.js +1 -0
- data/lib/archsight/web/public/vue/treeView-D4JQ5SDB-CamtB04D.js +1 -0
- data/lib/archsight/web/public/vue/treemap-SAJKECNS-DeQxu_HM.js +1 -0
- data/lib/archsight/web/public/vue/{useGraphviz-4evusK9i.js → useGraphviz-Dnm2pkon.js} +2 -2
- data/lib/archsight/web/public/vue/useHighlight-BH32C7yt.js +10 -0
- data/lib/archsight/web/public/vue/usePanZoom-HxgEsqq2.js +12 -0
- data/lib/archsight/web/public/vue/wardley-7MLQ67FV-BvKIB6Tv.js +1 -0
- data/lib/archsight/web/public/vue.html +4 -4
- metadata +46 -39
- data/lib/archsight/web/public/vue/ApiDocsPage-COp4onwn.js +0 -1
- data/lib/archsight/web/public/vue/ApiDocsPage-DZ0fa5-h.css +0 -1
- data/lib/archsight/web/public/vue/EditorPage-3aqL7yVf.js +0 -35
- data/lib/archsight/web/public/vue/ErrorPage--b9XeSYa.js +0 -2
- data/lib/archsight/web/public/vue/GraphView-qh_EtRrQ.js +0 -1
- data/lib/archsight/web/public/vue/HomePage-C2TNh2U-.js +0 -2
- data/lib/archsight/web/public/vue/InstanceRouter-uzb_OMG0.js +0 -1
- data/lib/archsight/web/public/vue/KindList-CyJuP_uq.js +0 -1
- data/lib/archsight/web/public/vue/PageView-FyygrS-b.js +0 -1
- data/lib/archsight/web/public/vue/QueryError-B4Ex_csZ.js +0 -1
- data/lib/archsight/web/public/vue/ResourceList-BY6Z1Kcb.js +0 -2
- data/lib/archsight/web/public/vue/SearchResults-CkFpHF8m.js +0 -1
- data/lib/archsight/web/public/vue/WikiPage-BoHNewCX.js +0 -3
- data/lib/archsight/web/public/vue/architecture-7GRP2DOG-BvzKZE4e.js +0 -1
- data/lib/archsight/web/public/vue/cynefin-OW5HDTMX-D-xs_Hxh.js +0 -1
- data/lib/archsight/web/public/vue/eventmodeling-NTZA5JFV-nbjxG01S.js +0 -1
- data/lib/archsight/web/public/vue/gitGraph-4MIJSDKK-CW6mw2ab.js +0 -1
- data/lib/archsight/web/public/vue/graphviz-SXWTgZH5.js +0 -3
- data/lib/archsight/web/public/vue/index-BGlYCbi7.js +0 -3
- data/lib/archsight/web/public/vue/info-A6RAGUB7-C12sjlHe.js +0 -1
- data/lib/archsight/web/public/vue/katex-CXMH3UgJ.js +0 -257
- data/lib/archsight/web/public/vue/mermaid-DjsXQ_XN.js +0 -3334
- data/lib/archsight/web/public/vue/packet-AYTQ26CC-wMwKwHmA.js +0 -1
- data/lib/archsight/web/public/vue/pie-WAS4IAKB-w1-sAU_K.js +0 -1
- data/lib/archsight/web/public/vue/radar-RG4KPBEZ-DpwqIFoy.js +0 -1
- data/lib/archsight/web/public/vue/railroad-74A4TZTK-Ce5XC24d.js +0 -1
- data/lib/archsight/web/public/vue/railroad-abnf-HS5TGJTU-BSQO2pds.js +0 -1
- data/lib/archsight/web/public/vue/railroad-ebnf-LZEXJU2U-Dq1c2YtD.js +0 -1
- data/lib/archsight/web/public/vue/railroad-peg-WCYAUIDC-DKj6dli0.js +0 -1
- data/lib/archsight/web/public/vue/rolldown-runtime-hePW80VL.js +0 -1
- data/lib/archsight/web/public/vue/treeView-Q6P3EWNA-BmHNTWxU.js +0 -1
- data/lib/archsight/web/public/vue/treemap-WGGIJYW6-DsEI7sSD.js +0 -1
- data/lib/archsight/web/public/vue/useHighlight-DPguaxfm.js +0 -10
- data/lib/archsight/web/public/vue/usePanZoom-seQE1TPk.js +0 -12
- data/lib/archsight/web/public/vue/wardley-WFR3VGLG-CSCW83-M.js +0 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require_relative "resources"
|
|
6
|
+
|
|
7
|
+
module Archsight
|
|
8
|
+
# The OpenAPI document of the REST API: the hand-written base (`web/api/openapi/spec.yaml`: the fixed endpoints such
|
|
9
|
+
# as pages, assets, search and the editor, with their prose, examples and error shapes) plus what the model knows and
|
|
10
|
+
# nobody should type twice: one schema per kind with its annotations (type, enum, description, default), its `spec`
|
|
11
|
+
# relations and its relations, the list of kinds, and the instance detail schema that is one of them. A new kind, a
|
|
12
|
+
# changed enum or another relation shows up in the documentation without touching the YAML.
|
|
13
|
+
#
|
|
14
|
+
# Archsight::OpenAPI.document # => Hash, `.to_yaml` is what GET /api/v1/openapi.yaml serves
|
|
15
|
+
module OpenAPI
|
|
16
|
+
BASE = File.join(__dir__, "web", "api", "openapi", "spec.yaml")
|
|
17
|
+
|
|
18
|
+
# The operations that answer with the detail of one instance, whose kind the path names
|
|
19
|
+
DETAIL_PATHS = ["/api/v1/kinds/{kind}/instances/{name}", "/kinds/{kind}/instances/{name}.json"].freeze
|
|
20
|
+
|
|
21
|
+
# What an annotation of a type is in the JSON. The value is the one of the YAML file, so a number may also be its
|
|
22
|
+
# string ("100"); only a computed annotation is always a number. Everything else is a string.
|
|
23
|
+
TYPES = {
|
|
24
|
+
Integer => { "type" => %w[integer string] },
|
|
25
|
+
Float => { "type" => %w[number string] }
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
NAMES = { "type" => "array", "items" => { "type" => "string" } }.freeze
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
# @return [Hash] the complete document; built once, the model does not change while the server runs
|
|
32
|
+
def document
|
|
33
|
+
@document ||= build
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Forget the built document (the model was changed, in a test)
|
|
37
|
+
def reset!
|
|
38
|
+
@document = nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The name of the schema of a kind
|
|
42
|
+
def schema_name(kind) = "#{kind}Instance"
|
|
43
|
+
|
|
44
|
+
# The schema of one kind: the detail of an instance as GET /api/v1/kinds/{kind}/instances/{name} returns it
|
|
45
|
+
# @param kind [String] name of a kind
|
|
46
|
+
# @return [Hash]
|
|
47
|
+
def kind_schema(kind)
|
|
48
|
+
klass = Archsight::Resources[kind]
|
|
49
|
+
schema = {
|
|
50
|
+
"type" => "object",
|
|
51
|
+
"required" => %w[kind name metadata spec relations references],
|
|
52
|
+
"x-layer" => klass.layer,
|
|
53
|
+
"properties" => {
|
|
54
|
+
"kind" => { "type" => "string", "const" => kind },
|
|
55
|
+
"name" => { "type" => "string", "description" => "Name of the instance" },
|
|
56
|
+
"metadata" => { "type" => "object", "properties" => { "annotations" => annotations_schema(klass) } },
|
|
57
|
+
"annotation_formats" => { "type" => "object", "additionalProperties" => { "type" => "string" },
|
|
58
|
+
"description" => "How the UI shows each annotation that is set (markdown, link, tag_list, ...)" },
|
|
59
|
+
"annotation_links" => { "type" => "object", "additionalProperties" => true,
|
|
60
|
+
"description" => "Annotations whose value names resources, with the resources they resolve to" },
|
|
61
|
+
"spec" => spec_schema(klass),
|
|
62
|
+
"relations" => relations_schema(klass),
|
|
63
|
+
"references" => references_schema,
|
|
64
|
+
"diagram" => { "type" => %w[string null], "description" => "The `architecture/diagram` annotation as inline SVG" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
schema["description"] = klass.description.strip if klass.description
|
|
68
|
+
schema
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def build
|
|
74
|
+
document = YAML.safe_load_file(BASE, permitted_classes: [], aliases: true)
|
|
75
|
+
kinds = Archsight::Resources.resource_classes.keys.sort
|
|
76
|
+
schemas = document.fetch("components").fetch("schemas")
|
|
77
|
+
add_schemas(schemas, kinds)
|
|
78
|
+
document.dig("components", "parameters", "kind")["schema"] = { "$ref" => "#/components/schemas/KindName" }
|
|
79
|
+
DETAIL_PATHS.each { |path| use_detail_schema(document.fetch("paths").fetch(path)) }
|
|
80
|
+
# the schemas share small hashes: a copy of each keeps YAML from writing anchors and aliases for them
|
|
81
|
+
JSON.parse(JSON.generate(document))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def add_schemas(schemas, kinds)
|
|
85
|
+
kinds.each do |kind|
|
|
86
|
+
name = schema_name(kind)
|
|
87
|
+
raise ArgumentError, "OpenAPI: the schema #{name} of #{kind} clashes with a schema of the base spec" if schemas.key?(name)
|
|
88
|
+
|
|
89
|
+
schemas[name] = kind_schema(kind)
|
|
90
|
+
end
|
|
91
|
+
schemas["KindName"] = {
|
|
92
|
+
"type" => "string", "enum" => kinds,
|
|
93
|
+
"description" => "Name of a resource kind. A kind that was renamed is still accepted under its old name."
|
|
94
|
+
}
|
|
95
|
+
schemas["InstanceDetail"] = {
|
|
96
|
+
"description" => "An instance with its relations and references: the schema depends on the kind",
|
|
97
|
+
"oneOf" => kinds.map { |kind| { "$ref" => "#/components/schemas/#{schema_name(kind)}" } },
|
|
98
|
+
"discriminator" => { "propertyName" => "kind", "mapping" => kinds.to_h { |kind| [kind, "#/components/schemas/#{schema_name(kind)}"] } }
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def use_detail_schema(path_item)
|
|
103
|
+
path_item.dig("get", "responses", "200", "content", "application/json")["schema"] = { "$ref" => "#/components/schemas/InstanceDetail" }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The annotations of the kind that are set; every annotation is optional. Other annotations are allowed.
|
|
107
|
+
def annotations_schema(klass)
|
|
108
|
+
fixed, patterns = klass.annotations.partition { |annotation| !annotation.pattern? }
|
|
109
|
+
schema = {
|
|
110
|
+
"type" => "object", "additionalProperties" => true,
|
|
111
|
+
"properties" => fixed.to_h { |annotation| [annotation.key, annotation_schema(annotation)] }
|
|
112
|
+
}
|
|
113
|
+
schema["patternProperties"] = patterns.to_h { |annotation| [pattern_of(annotation.key), annotation_schema(annotation)] } unless patterns.empty?
|
|
114
|
+
schema
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def annotation_schema(annotation)
|
|
118
|
+
schema = (TYPES[annotation.type] || { "type" => "string" }).dup
|
|
119
|
+
description = annotation.description.to_s.strip
|
|
120
|
+
description = "#{description}\n\nRendered to HTML in the response." if annotation.format == :markdown && !description.empty?
|
|
121
|
+
schema["description"] = description unless description.empty?
|
|
122
|
+
if annotation.enum && annotation.list?
|
|
123
|
+
# a list is one string of comma separated values, each of them one of the enum
|
|
124
|
+
schema["description"] = "#{description}\n\nComma separated list of: #{annotation.enum.join(", ")}.".strip
|
|
125
|
+
schema["x-items-enum"] = annotation.enum
|
|
126
|
+
elsif annotation.enum
|
|
127
|
+
schema["enum"] = annotation.enum
|
|
128
|
+
end
|
|
129
|
+
schema["default"] = annotation.default unless annotation.default.nil?
|
|
130
|
+
schema["deprecated"] = true if annotation.deprecated
|
|
131
|
+
schema
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# `link/*` -> a regular expression for the keys it matches
|
|
135
|
+
def pattern_of(key) = "^#{Regexp.escape(key).gsub('\*', ".+")}$"
|
|
136
|
+
|
|
137
|
+
# What the file says: spec.<verb>.<key>: [names]
|
|
138
|
+
def spec_schema(klass)
|
|
139
|
+
properties = {}
|
|
140
|
+
klass.declared_relations.each do |verb, key, target|
|
|
141
|
+
verb_schema = (properties[verb.to_s] ||= { "type" => "object", "properties" => {} })
|
|
142
|
+
verb_schema["properties"][key.to_s] = NAMES.merge("description" => "Names of #{target} resources")
|
|
143
|
+
end
|
|
144
|
+
{ "type" => "object", "additionalProperties" => true, "properties" => properties,
|
|
145
|
+
"description" => "The relations as written in the resource: verb, then the kind's collection, then the names" }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The outgoing relations that exist: verb, then the kind of the target, then the names
|
|
149
|
+
def relations_schema(klass)
|
|
150
|
+
properties = {}
|
|
151
|
+
klass.relations.each do |verb, _key, target|
|
|
152
|
+
verb_schema = (properties[verb.to_s] ||= { "type" => "object" })
|
|
153
|
+
if Archsight::Resources::DERIVED_VERBS.include?(verb)
|
|
154
|
+
verb_schema["additionalProperties"] = NAMES # targets of any kind, grouped by the kind they are
|
|
155
|
+
else
|
|
156
|
+
(verb_schema["properties"] ||= {})[target.to_s] = NAMES
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
{ "type" => "object", "additionalProperties" => false, "properties" => properties,
|
|
160
|
+
"description" => "Outgoing relations that have targets, grouped by verb and the kind of the target" }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def references_schema
|
|
164
|
+
{ "type" => "object", "additionalProperties" => { "type" => "object", "additionalProperties" => NAMES },
|
|
165
|
+
"description" => "Incoming relations grouped by the kind of the source and the verb" }
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
data/lib/archsight/page_tree.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "kramdown-parser-gfm"
|
|
3
|
+
require_relative "markdown"
|
|
5
4
|
|
|
6
5
|
module Archsight
|
|
7
6
|
# PageTree builds the sidebar tree, breadcrumbs and tables of contents for Page resources
|
|
@@ -63,7 +62,7 @@ module Archsight
|
|
|
63
62
|
|
|
64
63
|
# Flat table of contents [{level:, id:, text:}] of a markdown body
|
|
65
64
|
def self.toc(markdown)
|
|
66
|
-
doc =
|
|
65
|
+
doc = Archsight::Markdown.document(markdown)
|
|
67
66
|
entries = [] #: Array[Hash[String, untyped]]
|
|
68
67
|
collect_toc(doc.to_toc, entries)
|
|
69
68
|
entries
|
|
@@ -25,6 +25,17 @@ class Archsight::Resources::ApplicationInterface < Archsight::Resources::Base
|
|
|
25
25
|
- gRPC services
|
|
26
26
|
- Message queues (producer/consumer interfaces)
|
|
27
27
|
- WebSocket endpoints
|
|
28
|
+
|
|
29
|
+
## Reachability
|
|
30
|
+
|
|
31
|
+
An interface is `servedBy` the `TechnologyInterface` that carries it (a protocol, an authentication method) and
|
|
32
|
+
by the `TechnologyCommunicationNetwork`s on which it can be reached, just as a `TechnologyNode` attaches to the
|
|
33
|
+
networks it is on. Together with `api/authenticationMethod` this decides the exposure of the interface: an
|
|
34
|
+
interface without authentication on an internal network is a different risk than on a network that
|
|
35
|
+
internet-facing components attach to. The network page lists the interfaces as incoming "served by".
|
|
36
|
+
|
|
37
|
+
Write the networks only when they are known. An interface without a network makes no claim about where it can
|
|
38
|
+
be reached.
|
|
28
39
|
MD
|
|
29
40
|
|
|
30
41
|
icon "usb"
|
|
@@ -47,6 +58,7 @@ class Archsight::Resources::ApplicationInterface < Archsight::Resources::Base
|
|
|
47
58
|
enum: ["none", "hard coded", "pbac", "abac", "rbac"]
|
|
48
59
|
|
|
49
60
|
relation :servedBy, :technologyComponents, :TechnologyInterface
|
|
61
|
+
relation :servedBy, :technologyCommunicationNetworks, :TechnologyCommunicationNetwork
|
|
50
62
|
relation :realizes, :motivationConstraints, :MotivationConstraint
|
|
51
63
|
relation :serves, :dataObjects, :DataObject
|
|
52
64
|
end
|
|
@@ -50,6 +50,8 @@ class Archsight::Resources::TechnologyCommunicationNetwork < Archsight::Resource
|
|
|
50
50
|
- An overlay that software builds is `realizedBy` that `ApplicationComponent` or `ApplicationService` (an SDN agent,
|
|
51
51
|
a network operator, a load-balancer controller), also when the software runs on the nodes the network serves
|
|
52
52
|
- A `TechnologyNode` is `servedBy` the networks it is attached to; the network page lists them
|
|
53
|
+
- An `ApplicationInterface` is `servedBy` the networks on which it can be reached (only when that is known); the
|
|
54
|
+
network page lists the interfaces, which shows what a network exposes
|
|
53
55
|
- A network is `realizedBy` the `TechnologyDevice`s that build it (its switches and routers) and `connects` the
|
|
54
56
|
devices attached to it (servers, firewalls, appliances). Both are written on the network, so a switch that is
|
|
55
57
|
also attached to the network it builds closes no cycle
|
data/lib/archsight/version.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
|
712
|
-
the resources it comes from (`by
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
|
@@ -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:
|
|
@@ -7,6 +7,7 @@ require_relative "page_helpers"
|
|
|
7
7
|
require_relative "requirements_helpers"
|
|
8
8
|
require_relative "analysis_helpers"
|
|
9
9
|
require_relative "../../assets"
|
|
10
|
+
require_relative "../../openapi"
|
|
10
11
|
|
|
11
12
|
module Archsight; end
|
|
12
13
|
module Archsight::Web; end
|
|
@@ -220,8 +221,7 @@ module Archsight::Web::API::Routes
|
|
|
220
221
|
# GET /api/v1/openapi.yaml - OpenAPI specification
|
|
221
222
|
get "/api/v1/openapi.yaml" do
|
|
222
223
|
content_type "text/yaml"
|
|
223
|
-
|
|
224
|
-
File.read(spec_path)
|
|
224
|
+
Archsight::OpenAPI.document.to_yaml
|
|
225
225
|
end
|
|
226
226
|
|
|
227
227
|
# Convenience aliases with .json suffix
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "sinatra/base"
|
|
4
|
-
|
|
4
|
+
require_relative "../markdown"
|
|
5
5
|
require "erb"
|
|
6
6
|
require "fast_mcp"
|
|
7
7
|
|
|
@@ -158,7 +158,7 @@ class Archsight::Web::Application < Sinatra::Base
|
|
|
158
158
|
# @param base [String, nil] directory of the file the markdown comes from, relative to the resources
|
|
159
159
|
# directory ("" for the root): its relative images become assets (see Archsight::Assets)
|
|
160
160
|
def markdown(data, git_url: nil, base: nil, page: nil)
|
|
161
|
-
html =
|
|
161
|
+
html = Archsight::Markdown.document(data).to_html
|
|
162
162
|
html = Archsight::Helpers::AssetImages.rewrite(html, base_dir: base, resources_dir: Archsight.resources_dir) if base
|
|
163
163
|
# ```asd blocks become placeholders until the text passes below are done (see DiagramBlocks)
|
|
164
164
|
html, diagrams = Archsight::Helpers::DiagramBlocks.extract(html, resolver: Archsight::Helpers::ResourceResolver.new(db))
|
|
@@ -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
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>API - Archsight</title>
|
|
7
|
+
<link rel="icon" href="/favicon.ico">
|
|
8
|
+
<style>
|
|
9
|
+
/* ReDoc brings its own styles. This page is not part of the single page app on purpose: none of the app's CSS
|
|
10
|
+
(Pico styles every <code>, headings, tables) reaches it. */
|
|
11
|
+
html, body { margin: 0; padding: 0; background: #fff; }
|
|
12
|
+
#back {
|
|
13
|
+
position: fixed; top: 12px; right: 12px; z-index: 100; padding: 6px 14px;
|
|
14
|
+
background: #fff; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
|
15
|
+
font: 13px/1.4 system-ui, sans-serif; color: #0172ad; text-decoration: none;
|
|
16
|
+
}
|
|
17
|
+
#error { margin: 24px; font: 14px/1.4 system-ui, sans-serif; color: #c62828; }
|
|
18
|
+
</style>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<a id="back" href="/">← Back</a>
|
|
22
|
+
<p id="error" role="alert" hidden></p>
|
|
23
|
+
<div id="redoc"></div>
|
|
24
|
+
<script src="/vendor/redoc/redoc.standalone.js"></script>
|
|
25
|
+
<script src="/vendor/redoc/api.js"></script>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// The API reference (/docs/api): ReDoc, vendored next to this file, renders the OpenAPI document of this server.
|
|
2
|
+
(function () {
|
|
3
|
+
var back = document.getElementById('back')
|
|
4
|
+
// back to where the reader came from, the start page when this page was opened directly
|
|
5
|
+
back.addEventListener('click', function (event) {
|
|
6
|
+
if (document.referrer.indexOf(location.origin) === 0 && history.length > 1) {
|
|
7
|
+
event.preventDefault()
|
|
8
|
+
history.back()
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
if (!window.Redoc) {
|
|
13
|
+
var error = document.getElementById('error')
|
|
14
|
+
error.textContent = 'Failed to load ReDoc'
|
|
15
|
+
error.hidden = false
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
window.Redoc.init('/api/v1/openapi.yaml', {}, document.getElementById('redoc'))
|
|
19
|
+
})()
|