feat(portal): add notifications sidebar (#2516)

* feat(portal): add notifications sidebar

* chore: add service

* chore: simplify style

* chore: fix comments

* WIP, moving notifications to patch-db

* revamp notifications

* chore: small adjustments

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
Alex Inkin
2023-12-08 20:12:03 +04:00
committed by GitHub
parent 8bc93d23b2
commit 7324a4973f
52 changed files with 1181 additions and 255 deletions

View File

@@ -339,6 +339,7 @@ export module Mock {
},
},
},
read: false,
},
{
id: 2,
@@ -349,6 +350,7 @@ export module Mock {
title: 'SSH Key Added',
message: 'A new SSH key was added. If you did not do this, shit is bad.',
data: null,
read: false,
},
{
id: 3,
@@ -359,6 +361,7 @@ export module Mock {
title: 'SSH Key Removed',
message: 'A SSH key was removed.',
data: null,
read: false,
},
{
id: 4,
@@ -367,7 +370,7 @@ export module Mock {
code: 4,
level: NotificationLevel.Error,
title: 'Service Crashed',
message: new Array(40)
message: new Array(3)
.fill(
`2021-11-27T18:36:30.451064Z 2021-11-27T18:36:30Z tor: Thread interrupt
2021-11-27T18:36:30.452833Z 2021-11-27T18:36:30Z Shutdown: In progress...
@@ -376,6 +379,7 @@ export module Mock {
)
.join(''),
data: null,
read: false,
},
]

View File

@@ -9,7 +9,13 @@ import {
ServiceOutboundProxy,
HealthCheckResult,
} from 'src/app/services/patch-db/data-model'
import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared'
import {
StartOSDiskInfo,
FetchLogsReq,
FetchLogsRes,
FollowLogsRes,
FollowLogsReq,
} from '@start9labs/shared'
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
export module RR {
@@ -50,14 +56,11 @@ export module RR {
uptime: number // seconds
}
export type GetServerLogsReq = ServerLogsReq // server.logs & server.kernel-logs
export type GetServerLogsRes = LogsRes
export type GetServerLogsReq = FetchLogsReq // server.logs & server.kernel-logs & server.tor-logs
export type GetServerLogsRes = FetchLogsRes
export type FollowServerLogsReq = { limit?: number } // server.logs.follow & server.kernel-logs.follow
export type FollowServerLogsRes = {
'start-cursor': string
guid: string
}
export type FollowServerLogsReq = FollowLogsReq & { limit?: number } // server.logs.follow & server.kernel-logs.follow & server.tor-logs.follow
export type FollowServerLogsRes = FollowLogsRes
export type GetServerMetricsReq = {} // server.metrics
export type GetServerMetricsRes = {
@@ -109,17 +112,29 @@ export module RR {
// notification
export type FollowNotificationsReq = {}
export type FollowNotificationsRes = {
notifications: ServerNotifications
guid: string
}
export type GetNotificationsReq = {
before?: number
limit?: number
} // notification.list
export type GetNotificationsRes = ServerNotification<number>[]
export type DeleteNotificationReq = { id: number } // notification.delete
export type DeleteNotificationReq = { ids: number[] } // notification.delete
export type DeleteNotificationRes = null
export type DeleteAllNotificationsReq = { before: number } // notification.delete-before
export type DeleteAllNotificationsRes = null
export type MarkSeenNotificationReq = DeleteNotificationReq // notification.mark-seen
export type MarkSeenNotificationRes = null
export type MarkSeenAllNotificationsReq = { before: number } // notification.mark-seen-before
export type MarkSeenAllNotificationsRes = null
export type MarkUnseenNotificationReq = DeleteNotificationReq // notification.mark-unseen
export type MarkUnseenNotificationRes = null
// network
@@ -298,8 +313,8 @@ export module RR {
export type GetPackageCredentialsReq = { id: string } // package.credentials
export type GetPackageCredentialsRes = Record<string, string>
export type GetPackageLogsReq = ServerLogsReq & { id: string } // package.logs
export type GetPackageLogsRes = LogsRes
export type GetPackageLogsReq = FetchLogsReq & { id: string } // package.logs
export type GetPackageLogsRes = FetchLogsRes
export type FollowPackageLogsReq = FollowServerLogsReq & { id: string } // package.logs.follow
export type FollowPackageLogsRes = FollowServerLogsRes
@@ -562,7 +577,7 @@ export interface SSHKey {
fingerprint: string
}
export type ServerNotifications = ServerNotification<any>[]
export type ServerNotifications = ServerNotification<number>[]
export interface ServerNotification<T extends number> {
id: number
@@ -573,6 +588,7 @@ export interface ServerNotification<T extends number> {
title: string
message: string
data: NotificationData<T>
read: boolean
}
export enum NotificationLevel {

View File

@@ -125,13 +125,21 @@ export abstract class ApiService {
params: RR.GetNotificationsReq,
): Promise<RR.GetNotificationsRes>
abstract deleteNotification(
abstract markSeenNotifications(
params: RR.MarkSeenNotificationReq,
): Promise<RR.MarkSeenNotificationRes>
abstract markSeenAllNotifications(
params: RR.MarkSeenAllNotificationsReq,
): Promise<RR.MarkSeenAllNotificationsRes>
abstract markUnseenNotifications(
params: RR.DeleteNotificationReq,
): Promise<RR.DeleteNotificationRes>
abstract deleteAllNotifications(
params: RR.DeleteAllNotificationsReq,
): Promise<RR.DeleteAllNotificationsRes>
abstract deleteNotifications(
params: RR.DeleteNotificationReq,
): Promise<RR.DeleteNotificationRes>
// network
@@ -308,8 +316,6 @@ export abstract class ApiService {
abstract getSetupStatus(): Promise<SetupStatus | null>
abstract followLogs(): Promise<string>
abstract setInterfaceClearnetAddress(
params: RR.SetInterfaceClearnetAddressReq,
): Promise<RR.SetInterfaceClearnetAddressRes>

View File

@@ -117,10 +117,6 @@ export class LiveApiService extends ApiService {
return this.openWebsocket(config)
}
async followLogs(): Promise<string> {
return this.rpcRequest({ method: 'setup.logs.follow', params: {} })
}
openLogsWebsocket$(config: WebSocketSubjectConfig<Log>): Observable<Log> {
return this.openWebsocket(config)
}
@@ -259,21 +255,33 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'notification.list', params })
}
async deleteNotification(
async deleteNotifications(
params: RR.DeleteNotificationReq,
): Promise<RR.DeleteNotificationRes> {
return this.rpcRequest({ method: 'notification.delete', params })
}
async deleteAllNotifications(
params: RR.DeleteAllNotificationsReq,
): Promise<RR.DeleteAllNotificationsRes> {
async markSeenNotifications(
params: RR.MarkSeenNotificationReq,
): Promise<RR.MarkSeenNotificationRes> {
return this.rpcRequest({ method: 'notification.mark-seen', params })
}
async markSeenAllNotifications(
params: RR.MarkSeenAllNotificationsReq,
): Promise<RR.MarkSeenAllNotificationsRes> {
return this.rpcRequest({
method: 'notification.delete-before',
method: 'notification.mark-seen-before',
params,
})
}
async markUnseenNotifications(
params: RR.MarkUnseenNotificationReq,
): Promise<RR.MarkUnseenNotificationRes> {
return this.rpcRequest({ method: 'notification.mark-unseen', params })
}
// network
async addProxy(params: RR.AddProxyReq): Promise<RR.AddProxyRes> {

View File

@@ -473,28 +473,34 @@ export class MockApiService extends ApiService {
params: RR.GetNotificationsReq,
): Promise<RR.GetNotificationsRes> {
await pauseFor(2000)
const patch = [
{
op: PatchOp.REPLACE,
path: '/server-info/unread-notification-count',
value: 0,
},
]
this.mockRevision(patch)
return Mock.Notifications
}
async deleteNotification(
async deleteNotifications(
params: RR.DeleteNotificationReq,
): Promise<RR.DeleteNotificationRes> {
await pauseFor(2000)
return null
}
async deleteAllNotifications(
params: RR.DeleteAllNotificationsReq,
): Promise<RR.DeleteAllNotificationsRes> {
async markSeenNotifications(
params: RR.MarkSeenNotificationReq,
): Promise<RR.MarkSeenNotificationRes> {
await pauseFor(2000)
return null
}
async markSeenAllNotifications(
params: RR.MarkSeenAllNotificationsReq,
): Promise<RR.MarkSeenAllNotificationsRes> {
await pauseFor(2000)
return null
}
async markUnseenNotifications(
params: RR.MarkUnseenNotificationReq,
): Promise<RR.MarkUnseenNotificationRes> {
await pauseFor(2000)
return null
}
@@ -1244,11 +1250,6 @@ export class MockApiService extends ApiService {
return getSetupStatusMock()
}
async followLogs(): Promise<string> {
await pauseFor(1000)
return 'fake-guid'
}
async setInterfaceClearnetAddress(
params: RR.SetInterfaceClearnetAddressReq,
): Promise<RR.SetInterfaceClearnetAddressRes> {

View File

@@ -88,7 +88,10 @@ export const mockPatchData: DataModel = {
outboundProxy: null,
},
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
'unread-notification-count': 4,
unreadNotifications: {
count: 4,
recent: Mock.Notifications,
},
'eos-version-compat': '>=0.3.0 <=0.3.0.1',
'status-info': {
'current-backup': null,

View File

@@ -1,7 +1,7 @@
import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes'
import { Url } from '@start9labs/shared'
import { Manifest } from '@start9labs/marketplace'
import { BackupJob } from '../api/api.types'
import { BackupJob, ServerNotifications } from '../api/api.types'
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
import { NetworkInterfaceType } from '@start9labs/start-sdk/lib/util/utils'
import { DependencyInfo } from 'src/app/apps/portal/routes/service/types/dependency-info'
@@ -61,7 +61,10 @@ export interface ServerInfo {
ui: AddressInfo
network: NetworkInfo
'last-backup': string | null
'unread-notification-count': number
unreadNotifications: {
count: number
recent: ServerNotifications
}
'status-info': ServerStatusInfo
'eos-version-compat': string
pubkey: string