stoplight 6.0.0 → 6.0.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/LICENSE.md +1 -0
- data/lib/stoplight/admin/assets/panel.css +107 -0
- data/lib/stoplight/admin/helpers.rb +2 -2
- data/lib/stoplight/admin/panel/actions/light_list.rb +41 -0
- data/lib/stoplight/admin/panel/dependencies.rb +28 -0
- data/lib/stoplight/admin/panel/helpers.rb +59 -0
- data/lib/stoplight/admin/panel/light_summary.rb +13 -0
- data/lib/stoplight/admin/panel/views/_empty.erb +20 -0
- data/lib/stoplight/admin/panel/views/_light_detail.erb +24 -0
- data/lib/stoplight/admin/panel/views/layout.erb +29 -0
- data/lib/stoplight/admin/panel/views/lights.erb +36 -0
- data/lib/stoplight/admin/panel/views/states/_green.erb +10 -0
- data/lib/stoplight/admin/panel/views/states/_locked_green.erb +10 -0
- data/lib/stoplight/admin/panel/views/states/_locked_red.erb +0 -0
- data/lib/stoplight/admin/panel/views/states/_red.erb +0 -0
- data/lib/stoplight/admin/panel/views/states/_yellow.erb +0 -0
- data/lib/stoplight/admin/panel.rb +54 -0
- data/lib/stoplight/admin/views/layout.erb +1 -1
- data/lib/stoplight/admin.rb +1 -1
- data/lib/stoplight/infrastructure/redis/storage/scripting.rb +2 -1
- data/lib/stoplight/version.rb +1 -1
- data/sig/stoplight/admin/panel/actions/light_list.rbs +24 -0
- data/sig/stoplight/admin/panel/dependencies.rbs +14 -0
- data/sig/stoplight/admin/panel/helpers.rbs +20 -0
- data/sig/stoplight/admin/panel/light_summary.rbs +28 -0
- data/sig/stoplight/admin/panel.rbs +7 -0
- data/sig/stoplight/admin.rbs +2 -0
- metadata +21 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c2fb11cc6efb02fa6c4dd2a6f5c83adcd5f98ee33fd2adb7ba5c8b960ec4655
|
|
4
|
+
data.tar.gz: 3a5ef6988bd7bcb8e98aa4a7bd2d9396a9bac114c0c9de9ad7e2f94ba37caca5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7bb11404866fc8c14379e3aa94da53aff011e66b86cf2dd582b451b72a61b41f271a375523a585c21d5f54750f37c5cc38ac72fe11a889690eb99b2a46c82a5
|
|
7
|
+
data.tar.gz: 5c2691111ce274ee61de5632200c77482bc885e0fc1f6d01a90bc329ad25cae57dda2fcf4c4264469c736d9d19e86c2a0b8dd3200efa3a755e0e5c3961f14893
|
data/LICENSE.md
CHANGED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
.panel-page {
|
|
2
|
+
height: 100vh;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.panel-empty {
|
|
8
|
+
padding: 6rem 2rem;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.panel-main {
|
|
12
|
+
flex: 1;
|
|
13
|
+
min-height: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.light-card {
|
|
17
|
+
grid-template-columns: 300px 1fr;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.light-pane {
|
|
21
|
+
min-height: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.light-detail-pane {
|
|
25
|
+
padding: 26px 28px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.light-detail-grid {
|
|
29
|
+
grid-template-columns: 40px 1fr;
|
|
30
|
+
column-gap: 12px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.light-detail-avatar {
|
|
34
|
+
grid-row: span 2;
|
|
35
|
+
align-self: center;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.light-detail-name {
|
|
39
|
+
grid-column: 2;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.light-detail-status {
|
|
43
|
+
grid-column: 2;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.light-detail-card {
|
|
47
|
+
grid-column: 1 / -1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.light-frame {
|
|
51
|
+
display: block;
|
|
52
|
+
height: 100%;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.light-row {
|
|
56
|
+
padding: 9px 14px;
|
|
57
|
+
min-height: 52px;
|
|
58
|
+
border-left: 3px solid transparent;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.light-row:hover {
|
|
62
|
+
background-color: var(--color-gray-50);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.light-row[aria-current="page"] {
|
|
66
|
+
background-color: #fff;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.light-row[aria-current="page"] .light-row-name {
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
li[data-color="green"] .light-row[aria-current="page"] {
|
|
74
|
+
border-left-color: var(--color-green-600);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
li[data-color="yellow"] .light-row[aria-current="page"] {
|
|
78
|
+
border-left-color: var(--color-yellow-600);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
li[data-color="red"] .light-row[aria-current="page"] {
|
|
82
|
+
border-left-color: var(--color-red-600);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.light-detail-message {
|
|
86
|
+
font-family: ui-monospace, Menlo, monospace;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.text-yellow-600 {
|
|
90
|
+
color: var(--color-yellow-600);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.text-yellow-900 {
|
|
94
|
+
color: var(--color-yellow-900);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.border-green-100 {
|
|
98
|
+
border-color: var(--color-green-100);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.w-2\.5 {
|
|
102
|
+
width: calc(var(--spacing) * 2.5);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.h-2\.5 {
|
|
106
|
+
height: calc(var(--spacing) * 2.5);
|
|
107
|
+
}
|
|
@@ -18,11 +18,11 @@ module Stoplight
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def system_url(system_id, path)
|
|
21
|
-
url("/systems/#{system_id}#{path}")
|
|
21
|
+
url("/systems/#{system_id}#{path}", false)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def asset_path(name)
|
|
25
|
-
url("/#{name}?v=#{ASSET_DIGESTS.fetch(name)}")
|
|
25
|
+
url("/#{name}?v=#{ASSET_DIGESTS.fetch(name)}", false)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
# A read-only control keeps its place but loses its href, so there is nothing to follow
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
class Admin
|
|
5
|
+
class Panel
|
|
6
|
+
module Actions
|
|
7
|
+
class LightList
|
|
8
|
+
COLORS = [
|
|
9
|
+
Stoplight::Color::GREEN,
|
|
10
|
+
Stoplight::Color::YELLOW,
|
|
11
|
+
Stoplight::Color::RED
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(config_registry:, storage:)
|
|
15
|
+
@config_registry = config_registry
|
|
16
|
+
@storage = storage
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call
|
|
20
|
+
@config_registry.all.map { |config| build_light(config) }.sort_by { |light| sort_key(light) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private def build_light(config)
|
|
24
|
+
state_snapshot = @storage.state_snapshot(config)
|
|
25
|
+
|
|
26
|
+
LightSummary.new(
|
|
27
|
+
id: config.id,
|
|
28
|
+
name: config.name,
|
|
29
|
+
color: state_snapshot.color,
|
|
30
|
+
lock_state: state_snapshot.locked_state
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private def sort_key(light)
|
|
35
|
+
[-COLORS.index(light.color).to_i, light.name]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
class Admin
|
|
5
|
+
class Panel
|
|
6
|
+
class Dependencies
|
|
7
|
+
def initialize(system:)
|
|
8
|
+
@system = system
|
|
9
|
+
@storage = @system.__stoplight__storage
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def config_registry
|
|
13
|
+
ConfigRegistry.new(
|
|
14
|
+
registry: @system.__stoplight__registry,
|
|
15
|
+
system_config: @system.config
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def light_list_action
|
|
20
|
+
Actions::LightList.new(
|
|
21
|
+
config_registry: config_registry,
|
|
22
|
+
storage: @storage
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
class Admin
|
|
5
|
+
class Panel
|
|
6
|
+
module Helpers
|
|
7
|
+
STATE_TEMPLATES = {
|
|
8
|
+
[Stoplight::Color::GREEN, Stoplight::State::UNLOCKED] => :"states/_green",
|
|
9
|
+
[Stoplight::Color::GREEN, Stoplight::State::LOCKED_GREEN] => :"states/_locked_green",
|
|
10
|
+
[Stoplight::Color::YELLOW, Stoplight::State::UNLOCKED] => :"states/_yellow",
|
|
11
|
+
[Stoplight::Color::RED, Stoplight::State::UNLOCKED] => :"states/_red",
|
|
12
|
+
[Stoplight::Color::RED, Stoplight::State::LOCKED_RED] => :"states/_locked_red"
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
def dependencies
|
|
16
|
+
Dependencies.new(system: current_system)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def selected_light(lights)
|
|
20
|
+
lights.first
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def find_light(lights, light_id)
|
|
24
|
+
lights.find(-> { halt 404 }) { |light| light.id == light_id }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def state_template_for(light)
|
|
28
|
+
STATE_TEMPLATES.fetch([light.color, light.lock_state])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def system_url(system_id, path)
|
|
32
|
+
url("/systems/#{system_id}#{path}", false)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def asset_path(name)
|
|
36
|
+
url("/#{name}?v=#{ASSET_DIGESTS.fetch(name)}", false)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def find_system(system_id)
|
|
40
|
+
settings.systems.find(-> { halt 404 }) do |system|
|
|
41
|
+
system.config.id == system_id
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def current_system_id
|
|
46
|
+
T.must(params[:system_id])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def current_system
|
|
50
|
+
find_system(current_system_id)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def turbo_frame_request?
|
|
54
|
+
!!request.env["HTTP_TURBO_FRAME"]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<section data-role="empty-state" class="panel-empty flex flex-col items-center justify-center text-center">
|
|
2
|
+
<div class="flex items-center justify-center w-16 h-16 mb-6 rounded-full bg-blue-50">
|
|
3
|
+
<svg class="w-8 h-8 text-blue-600" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4
|
+
<path d="m19 5 3-3"/><path d="m2 22 3-3"/><path d="M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z"/><path d="M7.5 13.5 10 11"/><path d="M10.5 16.5 13 14"/><path d="m12 6 6 6 2.3-2.3a2.4 2.4 0 0 0 0-3.4l-2.6-2.6a2.4 2.4 0 0 0-3.4 0Z"/>
|
|
5
|
+
</svg>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<h2 class="text-4xl leading-none font-semibold tracking-tight">No lights found</h2>
|
|
9
|
+
|
|
10
|
+
<p class="max-w-2xl mt-5 text-lg text-gray-500">
|
|
11
|
+
Ensure lights are registered and this Admin instance uses the same data store as your application.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<a href="<%= system_url(current_system_id, "/lights") %>" class="inline-flex items-center gap-2 mt-7 px-5 py-3 font-medium text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:ring-blue-300">
|
|
15
|
+
<svg class="w-3.5 h-3.5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
16
|
+
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/>
|
|
17
|
+
</svg>
|
|
18
|
+
Refresh Lights
|
|
19
|
+
</a>
|
|
20
|
+
</section>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<div data-role="light-detail" data-light="<%= CGI.escapeHTML(light.name) %>">
|
|
2
|
+
<div class="light-detail-grid grid">
|
|
3
|
+
<div class="light-detail-avatar relative w-10 h-10">
|
|
4
|
+
<div class="inline-flex items-center justify-center w-10 h-10 rounded-full text-sm font-medium bg-<%= light.color %>-100 text-<%= light.color %>-600">
|
|
5
|
+
<%= CGI.escapeHTML(light.name[0].to_s.upcase) %>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="absolute bottom-0 right-0 inline-flex items-center justify-center w-4 h-4 bg-white border-2 border-white rounded-full">
|
|
8
|
+
<% if light.locked? %>
|
|
9
|
+
<svg aria-hidden="true" class="w-3 h-3 text-<%= light.color %>-900" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
10
|
+
<circle cx="12" cy="16" r="1"/><rect x="3" y="10" width="18" height="12" rx="2"/><path d="M7 10V7a5 5 0 0 1 10 0v3"/>
|
|
11
|
+
</svg>
|
|
12
|
+
<% else %>
|
|
13
|
+
<svg aria-hidden="true" class="w-3 h-3 text-<%= light.color %>-900" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
14
|
+
<circle cx="12" cy="16" r="1"/><rect x="3" y="10" width="18" height="12" rx="2"/><path d="M7 10V7a5 5 0 0 1 9.33-2.5"/>
|
|
15
|
+
</svg>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<h2 class="light-detail-name min-w-0 truncate text-xl font-semibold"><%= CGI.escapeHTML(light.name) %></h2>
|
|
21
|
+
|
|
22
|
+
<%= erb state_template_for(light), locals: {light: light} %>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
|
|
7
|
+
<title>Stoplight Admin</title>
|
|
8
|
+
|
|
9
|
+
<link rel="icon" href="<%= asset_path("favicon.ico") %>" sizes="32x32">
|
|
10
|
+
<link rel="icon" href="<%= asset_path("icon.svg") %>" type="image/svg+xml">
|
|
11
|
+
<link rel="apple-touch-icon" href="<%= asset_path("apple-touch-icon.png") %>">
|
|
12
|
+
|
|
13
|
+
<script nonce="<%= settings.nonce(request) %>" type="module" src="<%= asset_path("turbo.es2017-esm.min.js") %>"></script>
|
|
14
|
+
<link rel="stylesheet" href="<%= asset_path("flowbite.min.css") %>">
|
|
15
|
+
<link rel="stylesheet" href="<%= asset_path("panel.css") %>">
|
|
16
|
+
</head>
|
|
17
|
+
<body class="panel-page bg-gray-50 text-gray-900 antialiased">
|
|
18
|
+
<header class="flex flex-wrap items-center justify-between p-4 bg-white border-b border-gray-200">
|
|
19
|
+
<a href="<%= url("/", false) %>" class="flex items-center gap-3">
|
|
20
|
+
<span class="text-2xl" aria-hidden="true">🚦</span>
|
|
21
|
+
<span class="text-2xl font-semibold whitespace-nowrap">Stoplight Admin</span>
|
|
22
|
+
</a>
|
|
23
|
+
</header>
|
|
24
|
+
|
|
25
|
+
<main class="panel-main">
|
|
26
|
+
<%= yield %>
|
|
27
|
+
</main>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<% if lights.empty? %>
|
|
2
|
+
<%= erb :_empty %>
|
|
3
|
+
<% else %>
|
|
4
|
+
<turbo-frame id="light-detail" class="light-frame">
|
|
5
|
+
<div class="light-card grid bg-white border border-gray-200 h-full">
|
|
6
|
+
<div class="light-pane bg-gray-50 border-r border-gray-200 overflow-y-auto">
|
|
7
|
+
<nav aria-label="Lights">
|
|
8
|
+
<ul data-role="light-list">
|
|
9
|
+
<% lights.each do |light| %>
|
|
10
|
+
<li data-role="light-row" data-light="<%= CGI.escapeHTML(light.name) %>" data-color="<%= light.color %>" data-locked="<%= light.locked? %>">
|
|
11
|
+
<a
|
|
12
|
+
href="<%= system_url(current_system_id, "/lights/#{CGI.escapeHTML(light.id)}") %>"
|
|
13
|
+
data-turbo-action="advance"
|
|
14
|
+
<% if light.id == selected_light.id %>aria-current="page"<% end %>
|
|
15
|
+
class="light-row flex items-center gap-2 text-sm border-b border-gray-100"
|
|
16
|
+
>
|
|
17
|
+
<span role="img" aria-label="<%= light.color %>" class="w-2 h-2 rounded-full shrink-0 bg-<%= light.color %>-600"></span>
|
|
18
|
+
<span class="light-row-name flex-1 min-w-0 truncate"><%= CGI.escapeHTML(light.name) %></span>
|
|
19
|
+
<% if light.locked? %>
|
|
20
|
+
<svg data-role="lock-icon" role="img" aria-label="Locked" class="w-3 h-3 shrink-0 text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
21
|
+
<circle cx="12" cy="16" r="1"/><rect x="3" y="10" width="18" height="12" rx="2"/><path d="M7 10V7a5 5 0 0 1 10 0v3"/>
|
|
22
|
+
</svg>
|
|
23
|
+
<% end %>
|
|
24
|
+
</a>
|
|
25
|
+
</li>
|
|
26
|
+
<% end %>
|
|
27
|
+
</ul>
|
|
28
|
+
</nav>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="light-pane light-detail-pane overflow-y-auto">
|
|
32
|
+
<%= erb :_light_detail, locals: {light: selected_light} %>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</turbo-frame>
|
|
36
|
+
<% end %>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<span class="light-detail-status flex items-center gap-2 mt-1 text-sm font-medium text-gray-500">
|
|
2
|
+
<span class="w-2.5 h-2.5 rounded-full bg-green-600"></span>
|
|
3
|
+
Green
|
|
4
|
+
</span>
|
|
5
|
+
|
|
6
|
+
<div class="light-detail-card mt-5 p-4 rounded-lg bg-green-50">
|
|
7
|
+
<p data-role="title" class="text-sm font-semibold text-green-800">Healthy</p>
|
|
8
|
+
<p data-role="comment" class="mt-1 text-xs text-green-800">Operating normally</p>
|
|
9
|
+
<p data-role="message" class="light-detail-message mt-3 pt-3 border-t border-green-100 text-sm text-green-900">No recent errors</p>
|
|
10
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<span class="light-detail-status flex items-center gap-2 mt-1 text-sm font-medium text-gray-500">
|
|
2
|
+
<span class="w-2.5 h-2.5 rounded-full bg-green-600"></span>
|
|
3
|
+
Green (Locked)
|
|
4
|
+
</span>
|
|
5
|
+
|
|
6
|
+
<div class="light-detail-card mt-5 p-4 rounded-lg bg-green-50">
|
|
7
|
+
<p data-role="title" class="text-sm font-semibold text-green-800">Forced Healthy</p>
|
|
8
|
+
<p data-role="comment" class="mt-1 text-xs text-green-800">Override active - all requests processed</p>
|
|
9
|
+
<p data-role="message" class="light-detail-message mt-3 pt-3 border-t border-green-100 text-sm text-green-900">Light manually locked to green</p>
|
|
10
|
+
</div>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
class Admin
|
|
5
|
+
# A Sinatra dashboard listing every light of a configured system and its state.
|
|
6
|
+
#
|
|
7
|
+
# It reads its systems and its +read_only+ setting from +Stoplight::Admin+, so a host
|
|
8
|
+
# configures them there and mounts either app.
|
|
9
|
+
#
|
|
10
|
+
# @example Mounting in Rails
|
|
11
|
+
# Payments = Stoplight.register_system("Payments", data_store:)
|
|
12
|
+
#
|
|
13
|
+
# Stoplight::Admin.configure do |config|
|
|
14
|
+
# config.add_system Payments
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# mount Stoplight::Admin::Panel => "/stoplights"
|
|
18
|
+
#
|
|
19
|
+
class Panel < Sinatra::Base
|
|
20
|
+
helpers Helpers
|
|
21
|
+
|
|
22
|
+
set :systems, proc { Admin.settings.systems }
|
|
23
|
+
set :read_only, proc { Admin.settings.read_only? }
|
|
24
|
+
set :views, File.join(T.must(__dir__), "panel", "views")
|
|
25
|
+
set :nonce, proc { |request| }
|
|
26
|
+
set :public_folder, ASSETS_PATH
|
|
27
|
+
set :static_cache_control, [:public, max_age: ONE_YEAR_IN_SECONDS, immutable: true]
|
|
28
|
+
|
|
29
|
+
before do
|
|
30
|
+
if settings.read_only? && !request.get? && !request.head?
|
|
31
|
+
halt 403, "Stoplight Admin is running in read-only mode."
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
get "/" do
|
|
36
|
+
system = settings.systems.first
|
|
37
|
+
|
|
38
|
+
redirect system_url(system.config.id, "/lights")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
get "/systems/:system_id/lights" do
|
|
42
|
+
lights = dependencies.light_list_action.call
|
|
43
|
+
|
|
44
|
+
erb :lights, layout: !turbo_frame_request?, locals: {lights: lights, selected_light: selected_light(lights)}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
get "/systems/:system_id/lights/:light_id" do
|
|
48
|
+
lights = dependencies.light_list_action.call
|
|
49
|
+
|
|
50
|
+
erb :lights, layout: !turbo_frame_request?, locals: {lights: lights, selected_light: find_light(lights, params[:light_id])}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<nav class="bg-white border-gray-200 dark:bg-gray-900">
|
|
25
25
|
<div class="flex flex-wrap items-center justify-between p-4">
|
|
26
26
|
<div class="flex items-center space-x-3 rtl:space-x-reverse">
|
|
27
|
-
<a href="<%= url('/') %>" class="flex items-center space-x-3 rtl:space-x-reverse">
|
|
27
|
+
<a href="<%= url('/', false) %>" class="flex items-center space-x-3 rtl:space-x-reverse">
|
|
28
28
|
🚦
|
|
29
29
|
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Stoplight Admin</span>
|
|
30
30
|
</a>
|
data/lib/stoplight/admin.rb
CHANGED
|
@@ -11,7 +11,7 @@ rescue LoadError
|
|
|
11
11
|
raise <<~WARN
|
|
12
12
|
"sinatra" and "sinatra-contrib" gems are unavailable and necessary for running Stoplight Admin panel
|
|
13
13
|
Please add them to your Gemfile and run `bundle install`:
|
|
14
|
-
gem "sinatra",
|
|
14
|
+
gem "sinatra", require: false
|
|
15
15
|
gem "sinatra-contrib", require: false
|
|
16
16
|
WARN
|
|
17
17
|
end
|
|
@@ -19,7 +19,8 @@ module Stoplight
|
|
|
19
19
|
@scripts_path = scripts_path
|
|
20
20
|
@redis = redis
|
|
21
21
|
@shas = Hash.new do |hash, script_name|
|
|
22
|
-
|
|
22
|
+
name = script_name #: String
|
|
23
|
+
hash[script_name] = Digest::SHA1.hexdigest(resolve_source(name))
|
|
23
24
|
end
|
|
24
25
|
@invoked = Set.new
|
|
25
26
|
end
|
data/lib/stoplight/version.rb
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Stoplight
|
|
2
|
+
class Admin
|
|
3
|
+
class Panel
|
|
4
|
+
module Actions
|
|
5
|
+
class LightList
|
|
6
|
+
COLORS: Array[Domain::color]
|
|
7
|
+
|
|
8
|
+
@config_registry: ConfigRegistry
|
|
9
|
+
@storage: Wiring::System::Storage
|
|
10
|
+
|
|
11
|
+
def initialize: (
|
|
12
|
+
config_registry: ConfigRegistry,
|
|
13
|
+
storage: Wiring::System::Storage,
|
|
14
|
+
) -> void
|
|
15
|
+
|
|
16
|
+
def call: -> Array[LightSummary]
|
|
17
|
+
|
|
18
|
+
private def build_light: (Domain::Config) -> LightSummary
|
|
19
|
+
private def sort_key: (LightSummary) -> Array[Comparable]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Stoplight
|
|
2
|
+
class Admin
|
|
3
|
+
class Panel
|
|
4
|
+
class Dependencies
|
|
5
|
+
@system: Wiring::System
|
|
6
|
+
@storage: Wiring::System::Storage
|
|
7
|
+
|
|
8
|
+
def initialize: (system: Wiring::System) -> void
|
|
9
|
+
def config_registry: -> ConfigRegistry
|
|
10
|
+
def light_list_action: -> Actions::LightList
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Stoplight
|
|
2
|
+
class Admin
|
|
3
|
+
class Panel
|
|
4
|
+
module Helpers : Sinatra::Base
|
|
5
|
+
STATE_TEMPLATES: Hash[Array[Domain::color | Domain::state], Symbol]
|
|
6
|
+
|
|
7
|
+
def dependencies: -> Dependencies
|
|
8
|
+
def selected_light: (Array[LightSummary]) -> LightSummary?
|
|
9
|
+
def find_light: (Array[LightSummary], String) -> LightSummary
|
|
10
|
+
def state_template_for: (LightSummary) -> Symbol
|
|
11
|
+
def system_url: (String system_id, String path) -> String
|
|
12
|
+
def asset_path: (String) -> String
|
|
13
|
+
def find_system: (String) -> Wiring::System
|
|
14
|
+
def current_system_id: -> String
|
|
15
|
+
def current_system: -> Wiring::System
|
|
16
|
+
def turbo_frame_request?: -> bool
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Stoplight
|
|
2
|
+
class Admin
|
|
3
|
+
class Panel
|
|
4
|
+
class LightSummary < Data
|
|
5
|
+
attr_reader id: String
|
|
6
|
+
attr_reader name: String
|
|
7
|
+
attr_reader color: Domain::color
|
|
8
|
+
attr_reader lock_state: Domain::state
|
|
9
|
+
|
|
10
|
+
def self.new: (
|
|
11
|
+
id: String,
|
|
12
|
+
name: String,
|
|
13
|
+
color: Domain::color,
|
|
14
|
+
lock_state: Domain::state,
|
|
15
|
+
) -> instance
|
|
16
|
+
|
|
17
|
+
def initialize: (
|
|
18
|
+
id: String,
|
|
19
|
+
name: String,
|
|
20
|
+
color: Domain::color,
|
|
21
|
+
lock_state: Domain::state,
|
|
22
|
+
) -> void
|
|
23
|
+
|
|
24
|
+
def locked?: -> bool
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/sig/stoplight/admin.rbs
CHANGED
|
@@ -7,6 +7,8 @@ module Stoplight
|
|
|
7
7
|
include Helpers
|
|
8
8
|
|
|
9
9
|
self.@systems: Array[Wiring::System]
|
|
10
|
+
def self.systems: -> Array[Wiring::System]
|
|
11
|
+
def self.read_only?: -> bool
|
|
10
12
|
def self.add_system: (Wiring::System) -> void
|
|
11
13
|
def self.__stoplight__reset_systems!: -> void
|
|
12
14
|
def self.validate_persistent_system!: (Wiring::System) -> Wiring::System
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stoplight
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 6.0.
|
|
4
|
+
version: 6.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tëma Bolshakov
|
|
@@ -66,12 +66,27 @@ files:
|
|
|
66
66
|
- lib/stoplight/admin/assets/flowbite.min.css
|
|
67
67
|
- lib/stoplight/admin/assets/flowbite.min.js
|
|
68
68
|
- lib/stoplight/admin/assets/icon.svg
|
|
69
|
+
- lib/stoplight/admin/assets/panel.css
|
|
69
70
|
- lib/stoplight/admin/assets/turbo.es2017-esm.min.js
|
|
70
71
|
- lib/stoplight/admin/config_registry.rb
|
|
71
72
|
- lib/stoplight/admin/dependencies.rb
|
|
72
73
|
- lib/stoplight/admin/helpers.rb
|
|
73
74
|
- lib/stoplight/admin/light_view.rb
|
|
74
75
|
- lib/stoplight/admin/lights_stats.rb
|
|
76
|
+
- lib/stoplight/admin/panel.rb
|
|
77
|
+
- lib/stoplight/admin/panel/actions/light_list.rb
|
|
78
|
+
- lib/stoplight/admin/panel/dependencies.rb
|
|
79
|
+
- lib/stoplight/admin/panel/helpers.rb
|
|
80
|
+
- lib/stoplight/admin/panel/light_summary.rb
|
|
81
|
+
- lib/stoplight/admin/panel/views/_empty.erb
|
|
82
|
+
- lib/stoplight/admin/panel/views/_light_detail.erb
|
|
83
|
+
- lib/stoplight/admin/panel/views/layout.erb
|
|
84
|
+
- lib/stoplight/admin/panel/views/lights.erb
|
|
85
|
+
- lib/stoplight/admin/panel/views/states/_green.erb
|
|
86
|
+
- lib/stoplight/admin/panel/views/states/_locked_green.erb
|
|
87
|
+
- lib/stoplight/admin/panel/views/states/_locked_red.erb
|
|
88
|
+
- lib/stoplight/admin/panel/views/states/_red.erb
|
|
89
|
+
- lib/stoplight/admin/panel/views/states/_yellow.erb
|
|
75
90
|
- lib/stoplight/admin/views/_card.erb
|
|
76
91
|
- lib/stoplight/admin/views/index.erb
|
|
77
92
|
- lib/stoplight/admin/views/layout.erb
|
|
@@ -201,6 +216,11 @@ files:
|
|
|
201
216
|
- sig/stoplight/admin/helpers.rbs
|
|
202
217
|
- sig/stoplight/admin/light_view.rbs
|
|
203
218
|
- sig/stoplight/admin/lights_stats.rbs
|
|
219
|
+
- sig/stoplight/admin/panel.rbs
|
|
220
|
+
- sig/stoplight/admin/panel/actions/light_list.rbs
|
|
221
|
+
- sig/stoplight/admin/panel/dependencies.rbs
|
|
222
|
+
- sig/stoplight/admin/panel/helpers.rbs
|
|
223
|
+
- sig/stoplight/admin/panel/light_summary.rbs
|
|
204
224
|
- sig/stoplight/color.rbs
|
|
205
225
|
- sig/stoplight/common/deprecations.rbs
|
|
206
226
|
- sig/stoplight/data_store/base.rbs
|