[feat] upgrade 1.10

1. view version in about modal
2. kubernets deployment
main
Johnson C 2 years ago
parent e8e51a5707
commit 99aa119756

@ -50,7 +50,19 @@ fileb0x b0x.yaml
## Docker
```
docker run -d --name "go-micro-dashboard" -p "4000:4000" xpunch/go-micro-dashboard
docker run -d --name micro-dashboard -p 8082:8082 xpunch/go-micro-dashboard:latest
```
## Docker Compose
```
docker-compose -f docker-compose.yml up -d
```
## Kubernetes
```
kubectl apply -f deployment.yaml
```
### Community

@ -2,6 +2,11 @@ package config
import "time"
const (
Name = "go.micro.dashboard"
Version = "1.1.0"
)
const (
EnvDev = "dev"
EnvProd = "prod"

@ -21,7 +21,7 @@ import (
var _cfg *Config = &Config{
Server: ServerConfig{
Env: EnvProd,
Address: ":80",
Address: ":8082",
Auth: AuthConfig{
Username: "admin",
Password: "123456",
@ -29,7 +29,7 @@ var _cfg *Config = &Config{
TokenExpiration: 24 * time.Hour,
},
Swagger: SwaggerConfig{
Host: "localhost",
Host: "localhost:8082",
Base: "/",
},
},

@ -0,0 +1,75 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: micro
namespace: micro
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: micro-registry
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- patch
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: micro-registry
namespace: micro
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: micro-registry
subjects:
- kind: ServiceAccount
name: micro
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: micro-dashboard
namespace: micro
labels:
app: micro-dashboard
spec:
replicas: 1
selector:
matchLabels:
app: micro-dashboard
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: micro-dashboard
spec:
containers:
- image: xpunch/go-micro-dashboard:latest
imagePullPolicy: IfNotPresent
name: micro-dashboard
ports:
- containerPort: 80
protocol: TCP
env:
- name: MICRO_REGISTRY
value: "kubernetes"
- name: MICRO_CLIENT_RETRIES
value: "0"
resources:
limits:
memory: 512Mi
cpu: "0.25"
requests:
memory: 512Mi
cpu: "0.25"
serviceAccountName: micro

@ -3,9 +3,9 @@ version: "3"
services:
dashboard:
image: xpunch/go-micro-dashboard:latest
container_name: go-micro-dashboard
container_name: micro-dashboard
ports:
- "4000:4000"
- "8082:8082"
environment:
- MICRO_REGISTRY=etcd
- MICRO_REGISTRY_ADDRESS=etcd

@ -448,6 +448,19 @@ var doc = `{
}
}
}
},
"/version": {
"get": {
"operationId": "getVersion",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object"
}
}
}
}
}
},
"definitions": {
@ -750,7 +763,7 @@ type swaggerInfo struct {
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = swaggerInfo{
Version: "1.0.0",
Version: "1.1.0",
Host: "",
BasePath: "/",
Schemes: []string{},

@ -5,7 +5,7 @@
"title": "Go Micro Dashboard",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"version": "1.0.0"
"version": "1.1.0"
},
"basePath": "/",
"paths": {
@ -432,6 +432,19 @@
}
}
}
},
"/version": {
"get": {
"operationId": "getVersion",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object"
}
}
}
}
}
},
"definitions": {

@ -187,7 +187,7 @@ info:
description: go micro dashboard restful-api
termsOfService: http://swagger.io/terms/
title: Go Micro Dashboard
version: 1.0.0
version: 1.1.0
paths:
/api/account/login:
post:
@ -454,6 +454,14 @@ paths:
- ApiKeyAuth: []
tags:
- Statistics
/version:
get:
operationId: getVersion
responses:
"200":
description: OK
schema:
type: object
securityDefinitions:
ApiKeyAuth:
in: header

@ -3,7 +3,7 @@
"defaultVariables": null,
"documentGenerator": {
"fromDocument": {
"url": "http://localhost:4000/swagger/doc.json",
"url": "http://localhost:8082/swagger/doc.json",
"output": null,
"newLineBehavior": "Auto"
}

@ -44,8 +44,7 @@ export class DefaultInterceptor implements HttpInterceptor {
console.log(ev);
if (ev instanceof HttpErrorResponse) {
this.blobToText(ev.error).subscribe((resp) => {
var msg = resp ? resp : ev.statusText;
this.notification.error('Error', `${ev.status}: ${ev.url} ${msg}`, { nzDuration: 5000 });
this.notification.error(ev.statusText, `${resp}\n${ev.url}`, { nzDuration: 15000 });
});
return throwError(ev);
}
@ -94,7 +93,7 @@ export class DefaultInterceptor implements HttpInterceptor {
};
reader.readAsText(blob);
} else {
observer.next(blob && blob.error ? blob.error : JSON.stringify(blob));
observer.next(blob && blob.error ? blob.error : '');
observer.complete();
}
});

@ -8,7 +8,7 @@ import { NzIconService } from 'ng-zorro-antd/icon';
import { ICONS } from '../../../style-icons';
import { ICONS_AUTO } from '../../../style-icons-auto';
import { AccountServiceProxy } from 'src/app/shared/service-proxies/service-proxies';
import { AccountServiceProxy, ServiceProxy } from 'src/app/shared/service-proxies/service-proxies';
/**
* Used for application startup
@ -22,7 +22,8 @@ export class StartupService {
private settingService: SettingsService,
private aclService: ACLService,
private titleService: TitleService,
private accountService: AccountServiceProxy
private accountService: AccountServiceProxy,
private readonly serviceProxy: ServiceProxy,
) {
iconSrv.addIcon(...ICONS_AUTO, ...ICONS);
}
@ -77,6 +78,9 @@ export class StartupService {
]
}
]);
this.serviceProxy.getVersion().subscribe(resp => {
this.settingService.setData('version', resp.version);
});
// Can be set page suffix title, https://ng-alain.com/theme/title
this.titleService.suffix = app.name;
return this.accountService.profile().pipe(

@ -11,12 +11,22 @@ import { SettingsService, User } from '@delon/theme';
</div>
<nz-dropdown-menu #userMenu="nzDropdownMenu">
<div nz-menu class="width-sm">
<div nz-menu-item (click)="about()">
<i nz-icon nzType="info-circle" class="mr-sm"></i>
About
</div>
<li nz-menu-divider></li>
<div nz-menu-item (click)="logout()">
<i nz-icon nzType="logout" class="mr-sm"></i>
Logout
</div>
</div>
</nz-dropdown-menu>
<nz-modal [(nzVisible)]="aboutVisible" nzTitle="About" nzCancelDisabled="true" (nzOnCancel)="aboutVisible=false" (nzOnOk)="aboutVisible=false">
<ng-container *nzModalContent>
<p>Version: {{version}}</p>
</ng-container>
</nz-modal>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
@ -24,9 +34,16 @@ export class HeaderUserComponent {
get user(): User {
return this.settings.user;
}
aboutVisible = false;
version: string = '';
constructor(private settings: SettingsService, private router: Router, @Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService) { }
about(): void {
this.aboutVisible = true;
this.version = this.settings.getData('version');
}
logout(): void {
this.tokenService.clear();
this.router.navigateByUrl(this.tokenService.login_url!);

@ -17,6 +17,7 @@ import { NzGridModule } from 'ng-zorro-antd/grid';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { NzInputModule } from 'ng-zorro-antd/input';
import { NzSpinModule } from 'ng-zorro-antd/spin';
import { NzModalModule } from 'ng-zorro-antd/modal';
import { LayoutBasicComponent } from './basic/basic.component';
import { HeaderClearStorageComponent } from './basic/widgets/clear-storage.component';
@ -59,6 +60,7 @@ const PASSPORT = [
NzBadgeModule,
NzAvatarModule,
NzIconModule,
NzModalModule,
],
declarations: [...COMPONENTS, ...HEADERCOMPONENTS, ...PASSPORT],
exports: [...COMPONENTS, ...PASSPORT],

@ -22,7 +22,7 @@ export class AccountServiceProxy {
constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "localhost:4000/";
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "localhost:8082/";
}
/**
@ -183,7 +183,7 @@ export class ClientServiceProxy {
constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "localhost:4000/";
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "localhost:8082/";
}
/**
@ -349,7 +349,7 @@ export class RegistryServiceProxy {
constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "localhost:4000/";
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "localhost:8082/";
}
/**
@ -673,7 +673,7 @@ export class StatisticsServiceProxy {
constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "localhost:4000/";
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "localhost:8082/";
}
/**
@ -749,6 +749,69 @@ export class StatisticsServiceProxy {
}
}
@Injectable()
export class ServiceProxy {
private http: HttpClient;
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "localhost:8082/";
}
/**
* @return OK
*/
getVersion() : Observable<any> {
let url_ = this.baseUrl + "/version";
url_ = url_.replace(/[?&]$/, "");
let options_ : any = {
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Accept": "application/json"
})
};
return this.http.request("get", url_, options_).pipe(_observableMergeMap((response_ : any) => {
return this.processGetVersion(response_);
})).pipe(_observableCatch((response_: any) => {
if (response_ instanceof HttpResponseBase) {
try {
return this.processGetVersion(<any>response_);
} catch (e) {
return <Observable<any>><any>_observableThrow(e);
}
} else
return <Observable<any>><any>_observableThrow(response_);
}));
}
protected processGetVersion(response: HttpResponseBase): Observable<any> {
const status = response.status;
const responseBlob =
response instanceof HttpResponse ? response.body :
(<any>response).error instanceof Blob ? (<any>response).error : undefined;
let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}
if (status === 200) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : <any>null;
return _observableOf(result200);
}));
} else if (status !== 200 && status !== 204) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}));
}
return _observableOf<any>(<any>null);
}
}
export class LoginRequest implements ILoginRequest {
password!: string;
username!: string;

@ -8,6 +8,7 @@ import * as ApiServiceProxies from './service-proxies';
ApiServiceProxies.ClientServiceProxy,
ApiServiceProxies.RegistryServiceProxy,
ApiServiceProxies.StatisticsServiceProxy,
ApiServiceProxies.ServiceProxy,
]
})
export class ServiceProxyModule {

@ -8,7 +8,7 @@ export const environment = {
production: false,
useHash: true,
api: {
baseUrl: 'http://localhost:4000'
baseUrl: 'http://localhost:8082'
}
} as Environment;

@ -53,6 +53,7 @@ import {
UserOutline,
WeiboCircleOutline,
ClearOutline,
InfoCircleOutline,
} from '@ant-design/icons-angular/icons';
export const ICONS_AUTO = [
@ -105,4 +106,5 @@ export const ICONS_AUTO = [
UserOutline,
WeiboCircleOutline,
ClearOutline,
InfoCircleOutline,
];

@ -16,12 +16,9 @@ func NewRouteRegistrar() route.Registrar {
return service{}
}
func (s service) RegisterAuthRoute(router gin.IRoutes) {
router.GET("/api/account/profile", s.Profile)
}
func (s service) RegisterNonAuthRoute(router gin.IRoutes) {
func (s service) RegisterRoute(router gin.IRoutes) {
router.POST("/api/account/login", s.Login)
router.Use(route.AuthRequired()).GET("/api/account/profile", s.Profile)
}
type loginRequest struct {

@ -3,6 +3,7 @@ package client
import (
"context"
"encoding/json"
"sync"
"time"
cgrpc "github.com/asim/go-micro/plugins/client/grpc/v4"
@ -20,18 +21,19 @@ import (
type service struct {
client client.Client
registry registry.Registry
}
func NewRouteRegistrar(client client.Client, registry registry.Registry) route.Registrar {
return service{client: client, registry: registry}
clients map[string]client.Client
clientsMu sync.Mutex
}
func (s service) RegisterAuthRoute(router gin.IRoutes) {
router.POST("/api/client/call", s.Call)
router.POST("/api/client/publish", s.Publish)
func NewRouteRegistrar(client client.Client, registry registry.Registry) route.Registrar {
return &service{client: client, registry: registry}
}
func (s service) RegisterNonAuthRoute(router gin.IRoutes) {
func (s *service) RegisterRoute(router gin.IRoutes) {
router.Use(route.AuthRequired()).
POST("/api/client/call", s.Call).
POST("/api/client/publish", s.Publish)
}
// @Security ApiKeyAuth
@ -70,16 +72,7 @@ func (s *service) Call(ctx *gin.Context) {
ctx.Render(400, render.String{Format: "service node not found"})
return
}
switch srv.Nodes[0].Metadata["server"] {
case "grpc":
c = cgrpc.NewClient()
case "http":
c = chttp.NewClient()
case "mucp":
c = cmucp.NewClient()
default:
c = s.client
}
c = s.getClient(srv.Nodes[0].Metadata["server"])
break
}
if c == nil {
@ -139,3 +132,33 @@ func (s *service) Publish(ctx *gin.Context) {
}
ctx.JSON(200, gin.H{"success": true})
}
func (s *service) getClient(serverType string) client.Client {
if serverType == s.client.String() {
return s.client
}
s.clientsMu.Lock()
defer s.clientsMu.Unlock()
if s.clients == nil {
s.clients = make(map[string]client.Client)
} else {
if c, ok := s.clients[serverType]; ok {
return c
}
}
var c client.Client
switch serverType {
case "grpc":
c = cgrpc.NewClient()
s.clients[serverType] = c
case "http":
c = chttp.NewClient()
s.clients[serverType] = c
case "mucp":
c = cmucp.NewClient()
s.clients[serverType] = c
default:
c = s.client
}
return c
}

@ -0,0 +1,23 @@
package client
import (
"testing"
"go-micro.dev/v4/client"
)
func TestGetClient(t *testing.T) {
s := &service{client: client.DefaultClient}
if s.getClient("grpc").String() != "grpc" {
t.Fail()
}
if s.getClient("http").String() != "http" {
t.Fail()
}
if s.getClient("mucp").String() != "mucp" {
t.Fail()
}
if s.getClient("other").String() != client.DefaultClient.String() {
t.Fail()
}
}

@ -31,17 +31,15 @@ func Register(opts Options) error {
return err
}
if cfg := config.GetServerConfig().CORS; cfg.Enable {
router.Use(CorsHandler(cfg.Origin))
router.Use(route.CorsHandler(cfg.Origin))
}
authRouter := router.Group("").Use(AuthRequired())
for _, r := range []route.Registrar{
account.NewRouteRegistrar(),
handlerclient.NewRouteRegistrar(opts.Client, opts.Client.Options().Registry),
registry.NewRouteRegistrar(opts.Client.Options().Registry),
statistics.NewRouteRegistrar(opts.Client.Options().Registry),
} {
r.RegisterNonAuthRoute(router)
r.RegisterAuthRoute(authRouter)
r.RegisterRoute(router)
}
return nil
}

@ -17,14 +17,12 @@ func NewRouteRegistrar(registry registry.Registry) route.Registrar {
return service{registry: registry}
}
func (s service) RegisterAuthRoute(router gin.IRoutes) {
router.GET("/api/registry/services", s.GetServices)
router.GET("/api/registry/service", s.GetServiceDetail)
router.GET("/api/registry/service/handlers", s.GetServiceHandlers)
router.GET("/api/registry/service/subscribers", s.GetServiceSubscribers)
}
func (s service) RegisterNonAuthRoute(router gin.IRoutes) {
func (s service) RegisterRoute(router gin.IRoutes) {
router.Use(route.AuthRequired()).
GET("/api/registry/services", s.GetServices).
GET("/api/registry/service", s.GetServiceDetail).
GET("/api/registry/service/handlers", s.GetServiceHandlers).
GET("/api/registry/service/subscribers", s.GetServiceSubscribers)
}
// @Security ApiKeyAuth

@ -1,4 +1,4 @@
package handler
package route
import (
"net/http"

@ -3,6 +3,5 @@ package route
import "github.com/gin-gonic/gin"
type Registrar interface {
RegisterAuthRoute(gin.IRoutes)
RegisterNonAuthRoute(gin.IRoutes)
RegisterRoute(gin.IRoutes)
}

@ -2,6 +2,7 @@ package statistics
import (
"github.com/gin-gonic/gin"
"github.com/xpunch/go-micro-dashboard/config"
"github.com/xpunch/go-micro-dashboard/handler/route"
"go-micro.dev/v4/registry"
)
@ -14,11 +15,9 @@ func NewRouteRegistrar(registry registry.Registry) route.Registrar {
return service{registry: registry}
}
func (s service) RegisterAuthRoute(router gin.IRoutes) {
router.GET("/api/summary", s.GetSummary)
}
func (s service) RegisterNonAuthRoute(router gin.IRoutes) {
func (s service) RegisterRoute(router gin.IRoutes) {
router.GET("/version", s.GetVersion)
router.Use(route.AuthRequired()).GET("/api/summary", s.GetSummary)
}
// @Security ApiKeyAuth
@ -54,3 +53,10 @@ func (s *service) GetSummary(ctx *gin.Context) {
}
ctx.JSON(200, resp)
}
// @ID getVersion
// @Success 200 {object} object
// @Router /version [get]
func (s *service) GetVersion(ctx *gin.Context) {
ctx.JSON(200, gin.H{"version": config.Version})
}

@ -9,13 +9,8 @@ import (
"go-micro.dev/v4/logger"
)
const (
Name = "go.micro.dashboard"
Version = "1.0.0"
)
// @title Go Micro Dashboard
// @version 1.0.0
// @version 1.1.0
// @description go micro dashboard restful-api
// @termsOfService http://swagger.io/terms/
// @BasePath /
@ -29,9 +24,9 @@ func main() {
}
srv := micro.NewService(micro.Server(mhttp.NewServer()))
opts := []micro.Option{
micro.Name(Name),
micro.Name(config.Name),
micro.Address(config.GetServerConfig().Address),
micro.Version(Version),
micro.Version(config.Version),
}
srv.Init(opts...)
if config.GetServerConfig().Env == config.EnvProd {

@ -1,5 +1,5 @@
// Code generated by fileb0x at "2021-11-26 18:52:47.6793726 +0800 CST m=+0.056801301" from config file "b0x.yaml" DO NOT EDIT.
// modification hash(6af8339fb9664713fa81d9a6e9522cf2.8be3f833d63e3c844663716446e13a42)
// Code generated by fileb0x at "2021-11-30 15:12:48.1868883 +0800 CST m=+0.092050301" from config file "b0x.yaml" DO NOT EDIT.
// modification hash(13d61c2e25ff201c032844b85c950766.8be3f833d63e3c844663716446e13a42)
package web

@ -1,5 +1,5 @@
// Code generaTed by fileb0x at "2021-11-26 18:52:47.8017879 +0800 CST m=+0.179216601" from config file "b0x.yaml" DO NOT EDIT.
// modified(2021-11-26 18:51:48.9504977 +0800 CST)
// Code generaTed by fileb0x at "2021-11-30 15:12:48.2738525 +0800 CST m=+0.179014501" from config file "b0x.yaml" DO NOT EDIT.
// modified(2021-11-30 14:59:54.7720466 +0800 CST)
// original path: frontend\dist\449.ea3505f8c3a78bc5ec51.js
package web

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,5 +1,5 @@
// Code generaTed by fileb0x at "2021-11-26 18:52:47.8090903 +0800 CST m=+0.186519001" from config file "b0x.yaml" DO NOT EDIT.
// modified(2021-11-26 18:51:48.9495057 +0800 CST)
// Code generaTed by fileb0x at "2021-11-30 15:12:48.2273449 +0800 CST m=+0.132506901" from config file "b0x.yaml" DO NOT EDIT.
// modified(2021-11-30 14:59:54.7705286 +0800 CST)
// original path: frontend\dist\polyfills.d3127c390f57a23419e1.js
package web

Loading…
Cancel
Save