status page
This commit is contained in:
parent
68c3fcb909
commit
537776f0c5
8 changed files with 168 additions and 50 deletions
|
@ -31,9 +31,15 @@ class StatusController extends Controller
|
||||||
{
|
{
|
||||||
$endpoints = array_keys(config('status.check'));
|
$endpoints = array_keys(config('status.check'));
|
||||||
$events = DB::table('status_events')
|
$events = DB::table('status_events')
|
||||||
|
->where('event_date', '>', Carbon::now()->subWeek(1)->format('Y-m-d'))
|
||||||
->get();
|
->get();
|
||||||
$incidents = [];
|
$incidents = [];
|
||||||
|
|
||||||
|
for ($i = 0; $i < 7; $i++) {
|
||||||
|
$date = Carbon::now()->subDay($i);
|
||||||
|
$incidents[$date->format('M j, Y')] = [];
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($events as $row) {
|
foreach ($events as $row) {
|
||||||
$date = Carbon::createFromFormat('Y-m-d H:i:s', $row->event_date);
|
$date = Carbon::createFromFormat('Y-m-d H:i:s', $row->event_date);
|
||||||
$incidents[$date->format('M j, Y')][] = [
|
$incidents[$date->format('M j, Y')][] = [
|
||||||
|
@ -60,7 +66,7 @@ class StatusController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
[$address, $port, $protocol] = explode('/', $endpoints[$name]);
|
[$address, $port, $protocol] = explode('/', $endpoints[$name]);
|
||||||
$status = new Status($address, $port, $protocol);
|
$status = new Status($address, $port, $protocol, true, ($_GET['history'] ?? '1') !== '0');
|
||||||
|
|
||||||
if ($status->state === null) {
|
if ($status->state === null) {
|
||||||
$status->check();
|
$status->check();
|
||||||
|
|
|
@ -220,9 +220,18 @@ class Net
|
||||||
CURLOPT_CONNECTTIMEOUT => 2,
|
CURLOPT_CONNECTTIMEOUT => 2,
|
||||||
CURLOPT_TIMEOUT => 4,
|
CURLOPT_TIMEOUT => 4,
|
||||||
CURLOPT_USERAGENT => 'Sakura/1.0 (+https://sakura.flash.moe)',
|
CURLOPT_USERAGENT => 'Sakura/1.0 (+https://sakura.flash.moe)',
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => false, // for CF flexible SSL
|
||||||
]);
|
]);
|
||||||
|
|
||||||
switch (strtolower($method)) {
|
switch (strtolower($method)) {
|
||||||
|
case 'head':
|
||||||
|
curl_setopt_array($curl, [
|
||||||
|
CURLOPT_HEADER => true,
|
||||||
|
CURLOPT_NOBODY => true,
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'post':
|
case 'post':
|
||||||
curl_setopt_array($curl, [
|
curl_setopt_array($curl, [
|
||||||
CURLOPT_POST => is_array($params) ? count($params) : 1,
|
CURLOPT_POST => is_array($params) ? count($params) : 1,
|
||||||
|
|
|
@ -24,24 +24,25 @@ class Status
|
||||||
public const ERROR = 1;
|
public const ERROR = 1;
|
||||||
public const FAIL = 0;
|
public const FAIL = 0;
|
||||||
|
|
||||||
public function __construct(string $address, int $port, string $protocol = '', bool $populate = true)
|
public function __construct(string $address, int $port, string $protocol = '', bool $populate = true, bool $history = true)
|
||||||
{
|
{
|
||||||
$this->address = $address;
|
$this->address = $address;
|
||||||
$this->port = $port;
|
$this->port = $port;
|
||||||
$this->protocol = $protocol;
|
$this->protocol = $protocol;
|
||||||
|
|
||||||
if ($populate) {
|
if ($populate) {
|
||||||
$this->populate();
|
$this->populate($history);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function populate(): void
|
public function populate(bool $history = true): void
|
||||||
{
|
{
|
||||||
$this->history = DB::table('status_history')
|
$this->history = DB::table('status_history')
|
||||||
->where('history_name', $this->address)
|
->where('history_name', $this->address)
|
||||||
->where('history_port', $this->port)
|
->where('history_port', $this->port)
|
||||||
->where('history_protocol', $this->protocol)
|
->where('history_protocol', $this->protocol)
|
||||||
->orderBy('history_date', 'desc')
|
->orderBy('history_date', 'desc')
|
||||||
|
->limit($history ? 10 : 1)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$this->state = isset($this->history[0]) ? intval($this->history[0]->history_state) : null;
|
$this->state = isset($this->history[0]) ? intval($this->history[0]->history_state) : null;
|
||||||
|
@ -50,7 +51,14 @@ class Status
|
||||||
public function check(): int
|
public function check(): int
|
||||||
{
|
{
|
||||||
$this->state = static::FAIL;
|
$this->state = static::FAIL;
|
||||||
$sock = checkdnsrr($this->address) ? fsockopen($this->address, $this->port, $errno, $errstr, 1) : false;
|
|
||||||
|
$sock = Net::detectIPVersion(
|
||||||
|
$this->address[0] === '[' && $this->address[strlen($this->address) - 1] === ']'
|
||||||
|
? substr($this->address, 1, -1)
|
||||||
|
: $this->address
|
||||||
|
) !== 0 || dns_get_record($this->address, DNS_A | DNS_A6 | DNS_MX)
|
||||||
|
? fsockopen($this->address, $this->port, $errno, $errstr, 1)
|
||||||
|
: false;
|
||||||
|
|
||||||
if ($sock !== false) {
|
if ($sock !== false) {
|
||||||
fclose($sock);
|
fclose($sock);
|
||||||
|
@ -64,7 +72,7 @@ class Status
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($header !== false && strtolower(substr($header, 0, 4)) == 'http') {
|
if ($header !== false && strtolower(substr($header, 0, 4)) == 'http') {
|
||||||
list($protocol, $response, $text) = explode(' ', $header, 3);
|
[$protocol, $response, $text] = explode(' ', $header, 3);
|
||||||
|
|
||||||
if ($response >= 400 && $response < 500) {
|
if ($response >= 400 && $response < 500) {
|
||||||
$this->state = static::ERROR;
|
$this->state = static::ERROR;
|
||||||
|
|
|
@ -211,6 +211,10 @@ repo['Sakura'] = https://github.com/flashwave/sakura
|
||||||
|
|
||||||
; Status settings
|
; Status settings
|
||||||
[status]
|
[status]
|
||||||
|
; Format:
|
||||||
|
; check['display name'] = "address/port/protocol"
|
||||||
|
; protocol only really matters if you're pinging to http or https
|
||||||
|
; in which case it should be set to the respective value
|
||||||
check['flash.moe http'] = "flash.moe/80/http"
|
check['flash.moe http'] = "flash.moe/80/http"
|
||||||
check['flash.moe https'] = "flash.moe/443/https"
|
check['flash.moe https'] = "flash.moe/443/https"
|
||||||
|
|
||||||
|
|
11
resources/assets/less/yuuno/bem/status.less
Normal file
11
resources/assets/less/yuuno/bem/status.less
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.status {
|
||||||
|
&__services {
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__service {
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@
|
||||||
@import "bem/profile";
|
@import "bem/profile";
|
||||||
@import "bem/settings";
|
@import "bem/settings";
|
||||||
@import "bem/sidepanel-table";
|
@import "bem/sidepanel-table";
|
||||||
|
@import "bem/status";
|
||||||
@import "bem/topic";
|
@import "bem/topic";
|
||||||
@import "bem/uploader";
|
@import "bem/uploader";
|
||||||
@import "bem/user";
|
@import "bem/user";
|
||||||
|
|
59
resources/views/aitemu/status/index.twig
Normal file
59
resources/views/aitemu/status/index.twig
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
{% extends 'master.twig' %}
|
||||||
|
|
||||||
|
{% set title = 'Status' %}
|
||||||
|
{% set banner = '/images/status-banner.png' %}
|
||||||
|
{% set banner_large = true %}
|
||||||
|
|
||||||
|
{% block banner_content %}
|
||||||
|
<div class="banner__bottom">
|
||||||
|
<div class="status__overall status__overall--good">
|
||||||
|
<span class="status__overall-icon"></span> <span class="status__overall-text">all services are go!</span>
|
||||||
|
</div>
|
||||||
|
<div class="status__last-update">
|
||||||
|
this is an not as static as it was before design preview
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="platform status__response">
|
||||||
|
<div class="status__services">
|
||||||
|
{% for name in endpoints %}
|
||||||
|
<div class="status__service status__service--good">
|
||||||
|
<div class="status__service-icon"></div>
|
||||||
|
<div class="status__service-name">{{ name }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="status__graph">graph goes here</div>
|
||||||
|
</div>
|
||||||
|
<div class="platform status__incidents">
|
||||||
|
{% for date, events in incidents %}
|
||||||
|
<div class="status__incident">
|
||||||
|
<div class="status__incident-date">{{ date }}</div>
|
||||||
|
<div class="status__updates">
|
||||||
|
{% if events|length > 0 %}
|
||||||
|
{% for event in events %}
|
||||||
|
{% set state = event.state|lower %}
|
||||||
|
<div class="status__update status__update--{% if state == 'resolved' %}good{% elseif state == 'monitoring' %}bad{% else %}busy{% endif %}">
|
||||||
|
<div class="status__update-meta">
|
||||||
|
<div class="status__update-time">{{ event.time }} UTC</div>
|
||||||
|
<div class="status__update-state">{{ event.state }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="status__update-text">
|
||||||
|
{{ event.comment }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="status__update status__update">
|
||||||
|
<div class="status__update-text">
|
||||||
|
No incidents occurred.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -1,59 +1,79 @@
|
||||||
{% extends '@aitemu/master.twig' %}
|
{% extends 'master.twig' %}
|
||||||
|
|
||||||
{% set title = 'Status' %}
|
{% set title = 'Status' %}
|
||||||
{% set banner = '/images/status-banner.png' %}
|
|
||||||
{% set banner_large = true %}
|
|
||||||
|
|
||||||
{% block banner_content %}
|
{% block js %}
|
||||||
<div class="banner__bottom">
|
<script>
|
||||||
<div class="status__overall status__overall--good">
|
var yuunoStatusStates = [
|
||||||
<span class="status__overall-icon"></span> <span class="status__overall-text">all services are go!</span>
|
{"text": "Offline", "colour": "#800"},
|
||||||
</div>
|
{"text": "Experiencing Issues", "colour": "#840"},
|
||||||
<div class="status__last-update">
|
{"text": "Online", "colour": "#080"}
|
||||||
this is an not as static as it was before design preview
|
];
|
||||||
</div>
|
|
||||||
</div>
|
var yuunoStatusEndpoints = {{ endpoints|json_encode|raw }};
|
||||||
|
|
||||||
|
function yuunoCheckStatus(endpoint) {
|
||||||
|
var client = new Sakura.AJAX;
|
||||||
|
client.SetUrl("{{ route('status.data') }}?history=0&name=" + endpoint);
|
||||||
|
client.AddCallback(200, function () {
|
||||||
|
var result = client.JSON(),
|
||||||
|
field = document.querySelector('[data-status-name="' + endpoint + '"]');
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
if (field) {
|
||||||
|
field.innerText = 'error';
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = new Sakura.Dialogue;
|
||||||
|
error.Title = "Status";
|
||||||
|
error.Text = result.error;
|
||||||
|
error.Display();
|
||||||
|
} else {
|
||||||
|
var state = yuunoStatusStates[result.state];
|
||||||
|
field.innerText = state.text;
|
||||||
|
field.style.color = state.colour;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.Start(Sakura.HTTPMethod.GET);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("load", function () {
|
||||||
|
for (var i in yuunoStatusEndpoints) {
|
||||||
|
var endpoint = yuunoStatusEndpoints[i];
|
||||||
|
yuunoCheckStatus(endpoint);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="platform status__response">
|
<div class="content status">
|
||||||
|
<div class="content__header">Status</div>
|
||||||
<div class="status__services">
|
<div class="status__services">
|
||||||
{% for name in endpoints %}
|
{% for name in endpoints %}
|
||||||
<div class="status__service status__service--good">
|
<div class="status__service">
|
||||||
<div class="status__service-icon"></div>
|
<h1>{{ name }}</h1>
|
||||||
<div class="status__service-name">{{ name }}</div>
|
<h3 data-status-name="{{ name }}">loading...</h3>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="status__graph">graph goes here</div>
|
<div class="content__header">Incidents</div>
|
||||||
</div>
|
|
||||||
<div class="platform status__incidents">
|
|
||||||
{% for date, events in incidents %}
|
{% for date, events in incidents %}
|
||||||
<div class="status__incident">
|
<div class="news__head">{{ date }}</div>
|
||||||
<div class="status__incident-date">{{ date }}</div>
|
|
||||||
<div class="status__updates">
|
|
||||||
{% if events|length > 0 %}
|
{% if events|length > 0 %}
|
||||||
|
<table>
|
||||||
{% for event in events %}
|
{% for event in events %}
|
||||||
{% set state = event.state|lower %}
|
{% set state = event.state|lower %}
|
||||||
<div class="status__update status__update--{% if state == 'resolved' %}good{% elseif state == 'monitoring' %}bad{% else %}busy{% endif %}">
|
<tr>
|
||||||
<div class="status__update-meta">
|
<td><b><span style="font-size: .8em">{{ event.time }} UTC</span></b></td>
|
||||||
<div class="status__update-time">{{ event.time }} UTC</div>
|
<td><b style="font-size: .8em; color: {% if state == 'resolved' %}#080{% elseif state == 'monitoring' %}#800{% else %}#840{% endif %}">{{ event.state }}</b></td>
|
||||||
<div class="status__update-state">{{ event.state }}</div>
|
<td>{{ event.comment }}</td>
|
||||||
</div>
|
</tr>
|
||||||
<div class="status__update-text">
|
|
||||||
{{ event.comment }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="status__update status__update">
|
<i>No incidents occurred.</i>
|
||||||
<div class="status__update-text">
|
|
||||||
No incidents occurred.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Reference in a new issue