Skip to content

Commit d2e93ee

Browse files
author
HaoDaWang
committed
[Feat] wrap websocket
1 parent d89a873 commit d2e93ee

15 files changed

+261
-15
lines changed

src/.config/server.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"host": "localhost",
2+
"host": "119.23.73.70",
33
"port": 9090
44
}

src/app.module.ts

+17-4
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ import {
1818
import { LoginCardComponent } from "./app/login-card/login-card.component";
1919
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
2020
import { ReactiveFormsModule } from "@angular/forms";
21-
import { HttpClientModule } from "@angular/common/http";
22-
import { HttpInfo } from "./services";
21+
import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http";
22+
import { HttpInfo, WebSocketService } from "./services";
2323
import { UserService } from "./services/user-service";
2424
import { PanelComponent } from "./app/panel/panel.component";
2525
import { EntryComponent } from "./app/entry/entry.component";
2626
import { MainCanActiveGuard } from "./guards/main/active";
27+
import { AuthInterceptor } from "./interceptors/auth-interceptor";
28+
import { ProjectName } from "./pipes/project-name";
2729

2830
@NgModule({
2931
declarations: [
@@ -32,7 +34,8 @@ import { MainCanActiveGuard } from "./guards/main/active";
3234
LoginCardComponent,
3335
PanelComponent,
3436
EntryComponent,
35-
PanelComponent
37+
PanelComponent,
38+
ProjectName
3639
],
3740
imports: [
3841
BrowserModule,
@@ -50,7 +53,17 @@ import { MainCanActiveGuard } from "./guards/main/active";
5053
MatSnackBarModule,
5154
MatButtonToggleModule
5255
],
53-
providers: [HttpInfo, UserService, MainCanActiveGuard],
56+
providers: [
57+
HttpInfo,
58+
UserService,
59+
MainCanActiveGuard,
60+
WebSocketService,
61+
{
62+
provide: HTTP_INTERCEPTORS,
63+
useClass: AuthInterceptor,
64+
multi: true
65+
}
66+
],
5467
bootstrap: [AppComponent]
5568
})
5669
export class AppModule {}

src/app/panel/panel.component.html

+18-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
</mat-card>
2323
</mat-card-content>
2424
<mat-card-actions class="project-footer">
25-
<button mat-button mat-raised-button>部署</button>
25+
<button mat-button mat-raised-button (click)="handleDeployClick()">
26+
部署
27+
</button>
2628
</mat-card-actions>
2729
</mat-card>
2830
<mat-card class="task-card">
@@ -33,8 +35,12 @@
3335
<mat-card-subtitle>
3436
tasks pool
3537
</mat-card-subtitle>
36-
<mat-card-content>
37-
<div class="task-nothing">
38+
<mat-card-content class="task-content">
39+
<mat-card *ngFor="let task of tasks" class="task-item-card">
40+
<span>正在部署中...</span>
41+
<span>{{ task.projectName }}</span>
42+
</mat-card>
43+
<div class="task-nothing" *ngIf="tasks.length === 0">
3844
<mat-icon>
3945
desktop_windows
4046
</mat-icon>
@@ -75,7 +81,15 @@
7581
notify
7682
</mat-card-subtitle>
7783
<mat-card-content class="notify-content">
78-
<div class="notify-nothing">
84+
<mat-card
85+
*ngFor="let notify of notifications"
86+
[ngClass]="notify.type"
87+
class="notify-item-card"
88+
>
89+
<span>{{ notify.text }}</span>
90+
<span>{{ notify.projectName }} </span>
91+
</mat-card>
92+
<div class="notify-nothing" *ngIf="notifications.length === 0">
7993
<mat-icon>nights_stay</mat-icon>
8094
<span>通知不做持久化</span>
8195
</div>

src/app/panel/panel.component.scss

+32
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33
transition: all 0.3s;
44
}
55

6+
@mixin scroll-content {
7+
height: calc(100% - 80px);
8+
overflow-y: scroll;
9+
}
10+
11+
@mixin base-card {
12+
margin-bottom: 10px;
13+
display: flex;
14+
justify-content: space-between;
15+
}
16+
617
$full-height: calc(100% - 150px);
718

