MapComplete/src/UI/Status/StatusGUI.svelte

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

347 lines
9.5 KiB
Svelte
Raw Normal View History

2024-07-14 03:55:47 +02:00
<script lang="ts">
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
2024-07-14 03:55:47 +02:00
import StatusIcon from "./StatusIcon.svelte"
import type { MCService } from "./MCService"
import ServiceIndicator from "./ServiceIndicator.svelte"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Constants from "../../Models/Constants"
import { Utils } from "../../Utils"
import Loading from "../Base/Loading.svelte"
import Checkbox from "../Base/Checkbox.svelte"
2024-07-14 03:55:47 +02:00
let services: MCService[] = []
let recheckSignal: UIEventSource<any> = new UIEventSource<any>(undefined)
let checkSignal = Stores.Chronic(10000)
let autoCheckAgain = new UIEventSource<boolean>(false)
function testDownload(url: string, raw: boolean = false): Store<{ success } | { error }> {
const src = new UIEventSource(undefined)
function check() {
const promise = raw ? Utils.download(url) : Utils.downloadJson(url)
2024-07-21 10:52:51 +02:00
promise?.then((d) => src.setData({ success: d }))?.catch((err) => src.setData({ error: err }))
}
check()
2024-07-21 10:52:51 +02:00
recheckSignal.addCallback((_) => check())
checkSignal.addCallback((_) => {
if (autoCheckAgain.data) {
check()
}
})
return src
}
2024-07-14 03:55:47 +02:00
function simpleMessage(s: Store<{ success: any } | { error: any }>): Store<string> {
2024-07-21 10:52:51 +02:00
return s.mapD((s) => {
2024-07-14 03:55:47 +02:00
if (s["success"]) {
return JSON.stringify(s["success"])
}
return s["error"]
})
}
{
const connection = new OsmConnection()
const osmApi = connection.apiIsOnline
services.push({
name: connection.Backend(),
2024-07-21 10:52:51 +02:00
status: osmApi.mapD((serviceState) => {
2024-07-14 03:55:47 +02:00
switch (serviceState) {
case "offline":
return "offline"
case "online":
return "online"
case "readonly":
return "degraded"
case "unknown":
return undefined
case "unreachable":
return "offline"
}
}),
message: osmApi,
2024-07-14 03:55:47 +02:00
})
}
{
const s = "https://studio.mapcomplete.org"
const status = testDownload(s + "/overview")
2024-07-14 03:55:47 +02:00
services.push({
name: s,
2024-07-21 10:52:51 +02:00
status: status.mapD((s) => {
2024-07-14 03:55:47 +02:00
if (s["error"]) {
return "offline"
}
const files: string[] = s["success"]["allFiles"]
if (files.length < 10) {
return "offline"
}
if (files.length < 100) {
return "degraded"
}
return "online"
}),
2024-07-21 10:52:51 +02:00
message: status.mapD((s) => {
if (s["error"]) {
2024-07-14 03:55:47 +02:00
return s["error"]
}
const files: string[] = s["success"]["allFiles"]
return "Contains " + (files.length ?? "no") + " files"
}),
2024-07-14 03:55:47 +02:00
})
}
{
2024-07-21 10:52:51 +02:00
services.push({
name: Constants.GeoIpServer,
status: testDownload(Constants.GeoIpServer + "/status").mapD((result) => {
if (result["success"].online) {
return "online"
}
if (result["error"]) {
return "offline"
} else {
return "degraded"
}
}),
message: simpleMessage(testDownload(Constants.GeoIpServer + "/ip")),
})
2024-07-14 03:55:47 +02:00
}
{
const s = Constants.ErrorReportServer
const status = testDownload(s.replace(/\/report$/, "/status"))
2024-07-14 03:55:47 +02:00
services.push({
name: s,
2024-07-21 10:52:51 +02:00
status: status.mapD((s) => {
2024-07-14 03:55:47 +02:00
if (s["error"]) {
return "offline"
}
const data = s["success"]
if (data["errors_today"] === 0) {
return "online"
}
return "degraded"
}),
message: simpleMessage(status),
2024-07-14 03:55:47 +02:00
})
}
{
const s = Constants.linkedDataProxy.replace(/\/[^/]*$/, "")
const status = testDownload(s + "/status")
2024-07-14 03:55:47 +02:00
services.push({
name: s,
2024-07-21 10:52:51 +02:00
status: status.mapD((s) => {
2024-07-14 03:55:47 +02:00
if (s["error"]) {
return "offline"
}
const data = s["success"]
if (data.cached_entries < 10 || data.uptime < 60 * 60) {
return "degraded"
}
return "online"
}),
message: simpleMessage(status),
2024-07-14 03:55:47 +02:00
})
}
{
const s = Constants.SummaryServer
const status = testDownload(s + "/summary/status.json")
2024-07-14 03:55:47 +02:00
services.push({
name: s,
2024-07-21 10:52:51 +02:00
status: status.mapD((s) => {
2024-07-14 03:55:47 +02:00
if (s["error"]) {
return "offline"
}
console.log(s)
const attributes = s["success"]["meta"]
const lastUpdate = new Date(attributes["current_timestamp"])
console.log("Last update:", lastUpdate, attributes["current_timestamp"], attributes)
const timediffSec = (new Date().getTime() - lastUpdate.getTime()) / 1000
const timediffDays = timediffSec / (60 * 60 * 26)
2024-07-27 11:36:01 +02:00
if (timediffDays > 30) {
2024-07-14 03:55:47 +02:00
return "degraded"
}
return "online"
}),
2024-07-21 10:52:51 +02:00
message: status.mapD((s) => {
2024-07-14 03:55:47 +02:00
if (s["error"]) {
return s["error"]
}
const attributes = s["success"]["meta"]
const lastUpdate = new Date(attributes["current_timestamp"])
const timediffSec = (new Date().getTime() - lastUpdate.getTime()) / 1000
const timediffDays = timediffSec / (60 * 60 * 26)
const json = JSON.stringify(s["success"], null, " ")
return "Database is " + Math.floor(timediffDays) + " days out of sync\n\n" + json
}),
2024-07-14 03:55:47 +02:00
})
}
{
const s = Constants.countryCoderEndpoint
const status = testDownload(s + "/0.0.0.json")
2024-07-14 03:55:47 +02:00
services.push({
name: s,
2024-07-21 10:52:51 +02:00
status: status.mapD((s) => {
2024-07-14 03:55:47 +02:00
if (s["error"]) {
return "offline"
}
const arr = s["success"]
if (Array.isArray(arr)) {
return "online"
}
return "degraded"
}),
2024-07-21 10:52:51 +02:00
message: status.map((s) => JSON.stringify(s)),
2024-07-14 03:55:47 +02:00
})
}
{
for (const defaultOverpassUrl of Constants.defaultOverpassUrls) {
const statusUrl = defaultOverpassUrl.replace(/\/interpreter$/, "/status")
const status = testDownload(statusUrl, true)
2024-07-14 03:55:47 +02:00
services.push({
name: "Overpass-server: " + defaultOverpassUrl,
2024-07-21 10:52:51 +02:00
status: status.mapD((result) => {
2024-07-14 03:55:47 +02:00
if (result["error"]) {
return "offline"
}
// "Connected as: 3587935836
// Current time: 2024-07-14T00:35:58Z
// Announced endpoint: gall.openstreetmap.de
// Rate limit: 6
// 6 slots available now.
// Currently running queries (pid, space limit, time limit, start time):\n"
const msgs = result["success"].split("\n")
return "online"
}),
message: simpleMessage(status),
2024-07-14 03:55:47 +02:00
})
}
}
{
services.push({
name: "Mangrove reviews",
2024-07-21 10:52:51 +02:00
status: testDownload("https://api.mangrove.reviews", true).mapD((r) => {
2024-07-14 03:55:47 +02:00
if (r["success"]) {
return "online"
}
return "offline"
}),
2024-07-14 03:55:47 +02:00
})
}
let all = new UIEventSource<"online" | "degraded" | "offline">("online")
let someLoading = new UIEventSource(true)
function setAll() {
2024-07-21 10:52:51 +02:00
const data = Utils.NoNull(services.map((s) => s.status.data))
2024-07-14 03:55:47 +02:00
someLoading.setData(data.length !== services.length)
2024-07-21 10:52:51 +02:00
if (data.some((d) => d === "offline")) {
2024-07-14 03:55:47 +02:00
all.setData("offline")
2024-07-21 10:52:51 +02:00
} else if (data.some((d) => d === "degraded")) {
2024-07-14 03:55:47 +02:00
all.setData("degraded")
2024-07-21 10:52:51 +02:00
} else if (data.some((d) => d === "online")) {
2024-07-14 03:55:47 +02:00
all.setData("online")
} else {
all.setData(undefined)
}
}
for (const service of services) {
service.status.addCallbackD(() => {
setAll()
})
}
const trafficLightUrl = "http://traffic_light_bicycle.local/"
let trafficLightIsOnline = testDownload(trafficLightUrl + "/status", true)
let enableTrafficLight = new UIEventSource(true)
/**
* So... IN my room, there is a traffic light. Like, an actual traffic light with bicycles.
* I put in an ESP32 and can now control it remotely - which is precisely what this code does.
* @param state
*/
async function setTrafficLight(state: "online" | "degraded" | "offline") {
try {
const url = trafficLightUrl
const status = await Utils.downloadJson(url + "status")
console.log(status)
if (!enableTrafficLight.data) {
await Utils.download(url + "configure?mode=0")
return
}
switch (state) {
case "offline":
await Utils.download(url + "configure?mode=3")
break
case "degraded":
await Utils.download(url + "configure?mode=2")
break
case "online":
await Utils.download(url + "configure?mode=1")
break
default:
await Utils.download(url + "configure?mode=7")
break
}
} catch (e) {
console.log("Could not connect to the traffic light")
}
}
2024-07-21 10:52:51 +02:00
all.addCallbackAndRunD((state) => {
setTrafficLight(state)
})
2024-07-21 10:52:51 +02:00
enableTrafficLight.addCallbackAndRunD((_) => {
setTrafficLight(all.data)
})
2024-07-14 03:55:47 +02:00
</script>
<h1>MapComplete status indicators</h1>
<div class="flex">
{#if $someLoading}
<Loading />
{/if}
<StatusIcon status={$all} cls="w-16 h-16" />
<button on:click={() => recheckSignal.ping()}>Check again</button>
2024-07-21 10:52:51 +02:00
<Checkbox selected={autoCheckAgain}>Automatically check again every 10s</Checkbox>
</div>
2024-07-21 10:52:51 +02:00
{#if $trafficLightIsOnline?.["success"]}
<Checkbox selected={enableTrafficLight}>Enable traffic light</Checkbox>
2024-07-14 03:55:47 +02:00
{/if}
{#each services as service}
<ServiceIndicator {service} />
{/each}
2024-07-21 10:52:51 +02:00
<button
on:click={() => {
fetch(Constants.ErrorReportServer, {
2024-07-21 10:52:51 +02:00
method: "POST",
body: JSON.stringify({
message: "Test via the status page, not an actual error",
version: Constants.vNumber,
}),
})
}}
>
Test error report function
</button>