support endpoint request call

main
Johnson C 3 years ago
parent bdc5994e62
commit c454c6963b

@ -23,6 +23,12 @@ swagger generate spec -o docs/swagger.json -b ./docs
swag init
```
#### Config
```
default username: admin
default password: 123456
```
### Web UI
[Document](https://github.com/xpunch/go-micro-dashboard/tree/main/frontend)
@ -39,6 +45,12 @@ docker run .
docker-compose up -d
```
### Community
QQ Group: 953973712
[![View](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fxpunch%2Fgo-micro-dashboard&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com)
## License
[Apache License 2.0](./LICENSE)

@ -59,6 +59,17 @@ export class StartupService {
icon: { type: 'icon', value: 'cloud' }
}
]
},
{
text: 'Client',
group: true,
children: [
{
text: 'Call',
link: '/client/call',
icon: { type: 'icon', value: 'api' }
}
]
}
]);
// Can be set page suffix title, https://ng-alain.com/theme/title

@ -0,0 +1,66 @@
<page-header [title]="'Call'" [breadcrumb]="breadcrumb" [action]="action">
<ng-template #breadcrumb>
<nz-breadcrumb>
<nz-breadcrumb-item>
<a href="#/dashboard">Dashboard</a>
</nz-breadcrumb-item>
<nz-breadcrumb-item>
<a href="#/services">Services</a>
</nz-breadcrumb-item>
<nz-breadcrumb-item>Call</nz-breadcrumb-item>
</nz-breadcrumb>
</ng-template>
<ng-template #action>
<button nz-button nzType="primary" style="float:right;" (click)="load()" [nzLoading]="loading">Refresh</button>
</ng-template>
</page-header>
<div nz-row>
<div nz-col nzSpan="12" class="p-sm">
<form nz-form (ngSubmit)="call()" role="form">
<nz-form-item>
<nz-form-control nzErrorTip="Select service">
<nz-select nzShowSearch name="service" nzPlaceHolder="Select service" [(ngModel)]="selectedService"
(ngModelChange)="serviceChanged($event)">
<nz-option *ngFor="let s of services" [nzValue]="s" [nzLabel]="s.name">
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item *ngIf="selectedService">
<nz-form-control>
<nz-select nzShowSearch name="version" nzPlaceHolder="Select version" [(ngModel)]="version"
(ngModelChange)="versionChanged($event)">
<nz-option *ngFor="let v of selectedService.versions" [nzValue]="v" [nzLabel]="v">
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item *ngIf="endpoints">
<nz-form-control>
<nz-select nzShowSearch name="endpoint" nzPlaceHolder="Select endpoint"
[(ngModel)]="selectedEndpoint" (ngModelChange)="endpointChanged($event)">
<nz-option *ngFor="let e of endpoints" [nzValue]="e" [nzLabel]="e.name">
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control>
<textarea nz-input name="request" placeholder="Request payload" [ngModel]="request|json"
[nzAutosize]="{ minRows: 5, maxRows: 20 }" (ngModelChange)="requestChanged($event)"></textarea>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<button nz-button type="submit" nzType="primary" nzSize="large" [nzLoading]="loading" nzBlock>
Call
</button>
</nz-form-item>
</form>
</div>
<div nz-col nzSpan="12" class="p-sm">
<nz-card nzTitle="Response">
<p style="overflow-wrap: break-word;white-space: break-spaces;">{{response|json}}</p>
</nz-card>
</div>
</div>

