Magnetar stream status for notification view
ci/woodpecker/push/ociImagePush Pipeline failed
Details
ci/woodpecker/push/ociImagePush Pipeline failed
Details
This commit is contained in:
parent
d12b65fff7
commit
ff3f0927fb
|
@ -28,11 +28,13 @@ async-trait = "0.1"
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
axum = "0.7"
|
axum = "0.7"
|
||||||
axum-extra = "0.9"
|
axum-extra = "0.9"
|
||||||
|
base64 = "0.22"
|
||||||
cached = "0.47"
|
cached = "0.47"
|
||||||
cfg-if = "1"
|
cfg-if = "1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
compact_str = "0.7"
|
compact_str = "0.7"
|
||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
|
ed25519-dalek = "2.1"
|
||||||
either = "1.9"
|
either = "1.9"
|
||||||
emojis = "0.6"
|
emojis = "0.6"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
@ -42,6 +44,7 @@ headers = "0.4"
|
||||||
http = "1.0"
|
http = "1.0"
|
||||||
hyper = "1.1"
|
hyper = "1.1"
|
||||||
idna = "0.5"
|
idna = "0.5"
|
||||||
|
indexmap = "2.2"
|
||||||
itertools = "0.12"
|
itertools = "0.12"
|
||||||
lru = "0.12"
|
lru = "0.12"
|
||||||
miette = "5.9"
|
miette = "5.9"
|
||||||
|
@ -51,12 +54,14 @@ percent-encoding = "2.2"
|
||||||
quick-xml = "0.31"
|
quick-xml = "0.31"
|
||||||
redis = "0.24"
|
redis = "0.24"
|
||||||
regex = "1.9"
|
regex = "1.9"
|
||||||
reqwest = "0.11"
|
rsa = "0.9"
|
||||||
|
reqwest = "0.12"
|
||||||
sea-orm = "0.12"
|
sea-orm = "0.12"
|
||||||
sea-orm-migration = "0.12"
|
sea-orm-migration = "0.12"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
|
sha2 = "0.10"
|
||||||
strum = "0.25"
|
strum = "0.25"
|
||||||
tera = { version = "1", default-features = false }
|
tera = { version = "1", default-features = false }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
<MkError v-else-if="error" @retry="init()" />
|
<MkError v-else-if="error" @retry="init()" />
|
||||||
|
|
||||||
<div v-else-if="empty" key="_empty_" class="empty">
|
<div v-else-if="empty" key="_empty_" class="empty">
|
||||||
|
<MagStreamStatus @reconnect="reload()" />
|
||||||
|
|
||||||
<slot name="empty">
|
<slot name="empty">
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img
|
<img
|
||||||
|
@ -18,6 +20,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else ref="rootEl" class="list">
|
<div v-else ref="rootEl" class="list">
|
||||||
|
<MagStreamStatus @reconnect="reload()" />
|
||||||
|
|
||||||
<XList
|
<XList
|
||||||
v-slot="{ item: notificationGroup }"
|
v-slot="{ item: notificationGroup }"
|
||||||
class="elsfgstc"
|
class="elsfgstc"
|
||||||
|
@ -89,6 +93,7 @@ import XNotification from "@/components/MagNotification.vue";
|
||||||
import XNotificationGroup from "@/components/MagNotificationGroup.vue";
|
import XNotificationGroup from "@/components/MagNotificationGroup.vue";
|
||||||
import XList from "@/components/MkDateSeparatedList.vue";
|
import XList from "@/components/MkDateSeparatedList.vue";
|
||||||
import XNote from "@/components/MagNote.vue";
|
import XNote from "@/components/MagNote.vue";
|
||||||
|
import MagStreamStatus from "@/components/MagStreamStatus.vue";
|
||||||
import { magStream, stream } from "@/stream";
|
import { magStream, stream } from "@/stream";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
@ -153,6 +158,7 @@ const onNotification = (notification: packed.PackNotification) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
let notifStream: ((e: ChannelEvent) => void) | undefined;
|
let notifStream: ((e: ChannelEvent) => void) | undefined;
|
||||||
|
|
||||||
let connection: Connection<Channels["main"]>;
|
let connection: Connection<Channels["main"]>;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="mag-connection-status"
|
||||||
|
v-if="state !== 'connected' || showConnected"
|
||||||
|
:class="state"
|
||||||
|
>
|
||||||
|
<span v-if="state === 'connected'">
|
||||||
|
{{ i18n.t("connectionStatus.connected") }}
|
||||||
|
</span>
|
||||||
|
<span v-if="state === 'exponentialBackoff' || state === 'initial'">
|
||||||
|
{{ i18n.t("connectionStatus.reconnecting") }}
|
||||||
|
<MkEllipsis />
|
||||||
|
</span>
|
||||||
|
<span v-else-if="state === 'failed' || state === 'closed'">
|
||||||
|
{{ i18n.t("connectionStatus.disconnected") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { i18n } from "@/i18n";
|
||||||
|
import { magStream } from "@/stream";
|
||||||
|
import { MagChannelState } from "magnetar-common";
|
||||||
|
import { onMounted, onUnmounted, ref } from "vue";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
showConnected?: boolean
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const state = ref<MagChannelState>(magStream.state);
|
||||||
|
let stateListener: (e: MagChannelState) => void = (e) => {
|
||||||
|
if (
|
||||||
|
state.value !== "initial" &&
|
||||||
|
state.value !== "connected" &&
|
||||||
|
e === "connected"
|
||||||
|
) {
|
||||||
|
emit("reconnect");
|
||||||
|
}
|
||||||
|
|
||||||
|
state.value = e;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
magStream.on("stateChange", stateListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
magStream.off("stateChange", stateListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "reconnect"): void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mag-connection-status {
|
||||||
|
padding: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--infoBg);
|
||||||
|
color: var(--infoFg);
|
||||||
|
|
||||||
|
// Experimental
|
||||||
|
position: sticky;
|
||||||
|
z-index: 5;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
&.closed, &.failed {
|
||||||
|
background-color: var(--infoWarnBg);
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.exponentialBackoff {
|
||||||
|
background-color: var(--infoWarnBg);
|
||||||
|
color: var(--infoWarnFg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2199,3 +2199,8 @@ _experiments:
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "Max characters exceeded! Current: {current}/Limit: {max}"
|
charactersExceeded: "Max characters exceeded! Current: {current}/Limit: {max}"
|
||||||
charactersBelow: "Not enough characters! Current: {current}/Limit: {min}"
|
charactersBelow: "Not enough characters! Current: {current}/Limit: {min}"
|
||||||
|
|
||||||
|
connectionStatus:
|
||||||
|
connected: "Connected"
|
||||||
|
reconnecting: "Reconnecting"
|
||||||
|
disconnected: "Disconnected from the server"
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
FrontendApiEndpoints,
|
FrontendApiEndpoints,
|
||||||
} from "./fe-api";
|
} from "./fe-api";
|
||||||
|
|
||||||
import { MagEventChannel } from "./sse-listener";
|
import { MagEventChannel, MagChannelState } from "./sse-listener";
|
||||||
|
|
||||||
import * as types from "./types";
|
import * as types from "./types";
|
||||||
import * as packed from "./packed";
|
import * as packed from "./packed";
|
||||||
|
@ -43,6 +43,7 @@ export {
|
||||||
FrontendApiEndpoint,
|
FrontendApiEndpoint,
|
||||||
FrontendApiEndpoints,
|
FrontendApiEndpoints,
|
||||||
MagEventChannel,
|
MagEventChannel,
|
||||||
|
MagChannelState,
|
||||||
types,
|
types,
|
||||||
packed,
|
packed,
|
||||||
endpoints,
|
endpoints,
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { EventEmitter } from "eventemitter3";
|
import { EventEmitter } from "eventemitter3";
|
||||||
import { ChannelEvent } from "./types/ChannelEvent";
|
import { ChannelEvent } from "./types/ChannelEvent";
|
||||||
|
|
||||||
export type MagChannelState = "connected" | "exponentialBackoff" | "failed";
|
export type MagChannelState =
|
||||||
|
| "initial"
|
||||||
|
| "connected"
|
||||||
|
| "exponentialBackoff"
|
||||||
|
| "failed"
|
||||||
|
| "closed";
|
||||||
|
|
||||||
export class MagEventChannel extends EventEmitter<{
|
export class MagEventChannel extends EventEmitter<{
|
||||||
stateChange: MagChannelState;
|
stateChange: MagChannelState;
|
||||||
|
@ -15,11 +20,12 @@ export class MagEventChannel extends EventEmitter<{
|
||||||
private readonly backoffFactor: number;
|
private readonly backoffFactor: number;
|
||||||
private readonly backoffBase: number;
|
private readonly backoffBase: number;
|
||||||
private readonly closePromise: Promise<"cancelled">;
|
private readonly closePromise: Promise<"cancelled">;
|
||||||
|
private _state: MagChannelState;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
token: string | null,
|
token: string | null,
|
||||||
maxReconnectAttempts: number = 12,
|
maxReconnectAttempts: number = 15,
|
||||||
backoffFactor: number = 1.618,
|
backoffFactor: number = 1.618,
|
||||||
backoffBase: number = 500.0
|
backoffBase: number = 500.0
|
||||||
) {
|
) {
|
||||||
|
@ -30,6 +36,8 @@ export class MagEventChannel extends EventEmitter<{
|
||||||
this.maxAttempts = maxReconnectAttempts;
|
this.maxAttempts = maxReconnectAttempts;
|
||||||
this.backoffFactor = backoffFactor;
|
this.backoffFactor = backoffFactor;
|
||||||
this.backoffBase = backoffBase;
|
this.backoffBase = backoffBase;
|
||||||
|
this._state = "initial";
|
||||||
|
this.updateState("initial");
|
||||||
this.closePromise = new Promise((resolve) => {
|
this.closePromise = new Promise((resolve) => {
|
||||||
this.on("close", resolve);
|
this.on("close", resolve);
|
||||||
});
|
});
|
||||||
|
@ -49,9 +57,18 @@ export class MagEventChannel extends EventEmitter<{
|
||||||
return cb;
|
return cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get state(): MagChannelState {
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateState(state: MagChannelState) {
|
||||||
|
this._state = state;
|
||||||
|
this.emit("stateChange", state);
|
||||||
|
}
|
||||||
|
|
||||||
private async connect() {
|
private async connect() {
|
||||||
if (this.attempts >= this.maxAttempts) {
|
if (this.attempts >= this.maxAttempts) {
|
||||||
this.emit("stateChange", "failed");
|
this.updateState("failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +90,7 @@ export class MagEventChannel extends EventEmitter<{
|
||||||
response.status >= 500 ||
|
response.status >= 500 ||
|
||||||
response.body === null
|
response.body === null
|
||||||
) {
|
) {
|
||||||
this.emit("stateChange", "exponentialBackoff");
|
this.updateState("exponentialBackoff");
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => this.connect(),
|
() => this.connect(),
|
||||||
this.backoffBase * Math.pow(this.backoffFactor, this.attempts)
|
this.backoffBase * Math.pow(this.backoffFactor, this.attempts)
|
||||||
|
@ -83,7 +100,7 @@ export class MagEventChannel extends EventEmitter<{
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status >= 400 && response.status < 500) {
|
if (response.status >= 400 && response.status < 500) {
|
||||||
this.emit("stateChange", "failed");
|
this.updateState("failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +108,7 @@ export class MagEventChannel extends EventEmitter<{
|
||||||
|
|
||||||
const decoderStream = new TextDecoderStream();
|
const decoderStream = new TextDecoderStream();
|
||||||
const reader = response.body.pipeThrough(decoderStream).getReader();
|
const reader = response.body.pipeThrough(decoderStream).getReader();
|
||||||
this.emit("stateChange", "connected");
|
this.updateState("connected");
|
||||||
|
|
||||||
let buf = "";
|
let buf = "";
|
||||||
|
|
||||||
|
@ -101,11 +118,11 @@ export class MagEventChannel extends EventEmitter<{
|
||||||
if (res === "cancelled") break;
|
if (res === "cancelled") break;
|
||||||
|
|
||||||
if (res.done) {
|
if (res.done) {
|
||||||
this.emit("stateChange", "exponentialBackoff");
|
this.updateState("exponentialBackoff");
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => this.connect(),
|
() => this.connect(),
|
||||||
this.backoffBase *
|
this.backoffBase *
|
||||||
Math.pow(this.backoffFactor, this.attempts)
|
Math.pow(this.backoffFactor, this.attempts)
|
||||||
);
|
);
|
||||||
this.attempts++;
|
this.attempts++;
|
||||||
break;
|
break;
|
||||||
|
@ -134,6 +151,8 @@ export class MagEventChannel extends EventEmitter<{
|
||||||
const data = JSON.parse(text) as ChannelEvent;
|
const data = JSON.parse(text) as ChannelEvent;
|
||||||
this.emit("message", data);
|
this.emit("message", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateState("closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async close() {
|
public async close() {
|
||||||
|
|
|
@ -1438,8 +1438,8 @@ packages:
|
||||||
'@types/node': 20.8.10
|
'@types/node': 20.8.10
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/yauzl@2.10.2:
|
/@types/yauzl@2.10.3:
|
||||||
resolution: {integrity: sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==}
|
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 14.18.63
|
'@types/node': 14.18.63
|
||||||
|
@ -2705,8 +2705,8 @@ packages:
|
||||||
is-plain-object: 5.0.0
|
is-plain-object: 5.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/core-js@3.33.2:
|
/core-js@3.36.1:
|
||||||
resolution: {integrity: sha512-XeBzWI6QL3nJQiHmdzbAOiMYqjrb7hwU7A39Qhvd/POSa/t9E1AeZyEZx3fNvp/vtM8zXwhoL0FsiS0hD0pruQ==}
|
resolution: {integrity: sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -3428,7 +3428,7 @@ packages:
|
||||||
get-stream: 5.2.0
|
get-stream: 5.2.0
|
||||||
yauzl: 2.10.0
|
yauzl: 2.10.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/yauzl': 2.10.2
|
'@types/yauzl': 2.10.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -7733,7 +7733,7 @@ packages:
|
||||||
name: plyr
|
name: plyr
|
||||||
version: 3.7.0
|
version: 3.7.0
|
||||||
dependencies:
|
dependencies:
|
||||||
core-js: 3.33.2
|
core-js: 3.36.1
|
||||||
custom-event-polyfill: 1.0.7
|
custom-event-polyfill: 1.0.7
|
||||||
loadjs: 4.2.0
|
loadjs: 4.2.0
|
||||||
rangetouch: 2.0.1
|
rangetouch: 2.0.1
|
||||||
|
|
Loading…
Reference in New Issue