Skip to content

Commit 9c32cbf

Browse files
authored
feat: push-based hearthbeats (#712)
1 parent bf3085a commit 9c32cbf

File tree

51 files changed

+2237
-67
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2237
-67
lines changed

.github/labeler.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ webhook-listener:
1414
- changed-files:
1515
- any-glob-to-any-file: server/webhook-listener/**
1616

17+
helios-status-spring-starter:
18+
- changed-files:
19+
- any-glob-to-any-file: server/helios-status-spring-starter/**
20+
1721
client:
1822
- changed-files:
1923
- any-glob-to-any-file: client/**

.github/workflows/release-maven.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Release Helios starter to GitHub Packages and Maven Central
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
releaseversion:
7+
description: 'Release version'
8+
required: true
9+
default: '0.0.1-SNAPSHOT'
10+
11+
env:
12+
VERSION: ${{ github.event.inputs.releaseversion }}
13+
14+
jobs:
15+
publish:
16+
runs-on: ubuntu-latest
17+
environment: publish-maven
18+
19+
permissions:
20+
contents: read
21+
packages: write
22+
23+
steps:
24+
- run: echo "Will start a Maven-Central/GitHub-Actions upload with version ${{ github.event.inputs.releaseversion }}"
25+
- uses: actions/checkout@v4
26+
- name: Set up JDK 21
27+
uses: actions/setup-java@v4
28+
with:
29+
distribution: 'temurin'
30+
java-version: '21'
31+
- name: Set project's Maven version
32+
run: mvn versions:set "-DnewVersion=${{ github.event.inputs.releaseversion }}" --no-transfer-progress
33+
- name: Build with Maven
34+
run: mvn -B clean package -DskipTests=true
35+
# https://github.com/ls1intum/Helios/packages
36+
- name: Publish to GitHub Packages Apache Maven
37+
run: mvn deploy -DskipTests=true
38+
env:
39+
# GITHUB_TOKEN is the default env for the password
40+
GITHUB_TOKEN: ${{ github.token }}
41+
42+
- name: Set up Apache Maven Central
43+
uses: actions/setup-java@v4
44+
with:
45+
distribution: 'temurin'
46+
java-version: '21'
47+
server-id: central
48+
server-username: MAVEN_USERNAME
49+
server-password: MAVEN_PASSWORD
50+
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
51+
gpg-passphrase: MAVEN_GPG_PASSPHRASE
52+
cache: 'maven'
53+
54+
# https://central.sonatype.com/publishing/deployments
55+
- name: Publish package
56+
run: mvn -B deploy -Pcentral-deploy -DskipTests=true
57+
env:
58+
MAVEN_USERNAME: ${{ secrets.OSS_SONATYPE_USERNAME }}
59+
MAVEN_PASSWORD: ${{ secrets.OSS_SONATYPE_PASSWORD }}
60+
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
61+

client/src/app/components/environments/environment-accordion/environment-accordion.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
<!-- Direct use of deployment stepper -->
115115
<app-deployment-stepper [deployment]="environment().latestDeployment" class="w-full" />
116116
} @else {
117-
<app-environment-details [environment]="environment()"></app-environment-details>
117+
<app-environment-details [environment]="environment()" class="w-full" />
118118
}
119119
</div>
120120

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
<div class="flex flex-col gap-1">
2-
@if (environment().latestDeployment; as deployment) {
3-
<app-environment-deployment-info class="max-w-2xl" [deployment]="deployment" [repositoryId]="environment().repository?.id || 0" />
4-
}
5-
</div>
6-
<div class="flex-grow"></div>
7-
<div class="flex flex-col gap-1">
8-
@if (environment().latestStatus; as status) {
9-
<app-environment-status-info class="max-w-xs mt-2 ml-14" [status]="status" />
10-
}
1+
<div class="flex w-full items-start">
2+
<div class="flex flex-col gap-1">
3+
@if (environment().latestDeployment; as deployment) {
4+
<app-environment-deployment-info class="max-w-2xl" [deployment]="deployment" [repositoryId]="environment().repository?.id || 0" />
5+
}
6+
</div>
7+
<div class="flex-grow"></div>
8+
<div class="flex flex-col gap-1">
9+
@if (environment().latestStatus; as status) {
10+
<app-environment-status-info class="max-w-xs mt-2 ml-14" [status]="status" />
11+
}
12+
</div>
1113
</div>
Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,54 @@
11
<div class="flex flex-col">
2-
<span class="text-xs uppercase tracking-tighter font-bold text-muted-color mb-2">Latest status check</span>
2+
@if (status().checkType === 'PUSH_UPDATE') {
3+
<span class="text-xs uppercase tracking-tighter font-bold text-muted-color mb-2">Latest status update</span>
34

4-
<div class="flex items-center justify-between gap-1">
5-
<span class="text-sm font-medium text-surface-700 dark:text-surface-300">Last Checked:</span>
6-
<span class="text-sm text-muted-color">
7-
{{ timeSinceChecked() }}
8-
</span>
9-
</div>
10-
<div class="flex items-center justify-between mt-1 gap-1">
11-
<span class="text-sm font-medium text-surface-700 dark:text-surface-300">Status Code:</span>
12-
<span class="text-sm text-muted-color">
13-
{{ status().httpStatusCode || 'N/A' }}
14-
</span>
15-
</div>
5+
<div class="flex items-center justify-between mt-1 gap-1">
6+
<span class="text-sm font-medium text-surface-700 dark:text-surface-300">State:</span>
7+
<span class="text-sm text-muted-color">
8+
{{ status().state || 'N/A' }}
9+
</span>
10+
</div>
1611

17-
@if (status().checkType === 'ARTEMIS_INFO') {
18-
<span class="text-xs uppercase tracking-tighter text-muted-color mt-3">Artemis Build</span>
19-
@for (item of artemisBuildInfo(); track item.label) {
12+
<div class="flex items-center justify-between gap-1">
13+
<span class="text-sm font-medium text-surface-700 dark:text-surface-300">Last Updated:</span>
14+
<span class="text-sm text-muted-color">
15+
{{ timeSinceChecked() }}
16+
</span>
17+
</div>
18+
19+
@if (Object.keys(flattenedMetadata()).length > 0) {
20+
<span class="text-xs uppercase tracking-tighter text-muted-color mt-3">Additional metadata</span>
21+
}
22+
@for (item of flattenedMetadata() | keyvalue; track item.key) {
2023
<div class="flex items-center justify-between mt-1 gap-1">
21-
<span class="text-sm font-medium text-surface-700 dark:text-surface-300">{{ item.label }}:</span>
22-
<span class="text-sm text-muted-color">{{ item.value || '-/-' }}</span>
24+
<span class="text-sm font-medium text-surface-700 dark:text-surface-300">{{ item.key }}:</span>
25+
<span class="text-sm text-muted-color">{{ item.value }}</span>
2326
</div>
2427
}
28+
} @else {
29+
<span class="text-xs uppercase tracking-tighter font-bold text-muted-color mb-2">Latest status check</span>
30+
<div class="flex items-center justify-between gap-1">
31+
<span class="text-sm font-medium text-surface-700 dark:text-surface-300">Last Checked:</span>
32+
<span class="text-sm text-muted-color">
33+
{{ timeSinceChecked() }}
34+
</span>
35+
</div>
36+
37+
<div class="flex items-center justify-between mt-1 gap-1">
38+
<span class="text-sm font-medium text-surface-700 dark:text-surface-300">Status Code:</span>
39+
<span class="text-sm text-muted-color">
40+
{{ status().httpStatusCode || 'N/A' }}
41+
</span>
42+
</div>
43+
44+
@if (status().checkType === 'ARTEMIS_INFO') {
45+
<span class="text-xs uppercase tracking-tighter text-muted-color mt-3">Artemis Build</span>
46+
@for (item of artemisBuildInfo(); track item.label) {
47+
<div class="flex items-center justify-between mt-1 gap-1">
48+
<span class="text-sm font-medium text-surface-700 dark:text-surface-300">{{ item.label }}:</span>
49+
<span class="text-sm text-muted-color">{{ item.value || '-/-' }}</span>
50+
</div>
51+
}
52+
}
2553
}
2654
</div>

client/src/app/components/environments/environment-status-info/environment-status-info.component.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ import { Component, computed, inject, input, OnDestroy, OnInit, signal } from '@
22
import { EnvironmentStatusDto } from '@app/core/modules/openapi';
33
import { DateService } from '@app/core/services/date.service';
44
import { TimeAgoPipe } from '@app/pipes/time-ago.pipe';
5+
import { KeyValuePipe } from '@angular/common';
6+
7+
type FlatMetadata = Record<string, string>;
58

69
@Component({
710
selector: 'app-environment-status-info',
811
providers: [TimeAgoPipe],
912
templateUrl: './environment-status-info.component.html',
13+
imports: [KeyValuePipe],
1014
})
1115
export class EnvironmentStatusInfoComponent implements OnInit, OnDestroy {
16+
protected readonly Object = Object;
17+
1218
timeAgoPipe = inject(TimeAgoPipe);
1319

1420
status = input.required<EnvironmentStatusDto>();
@@ -65,6 +71,36 @@ export class EnvironmentStatusInfoComponent implements OnInit, OnDestroy {
6571
];
6672
});
6773

74+
flattenedMetadata = computed<FlatMetadata>(() => {
75+
const meta = this.status().metadata;
76+
if (!meta) {
77+
return {};
78+
}
79+
80+
const result: FlatMetadata = {};
81+
82+
for (const [outerKey, outerVal] of Object.entries(meta)) {
83+
result[outerKey] = this.stringify(outerVal);
84+
}
85+
86+
return result;
87+
});
88+
89+
/** turn any primitive or JSON‑able value into a string */
90+
stringify(value: unknown): string {
91+
switch (typeof value) {
92+
case 'string':
93+
return value;
94+
case 'number':
95+
case 'boolean':
96+
case 'bigint':
97+
return String(value);
98+
default:
99+
// fallback for objects / arrays / null / undefined
100+
return JSON.stringify(value);
101+
}
102+
}
103+
68104
ngOnInit() {
69105
// Update timeNow every second
70106
this.intervalId = setInterval(() => {
Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,33 @@
11
@if (status(); as status) {
2-
<!-- Info State -->
3-
<!-- Success State -->
4-
@if (status.success) {
5-
<p-tag severity="success" [rounded]="true">
6-
<i-tabler name="check" class="!h-4 !w-4 mr-0.5"></i-tabler>
7-
Server Status
2+
@if (status.checkType === 'PUSH_UPDATE') {
3+
<p-tag [rounded]="true" [severity]="lifecycleView().severity" class="inline-flex items-center gap-x-1">
4+
<!-- lifecycle icon -->
5+
@if (lifecycleView().icon !== undefined) {
6+
<i-tabler [name]="lifecycleView().icon || 'info'" class="!h-4 !w-4" [class.animate-spin]="lifecycleView().spin" />
7+
}
8+
9+
<!-- human label -->
10+
{{ lifecycleView().label }}
11+
12+
<!-- stale red warning icon with tooltip -->
13+
@if (isStale()) {
14+
<i-tabler name="alert-triangle" class="!h-4 !w-4" style="color: #ff0700" pTooltip="Latest update was {{ status.checkedAt | timeAgo }} – Server might be down"></i-tabler>
15+
}
816
</p-tag>
917
} @else {
10-
<!-- Error State -->
11-
<p-tag severity="danger" [rounded]="true">
12-
<i-tabler name="exclamation-circle" class="!h-4 !w-4 mr-0.5"></i-tabler>
13-
Server Status
14-
</p-tag>
18+
<!-- Info State -->
19+
<!-- Success State -->
20+
@if (status.success) {
21+
<p-tag severity="success" [rounded]="true">
22+
<i-tabler name="activity" class="!h-4 !w-4 mr-0.5"></i-tabler>
23+
Running
24+
</p-tag>
25+
} @else {
26+
<!-- Error State -->
27+
<p-tag severity="danger" [rounded]="true">
28+
<i-tabler name="exclamation-circle" class="!h-4 !w-4 mr-0.5"></i-tabler>
29+
Down
30+
</p-tag>
31+
}
1532
}
1633
}

0 commit comments

Comments
 (0)