@ -0,0 +1,173 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { finalize } from 'rxjs/operators';
import { CallRequest, ClientServiceProxy, RegistryEndpoint, RegistryServiceProxy, RegistryServiceSummary, RegistryValue } from 'src/app/shared/service-proxies/service-proxies';
interface RequestPayload {
[key: string]: any
}
@Component({
selector: 'micro-client-call',
templateUrl: './call.component.html',
changeDetection: ChangeDetectionStrategy.Default
})
export class ClientEndpointCallComponent implements OnInit {
loading = false;
service: string = '';
version: string = '';
endpoint: string = '';
timeout = 10;
request: any = undefined;
response: any = {};
services: RegistryServiceSummary[] = [];
selectedService: RegistryServiceSummary | undefined = undefined;
endpoints: RegistryEndpoint[] = [];
selectedEndpoint: RegistryEndpoint | undefined = undefined;
constructor(private readonly route: ActivatedRoute,
private readonly clientService: ClientServiceProxy,
private readonly registryService: RegistryServiceProxy,
) {
}
ngOnInit(): void {
var service = this.route.snapshot.queryParams['service'];
if (service) {
this.service = service;
}
var version = this.route.snapshot.queryParams['version'];
if (version) {
this.version = version;
}
var endpoint = this.route.snapshot.queryParams['endpoint'];
if (endpoint) {
this.endpoint = endpoint;
}
this.load();
}
load() {
this.loading = true;
this.selectedEndpoint = undefined;
this.services = [];
this.registryService.getServices().pipe(
finalize(() => {
this.loading = false;
})
).subscribe(resp => {
this.services = resp.services;
if (!this.service || !resp.services.length) {
return
}
resp.services.forEach(s => {
if (s.name == this.service) {
this.selectedService = s;
if (!this.version) {
this.version = s.versions ? s.versions[0] : '';
}
}
});
this.loadEndpoints();
});
}
call() {
this.loading = true;
var input = new CallRequest({
service: this.service,
version: this.version,
endpoint: this.endpoint,
request: JSON.stringify(this.request),
timeout: this.timeout,
});
this.clientService.callEndpoint(input).pipe(
finalize(() => {
this.loading = false;
})
).subscribe(resp => {
this.response = resp;
});
}
serviceChanged(service: RegistryServiceSummary) {
this.service = service.name;
if (service.versions && service.versions.length) {
this.version = service.versions[0];
}
this.endpoint = '';
this.selectedEndpoint = undefined;
this.loadEndpoints();
}
versionChanged(version: string) {
this.version = version;
this.loadEndpoints();
}
endpointChanged(endpoint: RegistryEndpoint) {
this.endpoint = endpoint.name;
this.updateRequestPayload(endpoint.request);
}
requestChanged(request: string) {
this.request = eval('(' + request + ')');
}
private loadEndpoints() {
if (!this.service) {
return
}
this.endpoints = [];
this.registryService.getServiceEndpoints(this.service, this.version).subscribe(resp => {
this.endpoints = resp.endpoints ? resp.endpoints : [];
if (resp.endpoints && resp.endpoints.length) {
if (this.endpoint) {
resp.endpoints?.forEach(e => {
if (e.name == this.endpoint) {
this.selectedEndpoint = e;
this.updateRequestPayload(e.request);
}
});
} else {
this.selectedEndpoint = this.endpoints[0];
this.endpoint = this.endpoints[0].name;
this.updateRequestPayload(this.endpoints[0].request);
}
}
});
}
private updateRequestPayload(request: RegistryValue) {
let payload: RequestPayload = {};
if (request && request.values) {
request.values.forEach(v => {
if (!v.name || v.name === 'MessageState' || v.name === 'int32' || v.name === 'unknownFields') {
return
}
let value: any;
switch (v.type) {
case "string":
value = '';
break;
case "int":
case "int32":
case "int64":
case "uint":
case "uint32":
case "uint64":
value = 0;
break;
case 'bool':
value = false;
break;
default:
console.log(v.type);
value = v.type;
}
payload[v.name] = value;
})
}
this.request = payload;
}
}

