mission_control-servers 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +14 -12
- 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
|