819
.selected {
@@ -58,6 +69,18 @@ $full-height: calc(100% - 150px);
5869

5970
height: $full-height;
6071

72+
.notify-item-card {
73+
@include base-card;
74+
}
75+
76+
.notify-content {
77+
@include scroll-content;
78+
79+
&::-webkit-scrollbar {
80+
width: 0 !important;
81+
}
82+
}
83+
6184
.notify-nothing {
6285
height: 500px;
6386
display: flex;
@@ -81,6 +104,15 @@ $full-height: calc(100% - 150px);
81104
align-items: center;
82105
justify-content: center;
83106
}
107+
108+
.task-content {
109+
@include scroll-content;
110+
111+
.task-item-card {
112+
@include base-card;
113+
@extend .selected;
114+
}
115+
}
84116
}
85117
}
86118

src/app/panel/panel.component.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { Component, OnInit } from "@angular/core";
2+
import { WebSocketService, HttpInfo } from "src/services";
3+
import { HttpClient } from "@angular/common/http";
24

35
type LogType = "docker" | "dispatcher";
46

@@ -33,7 +35,11 @@ export class PanelComponent implements OnInit {
3335
selectedProject: Project = this.projects[0];
3436
selectedLogType: LogType = "dispatcher";
3537

36-
constructor() {}
38+
constructor(
39+
private websocketService: WebSocketService,
40+
private httpClient: HttpClient,
41+
private httpInfo: HttpInfo
42+
) {}
3743

3844
ngOnInit() {}
3945

@@ -44,4 +50,20 @@ export class PanelComponent implements OnInit {
4450
handleLogTypeChange(logType: LogType) {
4551
this.selectedLogType = logType;
4652
}
53+
54+
handleDeployClick() {
55+
this.httpClient
56+
.put(this.httpInfo.path(`/deploy/${this.selectedProject.type}`), {})
57+
.subscribe(data => console.info(data));
58+
}
59+
60+
get notifications() {
61+
return this.websocketService.notifications.filter(
62+
({ projectName }) => projectName === this.selectedProject.name
63+
);
64+
}
65+
66+
get tasks() {
67+
return this.websocketService.tasks;
68+
}
4769
}

src/dtos/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./login";
22
export * from "./common";
3+
export * from "./types";

src/dtos/types.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export const enum EventType {
2+
LOGGER,
3+
NOTIFY,
4+
TASK
5+
}
6+
7+
export const enum MessageType {
8+
ERROR,
9+
INFO,
10+
SUCCESS,
11+
WARNING
12+
}
13+
14+
export const enum LoggerType {
15+
DOCKER,
16+
DISPATCHER
17+
}

src/interceptors/auth-interceptor.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { HttpInterceptor } from "@angular/common/http";
2+
import { HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
3+
import { Observable } from "rxjs";
4+
import { Injectable } from "@angular/core";
5+
import { UserService } from "src/services";
6+
7+
@Injectable()
8+
export class AuthInterceptor implements HttpInterceptor {
9+
constructor(private userService: UserService) {}
10+
11+
intercept(
12+
req: HttpRequest<any>,
13+
next: HttpHandler
14+
): Observable<HttpEvent<any>> {
15+
console.info("aaaaa");
16+
17+
return next.handle(
18+
req.clone({
19+
headers: req.headers.set("Authentication", this.userService.token)
20+
})
21+
);
22+
}
23+
}

src/pipes/project-name.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PipeTransform, Pipe } from "@angular/core";
2+
3+
interface Nameable {
4+
projectName: string;
5+
}
6+
7+
@Pipe({ name: "projectName" })
8+
export class ProjectName implements PipeTransform {
9+
transform(value: Nameable[], name: string) {
10+
return value.filter(({ projectName }) => projectName === name);
11+
}
12+
}

src/services/http-info.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import HTTP_INFO from "../.config/server.json";
33

44
@Injectable()
55
export class HttpInfo {
6-
get baseName(): string {
6+
baseName(protocol?: string): string {
77
const { host, port } = HTTP_INFO;
88

9-
return `http://${host}:${port}`;
9+
return `${protocol || "http"}://${host}:${port}`;
1010
}
1111

12-
path(path: string): string {
13-
return `${this.baseName}${path}`;
12+
path(path: string, protocol?: string): string {
13+
return `${this.baseName(protocol)}${path}`;
1414
}
1515
}

src/services/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export * from "./http-info";
22
export * from "./user-service";
3+
export * from "./websocket";
4+
export * from "./logger";

src/services/logger.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { Injectable } from "@angular/core";
2+
3+
@Injectable()
4+
export class Logger {}

src/services/user-service.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ export class UserService {
99
constructor(private httpClient: HttpClient, private httpInfo: HttpInfo) {}
1010

1111
get isLogin(): boolean {
12-
return !!localStorage.getItem("jwt");
12+
return !!this.token;
13+
}
14+
15+
get token(): string | null {
16+
return localStorage.getItem("jwt");
1317
}
1418

1519
login(data: LoginPayload) {

src/services/websocket.ts

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Injectable } from "@angular/core";
2+
import { HttpInfo } from "./http-info";
3+
import { LoggerType, EventType, CommonResponse } from "../dtos";
4+
import { HttpClient } from "@angular/common/http";
5+
6+
export interface NotifyPayload {
7+
logType: number;
8+
projectName: string;
9+
text: string;
10+
type: number;
11+
}
12+
13+
export interface LoggerPayload {
14+
type: LoggerType;
15+
content: string;
16+
}
17+
18+
export interface TaskPayload {
19+
name: string;
20+
time: number;
21+
projectName: string;
22+
}
23+
24+
const NOTIFY_CLASS_TYPE = [
25+
"notify-error",
26+
"notify-info",
27+
"notify-success",
28+
"notify-warning"
29+
];
30+
31+
@Injectable()
32+
export class WebSocketService {
33+
private conn: WebSocket;
34+
35+
notifications: NotifyPayload[] = [];
36+
loggers: LoggerPayload[] = [];
37+
tasks: TaskPayload[] = [];
38+
39+
constructor(private httpInfo: HttpInfo, private httpClient: HttpClient) {
40+
const conn = new WebSocket(this.httpInfo.path("/notify/", "ws"));
41+
httpClient
42+
.get<CommonResponse<TaskPayload[]>>(this.httpInfo.path("/task/all"))
43+
.subscribe(({ data }) => (this.tasks = data));
44+
45+
this.conn = conn;
46+
conn.onopen = this.handleConnect;
47+
conn.onclose = this.handleClose;
48+
conn.onmessage = ws => this.handleMessage(ws) as any;
49+
}
50+
51+
private handleConnect() {
52+
console.info("connect success");
53+
}
54+
55+
private handleClose() {
56+
console.info("closed");
57+
}
58+
59+
private handleMessage(ws: MessageEvent) {
60+
const { type, payload } = JSON.parse(ws.data);
61+
62+
if (type === EventType.TASK) {
63+
this.tasks = payload;
64+
65+
return;
66+
}
67+
68+
const CONTAINER_MAP = {
69+
[EventType.LOGGER]: this.loggers,
70+
[EventType.NOTIFY]: this.notifications
71+
};
72+
const container = CONTAINER_MAP[type];
73+
let result = payload;
74+
75+
if (type === EventType.NOTIFY) {
76+
result.type = NOTIFY_CLASS_TYPE[payload.type];
77+
}
78+
79+
container.push(result);
80+
}
81+
}

src/styles.scss

+21
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,24 @@ html {
1313
.success-snackbar {
1414
background: #3f51b5;
1515
}
16+
17+
.notify-error {
18+
background: #c0392b;
19+
color: white;
20+
// text-shadow: 1px 1px 0 black;
21+
}
22+
23+
.notify-info {
24+
background: #2980b9;
25+
color: white;
26+
}
27+
28+
.notify-success {
29+
background: #27ae60;
30+
color: white;
31+
}
32+
33+
.notify-warning {
34+
background: #f39c12;
35+
color: white;
36+
}

0 commit comments

Comments
 (0)