status page

This commit is contained in:
flash 2016-12-10 23:36:24 +01:00
parent 68c3fcb909
commit 537776f0c5
8 changed files with 168 additions and 50 deletions

View file

@ -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();

View file

@ -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,

View file

@ -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;

View file

@ -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"

View file

@ -0,0 +1,11 @@
.status {
&__services {
vertical-align: middle;
text-align: center;
}
&__service {
padding: 10px 20px;
display: inline-block;
}
}

View file

@ -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";

View 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 %}

View file

@ -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> {% if events|length > 0 %}
<div class="status__updates"> <table>
{% if events|length > 0 %} {% for event in events %}
{% for event in events %} {% set state = event.state|lower %}
{% set state = event.state|lower %} <tr>
<div class="status__update status__update--{% if state == 'resolved' %}good{% elseif state == 'monitoring' %}bad{% else %}busy{% endif %}"> <td><b><span style="font-size: .8em">{{ event.time }} UTC</span></b></td>
<div class="status__update-meta"> <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-time">{{ event.time }} UTC</div> <td>{{ event.comment }}</td>
<div class="status__update-state">{{ event.state }}</div> </tr>
</div> {% endfor %}
<div class="status__update-text"> </table>
{{ event.comment }} {% else %}
</div> <i>No incidents occurred.</i>
</div> {% endif %}
{% endfor %}
{% else %}
<div class="status__update status__update">
<div class="status__update-text">
No incidents occurred.
</div>
</div>
{% endif %}
</div>
</div>
{% endfor %} {% endfor %}
</div> </div>
{% endblock %} {% endblock %}