@ -5,6 +5,7 @@ import { environment } from '@env/environment';
// layout
import { LayoutBasicComponent } from '../layout/basic/basic.component';
import { LayoutPassportComponent } from '../layout/passport/passport.component';
import { ClientEndpointCallComponent } from './client/call.component';
// dashboard pages
import { DashboardComponent } from './dashboard/dashboard.component';
// passport pages
@ -22,16 +23,10 @@ const routes: Routes = [
{ path: 'dashboard', component: DashboardComponent, data: { title: 'Dashboard', titleI18n: 'dashboard' } },
{ path: 'services', component: ServicesListComponent, data: { title: 'Services', titleI18n: 'services' } },
{ path: 'service/detail', component: ServiceDetailComponent },
{ path: 'client/call', component: ClientEndpointCallComponent, data: { title: 'Call', titleI18n: 'call' } },
{ path: 'exception', loadChildren: () => import('./exception/exception.module').then(m => m.ExceptionModule) },
]
},
// {
// path: 'blank',
// component: LayoutBlankComponent,
// children: [
// ]
// },
// passport
{
path: 'passport',
component: LayoutPassportComponent,

@ -1,5 +1,6 @@
import { NgModule, Type } from '@angular/core';
import { SharedModule } from '@shared';
import { ClientEndpointCallComponent } from './client/call.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { UserLoginComponent } from './passport/login/login.component';
@ -12,6 +13,7 @@ const COMPONENTS: Array<Type<void>> = [
UserLoginComponent,
ServicesListComponent,
ServiceDetailComponent,
ClientEndpointCallComponent,
];
@NgModule({

@ -15,47 +15,54 @@
</ng-template>
</page-header>
<div *ngFor="let service of services">
<nz-table *ngIf="service.nodes" #basicTable [nzData]="service.nodes" [nzTitle]="service.version?service.version:'-'"
[nzFrontPagination]="false" [nzBordered]="true">
<thead>
<tr>
<th>Id</th>
<th>Address</th>
<th>Metadata</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of basicTable.data">
<td>{{data.id}}</td>
<td>{{data.address}}</td>
<td>{{data.metadata|json}}</td>
</tr>
</tbody>
</nz-table>
<nz-table #endpointTable *ngIf="service.endpoints" [nzData]="service.endpoints" nzTitle="Endpoints"
[nzFrontPagination]="false" [nzBordered]="true">
<thead>
<tr>
<th>Name</th>
<th>Request</th>
<th>Response</th>
<th>Metadata</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of endpointTable.data">
<td>{{data.name}}</td>
<td>
<pre class="json">{{data.request|endpoint}}</pre>
</td>
<td>
<pre class="json">{{data.response|endpoint}}</pre>
</td>
<td>{{data.metadata|json}}</td>
</tr>
</tbody>
</nz-table>
</div>
<nz-collapse>
<nz-collapse-panel *ngFor="let service of services" [nzHeader]="service.version" nzActive="true">
<nz-table *ngIf="service.nodes" #basicTable [nzData]="service.nodes" [nzFrontPagination]="false"
[nzBordered]="true">
<thead>
<tr>
<th>Id</th>
<th>Address</th>
<th>Metadata</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of basicTable.data">
<td>{{data.id}}</td>
<td>{{data.address}}</td>
<td>{{data.metadata|json}}</td>
</tr>
</tbody>
</nz-table>
<nz-table #endpointTable *ngIf="service.endpoints" [nzData]="service.endpoints" nzTitle="Endpoints"
[nzFrontPagination]="false" [nzBordered]="true">
<thead>
<tr>
<th>Name</th>
<th>Request</th>
<th>Response</th>
<th>Metadata</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of endpointTable.data">
<td>
{{data.name}}
<a *ngIf="data.name!='Func'" (click)="gotoCall(service.name, service.version, data.name)">
Call
</a>
</td>
<td>
<pre class="json">{{data.request|endpoint}}</pre>
</td>
<td>
<pre class="json">{{data.response|endpoint}}</pre>
</td>
<td>{{data.metadata|json}}</td>
</tr>
</tbody>
</nz-table>
</nz-collapse-panel>
</nz-collapse>
<nz-back-top></nz-back-top>

@ -1,12 +1,11 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
import { finalize } from 'rxjs/operators';
import { RegistryService, RegistryServiceProxy } from 'src/app/shared/service-proxies/service-proxies';
@Component({
selector: 'micro-service',
templateUrl: './detail.component.html',
// styleUrls: ['./service.component.less'],
changeDetection: ChangeDetectionStrategy.Default
})
export class ServiceDetailComponent implements OnInit {
@ -16,12 +15,12 @@ export class ServiceDetailComponent implements OnInit {
services: RegistryService[] = [];
constructor(private readonly route: ActivatedRoute,
private readonly registryService: RegistryServiceProxy,) {
private readonly router: Router,
private readonly registryService: RegistryServiceProxy,
) {
}
ngOnInit(): void {
// console.log(this.route.snapshot.queryParams['name']);
// console.log(this.route.snapshot.queryParams['version']);
var name = this.route.snapshot.queryParams['name'];
if (name) {
this.name = name;
@ -46,7 +45,10 @@ export class ServiceDetailComponent implements OnInit {
});
}
goBack() {
history.go(-1);
gotoCall(service: string, version: string, endpoint: string) {
let navigationExtras: NavigationExtras = {
queryParams: { 'service': service, 'version': version, 'endpoint': endpoint }
};
this.router.navigate(['/client/call'], navigationExtras);
}
}

@ -14,9 +14,9 @@
<nz-list nzItemLayout="horizontal" style="background-color: #fff;" [nzLoading]="loading" nzBordered>
<nz-list-item *ngFor="let service of services" nzNoFlex>
<a (click)="gotoService(service.name, undefined)">{{ service.name }}</a>
<a (click)="gotoServiceDetail(service.name, undefined)">{{ service.name }}</a>
<span *ngFor="let version of service.versions" style="margin-left:8px;">
<nz-tag [nzColor]="'green'"><a (click)="gotoService(service.name, version)">{{ version }}</a></nz-tag>
<nz-tag [nzColor]="'green'"><a (click)="gotoServiceDetail(service.name, version)">{{ version }}</a></nz-tag>
</span>
</nz-list-item>
<nz-list-empty *ngIf="!services||!services.length"></nz-list-empty>

@ -33,11 +33,7 @@ export class ServicesListComponent implements OnInit {
});
}
goto(url: string) {
this.router.navigateByUrl(url);
}
gotoService(name: any, version: any) {
gotoServiceDetail(name: any, version: any) {
let navigationExtras: NavigationExtras = {
queryParams: { 'name': name, 'version': version }
};

@ -1,165 +0,0 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'json',
pure: false,
})
export class JsonPipe implements PipeTransform {
transform(d: any, args?: any[]): string {
if (!d) {
return '';
}
return this.stringify(d, { indent: ' ' });
}
stringify(input: any, options: any, pad = ''): string {
const seen: [] = [];
options.indent = options.indent || '\t';
let tokens: any;
if (options.inlineCharacterLimit === undefined) {
tokens = {
newLine: '\n',
newLineOrSpace: '\n',
pad,
indent: pad + options.indent,
};
} else {
tokens = {
newLine: '@@__STRINGIFY_OBJECT_NEW_LINE__@@',
newLineOrSpace: '@@__STRINGIFY_OBJECT_NEW_LINE_OR_SPACE__@@',
pad: '@@__STRINGIFY_OBJECT_PAD__@@',
indent: '@@__STRINGIFY_OBJECT_INDENT__@@',
};
}
const expandWhiteSpace = (s: string) => {
if (options.inlineCharacterLimit === undefined) {
return s;
}
const oneLined = s
.replace(new RegExp(tokens.newLine, 'g'), '')
.replace(new RegExp(tokens.newLineOrSpace, 'g'), ' ')
.replace(new RegExp(tokens.pad + '|' + tokens.indent, 'g'), '');
if (oneLined.length <= options.inlineCharacterLimit) {
return oneLined;
}
return s
.replace(new RegExp(tokens.newLine + '|' + tokens.newLineOrSpace, 'g'), '\n')
.replace(new RegExp(tokens.pad, 'g'), pad)
.replace(new RegExp(tokens.indent, 'g'), pad + options.indent);
};
if (seen.indexOf(<never>input) !== -1) {
return '"[Circular]"';
}
if (
input === null ||
input === undefined ||
typeof input === 'number' ||
typeof input === 'boolean' ||
typeof input === 'function' ||
typeof input === 'symbol' ||
this.isRegexp(input)
) {
return String(input);
}
if (input instanceof Date) {
return `new Date('${input.toISOString()}')`;
}
if (Array.isArray(input)) {
if (input.length === 0) {
return '[]';
}
seen.push(<never>input);
const ret =
'[' +
tokens.newLine +
input
.map((el, i) => {
const eol = input.length - 1 === i ? tokens.newLine : ',' + tokens.newLineOrSpace;
let value = this.stringify(el, options, pad + options.indent);
if (options.transform) {
value = options.transform(input, i, value);
}
return tokens.indent + value + eol;
})
.join('') +
tokens.pad +
']';
seen.pop();
return expandWhiteSpace(ret);
}
if (this.isObj(input)) {
const keys: Array<any> = Object.keys(input);
let objKeys = keys.concat(Object.getOwnPropertySymbols(input));
if (options.filter) {
objKeys = objKeys.filter((el) => options.filter(input, el));
}
if (objKeys.length === 0) {
return '{}';
}
seen.push(<never>input);
const ret =
'{' +
tokens.newLine +
objKeys
.map((el, i) => {
const eol = objKeys.length - 1 === i ? tokens.newLine : ',' + tokens.newLineOrSpace;
const isSymbol = typeof el === 'symbol';
const isClassic = !isSymbol && /^[a-z$_][a-z$_0-9]*$/i.test(el);
const key = isSymbol || isClassic ? el : this.stringify(el, options);
let value = this.stringify(input[el], options, pad + options.indent);
if (options.transform) {
value = options.transform(input, el, value);
}
return tokens.indent + String(key) + ': ' + value + eol;
})
.join('') +
tokens.pad +
'}';
seen.pop();
return expandWhiteSpace(ret);
}
input = String(input).replace(/[\r\n]/g, (x) => (x === '\n' ? '\\n' : '\\r'));
if (options.singleQuotes === false) {
input = input.replace(/"/g, '\\"');
return `"${input}"`;
}
input = input.replace(/\\?'/g, "\\'");
return `'${input}'`;
}
private isObj(x: any): boolean {
const type = typeof x;
return x !== null && (type === 'object' || type === 'function');
}
private isRegexp(input: any): boolean {
return Object.prototype.toString.call(input) === '[object RegExp]';
}
}

@ -2,16 +2,13 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { EndpointPipe } from './endpoint.pipe';
import { JsonPipe } from './json.pipe';
@NgModule({
declarations: [
JsonPipe,
EndpointPipe,
],
imports: [CommonModule],
exports: [
JsonPipe,
EndpointPipe,
],
})

@ -175,6 +175,95 @@ export class AccountServiceProxy {
}
}
@Injectable()
export class ClientServiceProxy {
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:4000/";
}
/**
* @param input request
* @return success
*/
callEndpoint(input: CallRequest) : Observable<any> {
let url_ = this.baseUrl + "/api/client/endpoint/call";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(input);
let options_ : any = {
body: content_,
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Content-Type": "application/json",
"Accept": "application/json"
})
};
return this.http.request("post", url_, options_).pipe(_observableMergeMap((response_ : any) => {
return this.processCallEndpoint(response_);
})).pipe(_observableCatch((response_: any) => {
if (response_ instanceof HttpResponseBase) {
try {
return this.processCallEndpoint(<any>response_);
} catch (e) {
return <Observable<any>><any>_observableThrow(e);
}
} else
return <Observable<any>><any>_observableThrow(response_);
}));
}
protected processCallEndpoint(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 === 400) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
let result400: any = null;
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result400 = resultData400 !== undefined ? resultData400 : <any>null;
return throwException("Bad Request", status, _responseText, _headers, result400);
}));
} else if (status === 401) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
let result401: any = null;
let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result401 = resultData401 !== undefined ? resultData401 : <any>null;
return throwException("Unauthorized", status, _responseText, _headers, result401);
}));
} else if (status === 500) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
let result500: any = null;
let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result500 = resultData500 !== undefined ? resultData500 : <any>null;
return throwException("Internal Server Error", status, _responseText, _headers, result500);
}));
} 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);
}
}
@Injectable()
export class RegistryServiceProxy {
private http: HttpClient;
@ -187,13 +276,15 @@ export class RegistryServiceProxy {
}
/**
* @param name (optional) service name
* @param name service name
* @param version (optional) service version
* @return OK
*/
getServiceDetail(name: string | null | undefined, version: string | null | undefined) : Observable<GetServiceDetailResponse> {
getServiceDetail(name: string, version: string | null | undefined) : Observable<GetServiceDetailResponse> {
let url_ = this.baseUrl + "/api/registry/service?";
if (name !== undefined && name !== null)
if (name === undefined || name === null)
throw new Error("The parameter 'name' must be defined and cannot be null.");
else
url_ += "name=" + encodeURIComponent("" + name) + "&";
if (version !== undefined && version !== null)
url_ += "version=" + encodeURIComponent("" + version) + "&";
@ -264,6 +355,86 @@ export class RegistryServiceProxy {
return _observableOf<GetServiceDetailResponse>(<any>null);
}
/**
* @param name service name
* @param version (optional) service version
* @return OK
*/
getServiceEndpoints(name: string, version: string | null | undefined) : Observable<GetServiceEndpointsResponse> {
let url_ = this.baseUrl + "/api/registry/service/endpoints?";
if (name === undefined || name === null)
throw new Error("The parameter 'name' must be defined and cannot be null.");
else
url_ += "name=" + encodeURIComponent("" + name) + "&";
if (version !== undefined && version !== null)
url_ += "version=" + encodeURIComponent("" + 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.processGetServiceEndpoints(response_);
})).pipe(_observableCatch((response_: any) => {
if (response_ instanceof HttpResponseBase) {
try {
return this.processGetServiceEndpoints(<any>response_);
} catch (e) {
return <Observable<GetServiceEndpointsResponse>><any>_observableThrow(e);
}
} else
return <Observable<GetServiceEndpointsResponse>><any>_observableThrow(response_);
}));
}
protected processGetServiceEndpoints(response: HttpResponseBase): Observable<GetServiceEndpointsResponse> {
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 = GetServiceEndpointsResponse.fromJS(resultData200);
return _observableOf(result200);
}));
} else if (status === 400) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
let result400: any = null;
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result400 = resultData400 !== undefined ? resultData400 : <any>null;
return throwException("Bad Request", status, _responseText, _headers, result400);
}));
} else if (status === 401) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
let result401: any = null;
let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result401 = resultData401 !== undefined ? resultData401 : <any>null;
return throwException("Unauthorized", status, _responseText, _headers, result401);
}));
} else if (status === 500) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
let result500: any = null;
let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result500 = resultData500 !== undefined ? resultData500 : <any>null;
return throwException("Internal Server Error", status, _responseText, _headers, result500);
}));
} else if (status !== 200 && status !== 204) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}));
}
return _observableOf<GetServiceEndpointsResponse>(<any>null);
}
/**
* @return OK
*/
@ -462,7 +633,7 @@ export interface ILoginRequest {
}
export class LoginResponse implements ILoginResponse {
token?: string | undefined;
token!: string;
constructor(data?: ILoginResponse) {
if (data) {
@ -494,7 +665,7 @@ export class LoginResponse implements ILoginResponse {
}
export interface ILoginResponse {
token?: string | undefined;
token: string;
}
export class ProfileResponse implements IProfileResponse {
@ -533,6 +704,58 @@ export interface IProfileResponse {
name?: string | undefined;
}
export class CallRequest implements ICallRequest {
endpoint!: string;
request?: string | undefined;
service!: string;
timeout?: number | undefined;
version?: string | undefined;
constructor(data?: ICallRequest) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(_data?: any) {
if (_data) {
this.endpoint = _data["endpoint"];
this.request = _data["request"];
this.service = _data["service"];
this.timeout = _data["timeout"];
this.version = _data["version"];
}
}
static fromJS(data: any): CallRequest {
data = typeof data === 'object' ? data : {};
let result = new CallRequest();
result.init(data);
return result;
}
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["endpoint"] = this.endpoint;
data["request"] = this.request;
data["service"] = this.service;
data["timeout"] = this.timeout;
data["version"] = this.version;
return data;
}
}
export interface ICallRequest {
endpoint: string;
request?: string | undefined;
service: string;
timeout?: number | undefined;
version?: string | undefined;
}
export class GetServiceDetailResponse implements IGetServiceDetailResponse {
services?: RegistryService[] | undefined;
@ -577,8 +800,52 @@ export interface IGetServiceDetailResponse {
services?: RegistryService[] | undefined;
}
export class GetServiceEndpointsResponse implements IGetServiceEndpointsResponse {
endpoints?: RegistryEndpoint[] | undefined;
constructor(data?: IGetServiceEndpointsResponse) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(_data?: any) {
if (_data) {
if (Array.isArray(_data["endpoints"])) {
this.endpoints = [] as any;
for (let item of _data["endpoints"])
this.endpoints!.push(RegistryEndpoint.fromJS(item));
}
}
}
static fromJS(data: any): GetServiceEndpointsResponse {
data = typeof data === 'object' ? data : {};
let result = new GetServiceEndpointsResponse();
result.init(data);
return result;
}
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
if (Array.isArray(this.endpoints)) {
data["endpoints"] = [];
for (let item of this.endpoints)
data["endpoints"].push(item.toJSON());
}
return data;
}
}
export interface IGetServiceEndpointsResponse {
endpoints?: RegistryEndpoint[] | undefined;
}
export class GetServiceListResponse implements IGetServiceListResponse {
services?: RegistryServiceSummary[] | undefined;
services!: RegistryServiceSummary[];
constructor(data?: IGetServiceListResponse) {
if (data) {
@ -587,6 +854,9 @@ export class GetServiceListResponse implements IGetServiceListResponse {
(<any>this)[property] = (<any>data)[property];
}
}
if (!data) {
this.services = [];
}
}
init(_data?: any) {
@ -618,13 +888,13 @@ export class GetServiceListResponse implements IGetServiceListResponse {
}
export interface IGetServiceListResponse {
services?: RegistryServiceSummary[] | undefined;
services: RegistryServiceSummary[];
}
export class RegistryEndpoint implements IRegistryEndpoint {
metadata?: { [key: string]: string; } | undefined;
name?: string | undefined;
request?: RegistryValue | undefined;
name!: string;
request!: RegistryValue;
response?: RegistryValue | undefined;
constructor(data?: IRegistryEndpoint) {
@ -634,6 +904,9 @@ export class RegistryEndpoint implements IRegistryEndpoint {
(<any>this)[property] = (<any>data)[property];
}
}
if (!data) {
this.request = new RegistryValue();
}
}
init(_data?: any) {
@ -646,7 +919,7 @@ export class RegistryEndpoint implements IRegistryEndpoint {
}
}
this.name = _data["name"];
this.request = _data["request"] ? RegistryValue.fromJS(_data["request"]) : <any>undefined;
this.request = _data["request"] ? RegistryValue.fromJS(_data["request"]) : new RegistryValue();
this.response = _data["response"] ? RegistryValue.fromJS(_data["response"]) : <any>undefined;
}
}
@ -676,14 +949,14 @@ export class RegistryEndpoint implements IRegistryEndpoint {
export interface IRegistryEndpoint {
metadata?: { [key: string]: string; } | undefined;
name?: string | undefined;
request?: RegistryValue | undefined;
name: string;
request: RegistryValue;
response?: RegistryValue | undefined;
}
export class RegistryNode implements IRegistryNode {
address?: string | undefined;
id?: string | undefined;
address!: string;
id!: string;
metadata?: { [key: string]: string; } | undefined;
constructor(data?: IRegistryNode) {
@ -732,17 +1005,17 @@ export class RegistryNode implements IRegistryNode {
}
export interface IRegistryNode {
address?: string | undefined;
id?: string | undefined;
address: string;
id: string;
metadata?: { [key: string]: string; } | undefined;
}
export class RegistryService implements IRegistryService {
endpoints?: RegistryEndpoint[] | undefined;
metadata?: { [key: string]: string; } | undefined;
name?: string | undefined;
name!: string;
nodes?: RegistryNode[] | undefined;
version?: string | undefined;
version!: string;
constructor(data?: IRegistryService) {
if (data) {
@ -812,13 +1085,13 @@ export class RegistryService implements IRegistryService {
export interface IRegistryService {
endpoints?: RegistryEndpoint[] | undefined;
metadata?: { [key: string]: string; } | undefined;
name?: string | undefined;
name: string;
nodes?: RegistryNode[] | undefined;
version?: string | undefined;
version: string;
}
export class RegistryServiceSummary implements IRegistryServiceSummary {
name?: string | undefined;
name!: string;
versions?: string[] | undefined;
constructor(data?: IRegistryServiceSummary) {
@ -861,13 +1134,13 @@ export class RegistryServiceSummary implements IRegistryServiceSummary {
}
export interface IRegistryServiceSummary {
name?: string | undefined;
name: string;
versions?: string[] | undefined;
}
export class RegistryValue implements IRegistryValue {
name?: string | undefined;
type?: string | undefined;
name!: string;
type!: string;
values?: RegistryValue[] | undefined;
constructor(data?: IRegistryValue) {
@ -912,8 +1185,8 @@ export class RegistryValue implements IRegistryValue {
}
export interface IRegistryValue {
name?: string | undefined;
type?: string | undefined;
name: string;
type: string;
values?: RegistryValue[] | undefined;
}

@ -5,6 +5,7 @@ import * as ApiServiceProxies from './service-proxies';
@NgModule({
providers: [
ApiServiceProxies.AccountServiceProxy,
ApiServiceProxies.ClientServiceProxy,
ApiServiceProxies.RegistryServiceProxy,
ApiServiceProxies.StatisticsServiceProxy,
]

@ -24,6 +24,7 @@ import { NzTagModule } from 'ng-zorro-antd/tag';
import { NzBreadCrumbModule } from 'ng-zorro-antd/breadcrumb';
import { NzBackTopModule } from 'ng-zorro-antd/back-top';
import { NzPageHeaderModule } from 'ng-zorro-antd/page-header';
import { NzCollapseModule } from 'ng-zorro-antd/collapse';
export const SHARED_ZORRO_MODULES = [
NzFormModule,
@ -52,4 +53,5 @@ export const SHARED_ZORRO_MODULES = [
NzBreadCrumbModule,
NzBackTopModule,
NzPageHeaderModule,
NzCollapseModule,
];

@ -5,6 +5,8 @@ go 1.17
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/asim/go-micro/plugins/client/grpc/v4 v4.0.0-20211103025805-c5be9f560cdb
github.com/asim/go-micro/plugins/client/http/v4 v4.0.0-20211111140334-799b8d6a6559
github.com/asim/go-micro/plugins/client/mucp/v4 v4.0.0-20211111140334-799b8d6a6559
github.com/asim/go-micro/plugins/config/encoder/toml/v4 v4.0.0-20211103025805-c5be9f560cdb
github.com/asim/go-micro/plugins/config/encoder/yaml/v4 v4.0.0-20211103025805-c5be9f560cdb
github.com/asim/go-micro/plugins/registry/etcd/v4 v4.0.0-20211103025805-c5be9f560cdb

@ -84,6 +84,10 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asim/go-micro/plugins/client/grpc/v4 v4.0.0-20211103025805-c5be9f560cdb h1:Hsj2MRPRA5/D5RLZD5Rv2ghXQ6o/dBEdU41pAjZ4psk=
github.com/asim/go-micro/plugins/client/grpc/v4 v4.0.0-20211103025805-c5be9f560cdb/go.mod h1:1f9oUqPOpPZZeBBeAQhQzAS4d3jTxLWQtNKHeKolW6M=
github.com/asim/go-micro/plugins/client/http/v4 v4.0.0-20211111140334-799b8d6a6559 h1:w0lcqJnqYaYTXQbtm9GQ7R7o+JXv4LtAPwRpBx08EDs=
github.com/asim/go-micro/plugins/client/http/v4 v4.0.0-20211111140334-799b8d6a6559/go.mod h1:b6uXG9QOlAs3Eh1es67Y6Mq6ALHyoQT+t0tFZEWW8+8=
github.com/asim/go-micro/plugins/client/mucp/v4 v4.0.0-20211111140334-799b8d6a6559 h1:qI1tfjXDPtHSWWKOsz7STAjGDMll+z8AVcAoIwI4GZA=
github.com/asim/go-micro/plugins/client/mucp/v4 v4.0.0-20211111140334-799b8d6a6559/go.mod h1:ZXjR4r15LatjvBSKIXqi9/frEVw28kmcvOZ7rkaAACg=
github.com/asim/go-micro/plugins/config/encoder/toml/v4 v4.0.0-20211103025805-c5be9f560cdb h1:74d2WVGDJQJRuDLQkzXrwgwJrvuAu2dcv9dVIBNm/70=
github.com/asim/go-micro/plugins/config/encoder/toml/v4 v4.0.0-20211103025805-c5be9f560cdb/go.mod h1:9Max62Jwq5e095WubZcqUc7DQA0+EKFRT+Voj6dYwvw=
github.com/asim/go-micro/plugins/config/encoder/yaml/v4 v4.0.0-20211103025805-c5be9f560cdb h1:nsAPI/QfurH9UQy40IP+QXtG90jyXbggvoGrw2ivcPc=

@ -30,7 +30,7 @@ type loginRequest struct {
}
type loginResponse struct {
Token string `json:"token"`
Token string `json:"token" binding:"required"`
}
// @Tags Account

@ -0,0 +1,9 @@
package client
type callRequest struct {
Service string `json:"service" binding:"required"`
Version string `json:"version"`
Endpoint string `json:"endpoint" binding:"required"`
Request string `json:"request"`
Timeout int64 `json:"timeout"`
}

@ -0,0 +1,106 @@
package client
import (
"context"
"encoding/json"
"time"
cgrpc "github.com/asim/go-micro/plugins/client/grpc/v4"
chttp "github.com/asim/go-micro/plugins/client/http/v4"
cmucp "github.com/asim/go-micro/plugins/client/mucp/v4"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/render"
"github.com/xpunch/go-micro-dashboard/handler/route"
"go-micro.dev/v4/client"
"go-micro.dev/v4/errors"
"go-micro.dev/v4/registry"
"go-micro.dev/v4/selector"
)
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}
}
func (s service) RegisterAuthRoute(router gin.IRoutes) {
router.POST("/api/client/endpoint/call", s.CallEndpoint)
}
func (s service) RegisterNonAuthRoute(router gin.IRoutes) {
}
// @Security ApiKeyAuth
// @Tags Client
// @ID client_callEndpoint
// @Param input body callRequest true "request"
// @Success 200 {object} object "success"
// @Failure 400 {object} string
// @Failure 401 {object} string
// @Failure 500 {object} string
// @Router /api/client/endpoint/call [post]
func (s *service) CallEndpoint(ctx *gin.Context) {
var req callRequest
if err := ctx.ShouldBindJSON(&req); nil != err {
ctx.Render(400, render.String{Format: err.Error()})
return
}
var callReq json.RawMessage
if len(req.Request) > 0 {
if err := json.Unmarshal([]byte(req.Request), &callReq); err != nil {
ctx.Render(400, render.String{Format: "parse request failed: %s", Data: []interface{}{err.Error()}})
return
}
}
services, err := s.registry.GetService(req.Service)
if err != nil {
ctx.Render(400, render.String{Format: err.Error()})
return
}
var c client.Client
for _, srv := range services {
if len(req.Version) > 0 && req.Version != srv.Version {
continue
}
if len(srv.Nodes) == 0 {
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
}
break
}
if c == nil {
ctx.Render(400, render.String{Format: "service not found"})
return
}
var resp json.RawMessage
callOpts := []client.CallOption{}
if len(req.Version) > 0 {
callOpts = append(callOpts, client.WithSelectOption(selector.WithFilter(selector.FilterVersion(req.Version))))
}
requestOpts := []client.RequestOption{client.WithContentType("application/json")}
if req.Timeout > 0 {
callOpts = append(callOpts, client.WithRequestTimeout(time.Duration(req.Timeout)*time.Second))
}
if err := c.Call(context.TODO(), client.NewRequest(req.Service, req.Endpoint, callReq, requestOpts...), &resp, callOpts...); err != nil {
if merr := errors.Parse(err.Error()); merr != nil {
ctx.JSON(200, gin.H{"success": false, "error": merr})
} else {
ctx.JSON(200, gin.H{"success": false, "error": err.Error})
}
return
}
ctx.JSON(200, resp)
}

@ -10,6 +10,7 @@ import (
"github.com/xpunch/go-micro-dashboard/config"
"github.com/xpunch/go-micro-dashboard/docs"
"github.com/xpunch/go-micro-dashboard/handler/account"
handlerclient "github.com/xpunch/go-micro-dashboard/handler/client"
"github.com/xpunch/go-micro-dashboard/handler/registry"
"github.com/xpunch/go-micro-dashboard/handler/route"
"github.com/xpunch/go-micro-dashboard/handler/statistics"
@ -38,6 +39,7 @@ func Register(opts Options) error {
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),
} {

@ -1,41 +1,62 @@
package registry
import "go-micro.dev/v4/registry"
type registryServiceSummary struct {
Name string `json:"name"`
Name string `json:"name" binding:"required"`
Versions []string `json:"versions,omitempty"`
}
type getServiceListResponse struct {
Services []registryServiceSummary `json:"services"`
Services []registryServiceSummary `json:"services" binding:"required"`
}
type registryService struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
Name string `json:"name" binding:"required"`
Version string `json:"version" binding:"required"`
Metadata map[string]string `json:"metadata,omitempty"`
Endpoints []registryEndpoint `json:"endpoints,omitempty"`
Nodes []registryNode `json:"nodes,omitempty"`
}
type registryEndpoint struct {
Name string `json:"name"`
Request registryValue `json:"request"`
Name string `json:"name" binding:"required"`
Request registryValue `json:"request" binding:"required"`
Response registryValue `json:"response"`
Metadata map[string]string `json:"metadata"`
Metadata map[string]string `json:"metadata,omitempty"`
}
type registryNode struct {
Id string `json:"id"`
Address string `json:"address"`
Metadata map[string]string `json:"metadata"`
Id string `json:"id" binding:"required"`
Address string `json:"address" binding:"required"`
Metadata map[string]string `json:"metadata,omitempty"`
}
type registryValue struct {
Name string `json:"name"`
Type string `json:"type"`
Values []registryValue `json:"values"`
Name string `json:"name" binding:"required"`
Type string `json:"type" binding:"required"`
Values []registryValue `json:"values,omitempty"`
}
type getServiceDetailResponse struct {
Services []registryService `json:"services"`
}
type getServiceEndpointsResponse struct {
Endpoints []registryEndpoint `json:"endpoints"`
}
func convertRegistryValue(v *registry.Value) registryValue {
if v == nil {
return registryValue{}
}
res := registryValue{
Name: v.Name,
Type: v.Type,
Values: make([]registryValue, 0, len(v.Values)),
}
for _, vv := range v.Values {
res.Values = append(res.Values, convertRegistryValue(vv))
}
return res
}

@ -20,6 +20,7 @@ func NewRouteRegistrar(registry registry.Registry) route.Registrar {
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/endpoints", s.GetServiceEndpoints)
}
func (s service) RegisterNonAuthRoute(router gin.IRoutes) {
@ -80,21 +81,6 @@ func (s *service) GetServiceDetail(ctx *gin.Context) {
ctx.Render(500, render.String{Format: err.Error()})
return
}
var convertValue func(v *registry.Value) registryValue
convertValue = func(v *registry.Value) registryValue {
if v == nil {
return registryValue{}
}
res := registryValue{
Name: v.Name,
Type: v.Type,
Values: make([]registryValue, 0, len(v.Values)),
}
for _, vv := range v.Values {
res.Values = append(res.Values, convertValue(vv))
}
return res
}
version := ctx.Query("version")
resp := getServiceDetailResponse{Services: make([]registryService, 0, len(services))}
for _, s := range services {
@ -105,8 +91,8 @@ func (s *service) GetServiceDetail(ctx *gin.Context) {
for _, e := range s.Endpoints {
endpoints = append(endpoints, registryEndpoint{
Name: e.Name,
Request: convertValue(e.Request),
Response: convertValue(e.Response),
Request: convertRegistryValue(e.Request),
Response: convertRegistryValue(e.Response),
Metadata: e.Metadata,
})
}
@ -128,3 +114,48 @@ func (s *service) GetServiceDetail(ctx *gin.Context) {
}
ctx.JSON(200, resp)
}
// @Security ApiKeyAuth
// @Tags Registry
// @ID registry_getServiceEndpoints
// @Param name query string true "service name"
// @Param version query string false "service version"
// @Success 200 {object} getServiceEndpointsResponse
// @Failure 400 {object} string
// @Failure 401 {object} string
// @Failure 500 {object} string
// @Router /api/registry/service/endpoints [get]
func (s *service) GetServiceEndpoints(ctx *gin.Context) {
name := ctx.Query("name")
if len(name) == 0 {
ctx.Render(400, render.String{Format: "service name required"})
return
}
services, err := s.registry.GetService(name)
if err != nil {
ctx.Render(500, render.String{Format: err.Error()})
return
}
version := ctx.Query("version")
resp := getServiceEndpointsResponse{}
for _, s := range services {
if s.Version != version {
continue
}
endpoints := make([]registryEndpoint, 0, len(s.Endpoints))
for _, e := range s.Endpoints {
if e.Name == "Func" {
continue
}
endpoints = append(endpoints, registryEndpoint{
Name: e.Name,
Request: convertRegistryValue(e.Request),
Response: convertRegistryValue(e.Response),
Metadata: e.Metadata,
})
}
resp.Endpoints = endpoints
break
}
ctx.JSON(200, resp)
}

@ -2,6 +2,8 @@ package main
import (
_ "github.com/asim/go-micro/plugins/client/grpc/v4"
_ "github.com/asim/go-micro/plugins/client/http/v4"
_ "github.com/asim/go-micro/plugins/client/mucp/v4"
_ "github.com/asim/go-micro/plugins/registry/etcd/v4"
_ "github.com/asim/go-micro/plugins/registry/kubernetes/v4"
)

Loading…
Cancel
Save