mission_control-servers 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/mission_control_servers_application.css +1969 -1
- data/app/controllers/mission_control/servers/dashboards/request_charts_controller.rb +16 -0
- data/app/controllers/mission_control/servers/dashboards/request_totals_controller.rb +15 -0
- data/app/controllers/mission_control/servers/ingresses_controller.rb +6 -2
- data/app/controllers/mission_control/servers/service_settings_controller.rb +2 -1
- data/app/javascript/mission_control/servers/controllers/hostname_select_controller.js +19 -0
- data/app/javascript/mission_control/servers/controllers/status_chart_controller.js +97 -0
- data/app/models/mission_control/servers/project.rb +1 -0
- data/app/models/mission_control/servers/request.rb +29 -0
- data/app/models/mission_control/servers/service.rb +69 -65
- data/app/models/mission_control/servers/service_setting.rb +7 -0
- data/app/views/mission_control/servers/dashboards/request_charts/show.html.erb +15 -0
- data/app/views/mission_control/servers/dashboards/request_totals/show.html.erb +13 -0
- data/app/views/mission_control/servers/projects/_panels.html.erb +15 -3
- data/app/views/mission_control/servers/service_settings/edit.html.erb +33 -1
- data/config/routes.rb +2 -0
- data/db/migrate/20240213011036_create_mission_control_servers_requests.rb +15 -0
- data/db/migrate/20240214021836_add_hostname_to_service_settings.rb +5 -0
- data/db/migrate/20240214160849_add_missing_mission_control_server_indexes.rb +7 -0
- data/lib/mission_control/servers/engine.rb +9 -0
- data/lib/mission_control/servers/request_tally_middleware.rb +53 -0
- data/lib/mission_control/servers/routing_helpers.rb +2 -0
- data/lib/mission_control/servers/version.rb +1 -1
- data/lib/mission_control/servers.rb +1 -0
- data/lib/tasks/mission_control/servers_tasks.rake +1 -1
- metadata +18 -8
@@ -0,0 +1,16 @@
|
|
1
|
+
module MissionControl::Servers
|
2
|
+
class Dashboards::RequestChartsController < ApplicationController
|
3
|
+
def show
|
4
|
+
@project = MissionControl::Servers::Project.find_by(token: params[:project_id])
|
5
|
+
@hostname = params[:hostname]
|
6
|
+
recent_requests = @project.requests.recent_totals(@project, @hostname)
|
7
|
+
@summary_hash = {
|
8
|
+
sum_2xx: recent_requests.sum(:sum_2xx).to_i,
|
9
|
+
sum_3xx: recent_requests.sum(:sum_3xx).to_i,
|
10
|
+
sum_4xx: recent_requests.sum(:sum_4xx).to_i,
|
11
|
+
sum_5xx: recent_requests.sum(:sum_5xx).to_i,
|
12
|
+
unknown: recent_requests.sum(:unknown).to_i
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module MissionControl::Servers
|
2
|
+
class Dashboards::RequestTotalsController < ApplicationController
|
3
|
+
def show
|
4
|
+
@project = Project.find_by(token: params[:project_id])
|
5
|
+
@hostname = params[:hostname]
|
6
|
+
recent_requests = @project.requests.recent_totals(@project, @hostname)
|
7
|
+
@total_sum = recent_requests.sum(:sum_2xx).to_i +
|
8
|
+
recent_requests.sum(:sum_3xx).to_i +
|
9
|
+
recent_requests.sum(:sum_4xx).to_i +
|
10
|
+
recent_requests.sum(:sum_5xx).to_i +
|
11
|
+
recent_requests.sum(:unknown).to_i
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -5,8 +5,8 @@ module MissionControl::Servers
|
|
5
5
|
|
6
6
|
def create
|
7
7
|
ingress = @project.services.new(ingress_params)
|
8
|
-
|
9
|
-
if ingress.save
|
8
|
+
request = @project.requests.new(tally_params)
|
9
|
+
if ingress.save && request.save
|
10
10
|
head :ok
|
11
11
|
else
|
12
12
|
head :unprocessable_entity
|
@@ -22,5 +22,9 @@ module MissionControl::Servers
|
|
22
22
|
def ingress_params
|
23
23
|
params.require(:service).permit(:hostname, :cpu, :mem_used, :mem_free, :disk_free)
|
24
24
|
end
|
25
|
+
|
26
|
+
def tally_params
|
27
|
+
params.require(:tally).permit(:hostname, :'sum_2xx', :'sum_3xx', :'sum_4xx', :'sum_5xx', :unknown)
|
28
|
+
end
|
25
29
|
end
|
26
30
|
end
|
@@ -5,6 +5,7 @@ module MissionControl::Servers
|
|
5
5
|
@service_setting = @project.service_settings.find_or_initialize_by(hostname: params[:id])
|
6
6
|
@service_setting.label = params[:id] if @service_setting.new_record?
|
7
7
|
@service_setting.save # To limit having to have a create action
|
8
|
+
@requests = @project.requests.select(:hostname).distinct.pluck(:hostname)
|
8
9
|
end
|
9
10
|
|
10
11
|
def update
|
@@ -21,7 +22,7 @@ module MissionControl::Servers
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def label
|
24
|
-
params.require(:service_setting).permit(:label)
|
25
|
+
params.require(:service_setting).permit(:label, :hostname, :request_hostname)
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ["input"]
|
5
|
+
|
6
|
+
connect() {
|
7
|
+
console.log("Hostname select controller connected");
|
8
|
+
}
|
9
|
+
|
10
|
+
copy(event) {
|
11
|
+
event.preventDefault();
|
12
|
+
|
13
|
+
const hostnameElement = event.target.closest('li').querySelector('[data-target="hostname"]');
|
14
|
+
|
15
|
+
if (hostnameElement) {
|
16
|
+
this.inputTarget.value = hostnameElement.textContent.trim();
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
@@ -0,0 +1,97 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static values = {
|
5
|
+
data: String,
|
6
|
+
darkBorderColor: "rgb(255, 255, 255, 0.1)",
|
7
|
+
lightBorderColor: "rgb(0, 0, 0, 0.1)"
|
8
|
+
}
|
9
|
+
|
10
|
+
connect() {
|
11
|
+
this.observeHtmlClassChanges()
|
12
|
+
this.values = JSON.parse(this.dataValue)
|
13
|
+
this.displayChart()
|
14
|
+
}
|
15
|
+
|
16
|
+
observeHtmlClassChanges() {
|
17
|
+
const observer = new MutationObserver(mutations => {
|
18
|
+
mutations.forEach(mutation => {
|
19
|
+
if (mutation.type === "attributes" && mutation.attributeName === "class") {
|
20
|
+
this.updateChartColors()
|
21
|
+
}
|
22
|
+
})
|
23
|
+
})
|
24
|
+
observer.observe(document.documentElement, { attributes: true })
|
25
|
+
}
|
26
|
+
|
27
|
+
updateChartColors() {
|
28
|
+
const isDarkMode = document.documentElement.classList.contains('dark')
|
29
|
+
const borderColor = isDarkMode ? this.darkBorderColorValue : this.lightBorderColorValue
|
30
|
+
const backgroundColors = isDarkMode ? this.darkModeBackgroundColors() : this.lightModeBackgroundColors()
|
31
|
+
|
32
|
+
this.chart.data.datasets.forEach(dataset => {
|
33
|
+
dataset.borderColor = borderColor
|
34
|
+
dataset.backgroundColor = backgroundColors
|
35
|
+
})
|
36
|
+
this.chart.update()
|
37
|
+
}
|
38
|
+
|
39
|
+
darkModeBackgroundColors() {
|
40
|
+
return [
|
41
|
+
"rgb(45, 167, 167)",
|
42
|
+
"rgb(204, 175, 55)",
|
43
|
+
"rgb(204, 85, 0)",
|
44
|
+
"rgb(150, 75, 75)",
|
45
|
+
"rgb(85, 85, 255)"
|
46
|
+
]
|
47
|
+
}
|
48
|
+
|
49
|
+
lightModeBackgroundColors() {
|
50
|
+
return [
|
51
|
+
"rgb(75, 192, 192)",
|
52
|
+
"rgb(255, 159, 64)",
|
53
|
+
"rgb(255, 99, 132)",
|
54
|
+
"rgb(153, 102, 255)",
|
55
|
+
"rgb(54, 162, 235)"
|
56
|
+
]
|
57
|
+
}
|
58
|
+
|
59
|
+
|
60
|
+
displayChart() {
|
61
|
+
const isDarkMode = document.documentElement.classList.contains('dark')
|
62
|
+
const backgroundColors = isDarkMode ? this.darkModeBackgroundColors() : this.lightModeBackgroundColors()
|
63
|
+
const borderColor = isDarkMode ? this.darkBorderColorValue : this.lightBorderColorValue
|
64
|
+
|
65
|
+
this.chart = new Chart(this.element, {
|
66
|
+
type: 'doughnut',
|
67
|
+
data: {
|
68
|
+
labels: ['2xx', '3xx', '4xx', '5xx', 'Unknown'],
|
69
|
+
datasets: [{
|
70
|
+
data: [
|
71
|
+
this.values.sum_2xx,
|
72
|
+
this.values.sum_3xx,
|
73
|
+
this.values.sum_4xx,
|
74
|
+
this.values.sum_5xx,
|
75
|
+
this.values.unknown
|
76
|
+
],
|
77
|
+
backgroundColor: backgroundColors,
|
78
|
+
borderColor: borderColor,
|
79
|
+
borderWidth: 1,
|
80
|
+
hoverOffset: 4
|
81
|
+
}]
|
82
|
+
},
|
83
|
+
options: {
|
84
|
+
responsive: true,
|
85
|
+
plugins: {
|
86
|
+
legend: {
|
87
|
+
position: 'top',
|
88
|
+
},
|
89
|
+
title: {
|
90
|
+
display: true,
|
91
|
+
text: 'HTTP Status Codes Distribution'
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
})
|
96
|
+
}
|
97
|
+
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module MissionControl::Servers
|
2
2
|
class Project < ApplicationRecord
|
3
3
|
has_many :services, dependent: :destroy
|
4
|
+
has_many :requests, dependent: :destroy
|
4
5
|
has_many :service_settings, dependent: :destroy
|
5
6
|
has_many :public_projects, dependent: :destroy
|
6
7
|
has_secure_token :token
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MissionControl::Servers
|
2
|
+
class Request < ApplicationRecord
|
3
|
+
belongs_to :project
|
4
|
+
after_create :trim_old_requests
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def recent_totals(project, hostname)
|
8
|
+
recent_requests(project, hostname)
|
9
|
+
end
|
10
|
+
|
11
|
+
def recent_requests(project, hostname)
|
12
|
+
where(
|
13
|
+
hostname: [service_setting(project, hostname), hostname].compact.uniq,
|
14
|
+
created_at: 1.minute.ago..
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def service_setting(project, hostname)
|
19
|
+
project.service_settings.find_by(hostname: hostname)&.request_hostname
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def trim_old_requests
|
26
|
+
project.requests.where(hostname: hostname).where(created_at: ..1.hour.ago).delete_all
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -6,83 +6,87 @@ module MissionControl::Servers
|
|
6
6
|
|
7
7
|
scope :ordered, -> { order(created_at: :desc) }
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
9
|
+
class << self
|
10
|
+
def combo_history(services, start_time: 1.hour.ago, end_time: Time.now.utc)
|
11
|
+
timestamps = (start_time.to_i..end_time.to_i).step(60).map { |t| Time.at(t).utc.change(sec: 0).to_i }
|
12
|
+
grouped_services = services.group_by { |service| service.created_at.utc.change(sec: 0).to_i }
|
13
|
+
|
14
|
+
cpu_usages = []
|
15
|
+
memory_usages = []
|
16
|
+
created_at_times = []
|
17
|
+
|
18
|
+
timestamps.each do |timestamp|
|
19
|
+
relevant_services = grouped_services[timestamp] || []
|
20
|
+
max_cpu_service = relevant_services.max_by(&:cpu)
|
21
|
+
max_memory_service = relevant_services.max_by do |service|
|
22
|
+
return 0 if service.mem_used.to_f + service.mem_free.to_f == 0.0
|
23
|
+
service.mem_used.to_f / (service.mem_used.to_f + service.mem_free.to_f)
|
24
|
+
end
|
25
|
+
cpu_usages << (max_cpu_service&.cpu || 0).to_f
|
26
|
+
if max_memory_service
|
27
|
+
total_memory = max_memory_service.mem_used.to_f + max_memory_service.mem_free.to_f
|
28
|
+
percentage_memory_used = (max_memory_service.mem_used.to_f / total_memory) * 100
|
29
|
+
memory_usages << percentage_memory_used.to_i
|
30
|
+
else
|
31
|
+
memory_usages << 0.0
|
32
|
+
end
|
33
|
+
created_at_times << Time.at(timestamp).utc.strftime('%H:%M%p')
|
23
34
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
|
36
|
+
data = [
|
37
|
+
{
|
38
|
+
label: 'CPU Usage',
|
39
|
+
data: cpu_usages,
|
40
|
+
fill: true,
|
41
|
+
borderColor: "rgb(54, 162, 235)",
|
42
|
+
tension: 0.25
|
43
|
+
},
|
44
|
+
{
|
45
|
+
label: 'Memory Usage',
|
46
|
+
data: memory_usages,
|
47
|
+
fill: true,
|
48
|
+
borderColor: "rgb(235, 127, 54)",
|
49
|
+
tension: 0.25
|
50
|
+
}
|
51
|
+
]
|
52
|
+
{ created_at_times: created_at_times, data: data }
|
33
53
|
end
|
34
54
|
|
35
|
-
data = [
|
36
|
-
{
|
37
|
-
label: 'CPU Usage',
|
38
|
-
data: cpu_usages,
|
39
|
-
fill: true,
|
40
|
-
borderColor: "rgb(54, 162, 235)",
|
41
|
-
tension: 0.25
|
42
|
-
},
|
43
|
-
{
|
44
|
-
label: 'Memory Usage',
|
45
|
-
data: memory_usages,
|
46
|
-
fill: true,
|
47
|
-
borderColor: "rgb(235, 127, 54)",
|
48
|
-
tension: 0.25
|
49
|
-
}
|
50
|
-
]
|
51
|
-
{ created_at_times: created_at_times, data: data }
|
52
|
-
end
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
-
|
56
|
+
def cpu_usage_history(services, start_time: 1.hour.ago, end_time: Time.now.utc)
|
57
|
+
timestamps = (start_time.to_i..end_time.to_i).step(60).map { |t| Time.at(t).utc.change(sec: 0).to_i }
|
58
|
+
grouped_services = services.group_by { |service| service.created_at.utc.change(sec: 0).to_i }
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
+
cpu_usages = []
|
61
|
+
created_at_times = []
|
62
|
+
|
63
|
+
timestamps.each do |timestamp|
|
64
|
+
relevant_services = grouped_services[timestamp] || []
|
65
|
+
max_cpu_service = relevant_services.max_by(&:cpu)
|
66
|
+
cpu_usages << (max_cpu_service&.cpu || 0).to_f
|
67
|
+
created_at_times << Time.at(timestamp).utc.strftime('%H:%M%p')
|
68
|
+
end
|
60
69
|
|
61
|
-
|
62
|
-
relevant_services = grouped_services[timestamp] || []
|
63
|
-
max_cpu_service = relevant_services.max_by(&:cpu)
|
64
|
-
cpu_usages << (max_cpu_service&.cpu || 0).to_f
|
65
|
-
created_at_times << Time.at(timestamp).utc.strftime('%H:%M%p')
|
70
|
+
{ cpu_usages: cpu_usages, created_at_times: created_at_times }
|
66
71
|
end
|
67
72
|
|
68
|
-
{ cpu_usages: cpu_usages, created_at_times: created_at_times }
|
69
|
-
end
|
70
73
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
+
def memory_usage_history(services, start_time: 1.hour.ago, end_time: Time.now.utc)
|
75
|
+
timestamps = (start_time.to_i..end_time.to_i).step(60).map { |t| Time.at(t).utc.change(sec: 0).to_i }
|
76
|
+
grouped_services = services.group_by { |service| service.created_at.utc.change(sec: 0).to_i }
|
74
77
|
|
75
|
-
|
76
|
-
|
78
|
+
memory_usages = []
|
79
|
+
created_at_times = []
|
77
80
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
timestamps.each do |timestamp|
|
82
|
+
relevant_services = grouped_services[timestamp] || []
|
83
|
+
max_memory_service = relevant_services.max_by(&:mem_used)
|
84
|
+
memory_usages << (max_memory_service&.mem_used || 0).to_f
|
85
|
+
created_at_times << Time.at(timestamp).utc.strftime('%H:%M%p')
|
86
|
+
end
|
84
87
|
|
85
|
-
|
88
|
+
{ memory_usages: memory_usages, created_at_times: created_at_times }
|
89
|
+
end
|
86
90
|
end
|
87
91
|
|
88
92
|
def mem_percent
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<%= turbo_frame_tag dom_id(@project, [@hostname, :request_chart].join('-')) do %>
|
2
|
+
<div class="progress-bar" style="height: 5px; width: 100%;">
|
3
|
+
<div class="progress" data-refresh-target="progressBar" style="height: 100%; width: 100%;"></div>
|
4
|
+
</div>
|
5
|
+
<div class="flex flex-col items-center justify-center bg-gray-400/5 p-8">
|
6
|
+
<dt class="pie-chart">
|
7
|
+
<canvas class="chart"
|
8
|
+
data-controller="status-chart"
|
9
|
+
data-status-chart-data-value="<%= @summary_hash.to_json %>"></canvas>
|
10
|
+
</dt>
|
11
|
+
<dd class="card-heading">
|
12
|
+
Response Codes
|
13
|
+
</dd>
|
14
|
+
</div>
|
15
|
+
<% end %>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<%= turbo_frame_tag dom_id(@project, [@hostname, :request_total].join('-')) do %>
|
2
|
+
<div class="progress-bar bg-gray-300" style="height: 5px; width: 100%;">
|
3
|
+
<div class="progress bg-blue-500" data-refresh-target="progressBar" style="height: 100%; width: 100%;"></div>
|
4
|
+
</div>
|
5
|
+
<div class="flex flex-col bg-gray-400/5 p-8">
|
6
|
+
<dd class="card-heading">
|
7
|
+
Total Requests
|
8
|
+
</dd>
|
9
|
+
<dt class="stat-chart">
|
10
|
+
<%= number_with_delimiter @total_sum %>
|
11
|
+
</dt>
|
12
|
+
</div>
|
13
|
+
<% end %>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<%= tag.div id: [hostname, "tab-content"].join("-"), class: "py-4 sm:py-8 tab-content", "data-target": hostname, "data-tabs-target": "content" do %>
|
2
|
-
<div class="mx-auto
|
3
|
-
<div class="mx-auto
|
4
|
-
<dl class="grid grid-cols-1 gap-0.5 overflow-hidden rounded-2xl text-center sm:grid-cols-2 lg:grid-cols-
|
2
|
+
<div class="mx-auto px-6 lg:px-8">
|
3
|
+
<div class="mx-auto lg:max-w-none">
|
4
|
+
<dl class="grid grid-cols-1 gap-0.5 overflow-hidden rounded-2xl text-center sm:grid-cols-2 lg:grid-cols-6">
|
5
5
|
<%= turbo_frame_tag dom_id(project, [hostname, :cpu_usage].join('-')),
|
6
6
|
src: project_dashboards_cpu_usage_path(project_id: project.token, hostname: hostname),
|
7
7
|
"data-controller": "refresh",
|
@@ -25,6 +25,18 @@
|
|
25
25
|
"data-controller": "refresh",
|
26
26
|
"data-refresh-src-value": project_dashboards_last_seen_path(project_id: project.token, hostname: hostname),
|
27
27
|
"data-refresh-interval-value": 60000 %>
|
28
|
+
|
29
|
+
<%= turbo_frame_tag dom_id(project, [hostname, :request_total].join('-')),
|
30
|
+
src: project_dashboards_request_total_path(project_id: project.token, hostname: hostname),
|
31
|
+
"data-controller": "refresh",
|
32
|
+
"data-refresh-src-value": project_dashboards_request_total_path(project_id: project.token, hostname: hostname),
|
33
|
+
"data-refresh-interval-value": 60000 %>
|
34
|
+
|
35
|
+
<%= turbo_frame_tag dom_id(project, [hostname, :request_chart].join('-')),
|
36
|
+
src: project_dashboards_request_chart_path(project_id: project.token, hostname: hostname),
|
37
|
+
"data-controller": "refresh",
|
38
|
+
"data-refresh-src-value": project_dashboards_request_chart_path(project_id: project.token, hostname: hostname),
|
39
|
+
"data-refresh-interval-value": 60000 %>
|
28
40
|
</dl>
|
29
41
|
<% if params[:combo] == "false" %>
|
30
42
|
<dl class="mt-8 grid grid-cols-1 gap-0.5 overflow-hidden rounded-2xl text-center sm:grid-cols-1 lg:grid-cols-2">
|
@@ -7,13 +7,45 @@
|
|
7
7
|
</div>
|
8
8
|
</div>
|
9
9
|
|
10
|
-
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 my-10">
|
10
|
+
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 my-10" data-controller="hostname-select">
|
11
11
|
<h1 class="text-2xl text-center font-semibold text-gray-normal mb-8">Editing <%= @service_setting.hostname %></h1>
|
12
12
|
<%= form_with(model: [@project, @service_setting], class: "max-w-lg mx-auto my-10") do |form| %>
|
13
13
|
<%= form.hidden_field :hostname %>
|
14
14
|
<div class="mb-4">
|
15
15
|
<%= form.label :label, class: "block text-gray-700 text-sm font-bold mb-2" %>
|
16
16
|
<%= form.text_field :label, class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" %>
|
17
|
+
<p class="block text-gray-400 text-sm mt-1">
|
18
|
+
This is for display purposes on the dashboard. It's a good idea to name it something that makes sense to you.
|
19
|
+
</p>
|
20
|
+
</div>
|
21
|
+
<div class="mb-4">
|
22
|
+
<%= form.label :request_hostname, class: "block text-gray-700 text-sm font-bold mb-2" %>
|
23
|
+
<%= form.text_field :request_hostname, "data-hostname-select-target": "input",
|
24
|
+
class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" %>
|
25
|
+
<p class="block text-gray-400 text-sm mt-1">
|
26
|
+
With the server requets and status codes coming in, they may report a different hostname if you're running
|
27
|
+
them in docker or a different environment. This is the hostname that will be used to match the server.
|
28
|
+
</p>
|
29
|
+
</div>
|
30
|
+
<div class="border-t border-gray-100 px-4 py-6 sm:col-span-2 sm:px-0">
|
31
|
+
<dt class="text-sm font-medium leading-6 text-gray-900">Recent Hosts</dt>
|
32
|
+
<p class="block text-gray-400 text-sm mt-1">
|
33
|
+
Here are some of the recent hostnames that have been requested. You can select one of these if you'd like.
|
34
|
+
</p>
|
35
|
+
<dd class="mt-2 text-sm text-gray-900">
|
36
|
+
<ul role="list" class="divide-y divide-gray-100 rounded-md border border-gray-200">
|
37
|
+
<% @requests.each do |hostname| %>
|
38
|
+
<li class="flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6">
|
39
|
+
<div class="flex w-0 flex-1 items-center">
|
40
|
+
<span class="truncate font-medium dark:text-indigo-200" data-target="hostname"><%= hostname %></span>
|
41
|
+
</div>
|
42
|
+
<div class="ml-4 flex-shrink-0">
|
43
|
+
<a href="#" data-action="hostname-select#copy" class="font-medium text-indigo-600 dark:text-indigo-200">Select</a>
|
44
|
+
</div>
|
45
|
+
</li>
|
46
|
+
<% end %>
|
47
|
+
</ul>
|
48
|
+
</dd>
|
17
49
|
</div>
|
18
50
|
<div class="flex items-center justify-between">
|
19
51
|
<%= form.submit class: "btn btn-primary btn-block" %>
|
data/config/routes.rb
CHANGED
@@ -13,6 +13,8 @@ MissionControl::Servers::Engine.routes.draw do
|
|
13
13
|
resource :cpu_history, only: :show
|
14
14
|
resource :memory_history, only: :show
|
15
15
|
resource :combo_history, only: :show
|
16
|
+
resource :request_total, only: :show
|
17
|
+
resource :request_chart, only: :show
|
16
18
|
end
|
17
19
|
end
|
18
20
|
root to: "projects#index"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateMissionControlServersRequests < ActiveRecord::Migration[7.1]
|
2
|
+
def change
|
3
|
+
create_table :mission_control_servers_requests do |t|
|
4
|
+
t.belongs_to :project, null: false, foreign_key: { to_table: :mission_control_servers_projects }
|
5
|
+
t.string :hostname
|
6
|
+
t.integer :sum_2xx, default: 0
|
7
|
+
t.integer :sum_3xx, default: 0
|
8
|
+
t.integer :sum_4xx, default: 0
|
9
|
+
t.integer :sum_5xx, default: 0
|
10
|
+
t.integer :unknown, default: 0
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class AddMissingMissionControlServerIndexes < ActiveRecord::Migration[7.1]
|
2
|
+
def change
|
3
|
+
remove_index :mission_control_servers_services, :hostname
|
4
|
+
add_index :mission_control_servers_services, [:hostname, :created_at]
|
5
|
+
add_index :mission_control_servers_requests, [:hostname, :created_at]
|
6
|
+
end
|
7
|
+
end
|
@@ -5,8 +5,17 @@ require "stimulus-rails"
|
|
5
5
|
module MissionControl
|
6
6
|
module Servers
|
7
7
|
class Engine < ::Rails::Engine
|
8
|
+
if ENV['RAILS_ENV'] == 'test'
|
9
|
+
require 'simplecov'
|
10
|
+
SimpleCov.start 'rails'
|
11
|
+
end
|
12
|
+
|
8
13
|
isolate_namespace MissionControl::Servers
|
9
14
|
|
15
|
+
initializer 'mission_control-servers.middleware.request_tally' do |app|
|
16
|
+
app.middleware.use RequestTallyMiddleware
|
17
|
+
end
|
18
|
+
|
10
19
|
initializer "mission_control-servers.assets" do |app|
|
11
20
|
app.config.assets.paths << root.join("app/javascript")
|
12
21
|
app.config.assets.precompile += %w[ mission_control_servers_manifest ]
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module MissionControl
|
2
|
+
module Servers
|
3
|
+
class RequestTallyMiddleware
|
4
|
+
@@tally = Hash.new(0)
|
5
|
+
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
@hostname = if ENV["KAMAL_VERSION"].present?
|
9
|
+
Socket.gethostname.to_s.split("-").first
|
10
|
+
else
|
11
|
+
ENV.fetch('HOSTNAME', Socket.gethostname)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
req = Rack::Request.new(env)
|
17
|
+
if ingress_request?(req)
|
18
|
+
req.update_param('tally', @@tally.clone.merge('hostname' => @hostname))
|
19
|
+
@@tally.clear
|
20
|
+
end
|
21
|
+
|
22
|
+
status, headers, response = @app.call(env)
|
23
|
+
|
24
|
+
if !ingress_request?(req)
|
25
|
+
category = categorize_status(status)
|
26
|
+
update_tally(category)
|
27
|
+
end
|
28
|
+
|
29
|
+
[status, headers, response]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def ingress_request?(req)
|
35
|
+
req.post? && req.path.match?(/^\/[^\/]+\/projects\/[^\/]+\/ingress$/)
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_tally(category)
|
39
|
+
@@tally[category] += 1
|
40
|
+
end
|
41
|
+
|
42
|
+
def categorize_status(status)
|
43
|
+
case status
|
44
|
+
when 200..299 then 'sum_2xx'
|
45
|
+
when 300..399 then 'sum_3xx'
|
46
|
+
when 400..499 then 'sum_4xx'
|
47
|
+
when 500..599 then 'sum_5xx'
|
48
|
+
else 'unknown'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -11,6 +11,8 @@ module MissionControl
|
|
11
11
|
get "#{engine_mount_path}/projects/:project_id/dashboards/cpu_history", to: "mission_control/servers/dashboards/cpu_histories#show"
|
12
12
|
get "#{engine_mount_path}/projects/:project_id/dashboards/memory_history", to: "mission_control/servers/dashboards/memory_histories#show"
|
13
13
|
get "#{engine_mount_path}/projects/:project_id/dashboards/combo_history", to: "mission_control/servers/dashboards/combo_histories#show"
|
14
|
+
get "#{engine_mount_path}/projects/:project_id/dashboards/request_total", to: "mission_control/servers/dashboards/request_totals#show"
|
15
|
+
get "#{engine_mount_path}/projects/:project_id/dashboards/request_chart", to: "mission_control/servers/dashboards/request_charts#show"
|
14
16
|
get "#{engine_mount_path}/projects/:project_id/public_projects/new", to: "mission_control/servers/public_projects#new", as: :new_project_public_project
|
15
17
|
get "#{engine_mount_path}/projects/:project_id/public_projects/:id", to: "mission_control/servers/public_projects#show"
|
16
18
|
get "#{engine_mount_path}/projects/:project_id/script", to: "mission_control/servers/scripts#show"
|
@@ -2,6 +2,7 @@ require "mission_control/servers/version"
|
|
2
2
|
require "mission_control/servers/engine"
|
3
3
|
require "mission_control/servers/configuration"
|
4
4
|
require "mission_control/servers/routing_helpers"
|
5
|
+
require "mission_control/servers/request_tally_middleware"
|
5
6
|
|
6
7
|
require "zeitwerk"
|
7
8
|
|
@@ -5,5 +5,5 @@ task :tailwind_engine_watch do
|
|
5
5
|
-i #{MissionControl::Servers::Engine.root.join("app/assets/stylesheets/mission_control/servers/application.tailwind.css")} \
|
6
6
|
-o #{MissionControl::Servers::Engine.root.join("app/assets/builds/mission_control_servers_application.css")} \
|
7
7
|
-c #{MissionControl::Servers::Engine.root.join("config/tailwind.config.js")} \
|
8
|
-
|
8
|
+
-w"
|
9
9
|
end
|