Browse Source

Select client (#998)

* Select client.

* Fixes to OpenAPI: Parameter order.
pull/1003/head
Sebastian Stehle 3 years ago
committed by GitHub
parent
commit
b7bc76b6a1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      backend/i18n/frontend_en.json
  2. 5
      backend/i18n/frontend_fr.json
  3. 5
      backend/i18n/frontend_it.json
  4. 5
      backend/i18n/frontend_nl.json
  5. 5
      backend/i18n/frontend_pt.json
  6. 5
      backend/i18n/frontend_zh.json
  7. 5
      backend/i18n/source/frontend_en.json
  8. 21
      backend/src/Squidex/Areas/Api/Config/OpenApi/AcceptHeaderAttribute.cs
  9. 12
      backend/src/Squidex/Areas/Api/Config/OpenApi/QueryExtensions.cs
  10. 1
      backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/OperationBuilder.cs
  11. 37
      frontend/src/app/features/api/pages/graphql/graphql-page.component.html
  12. 16
      frontend/src/app/features/api/pages/graphql/graphql-page.component.scss
  13. 51
      frontend/src/app/features/api/pages/graphql/graphql-page.component.ts

5
backend/i18n/frontend_en.json

@ -3,7 +3,10 @@
"api.generalApi": "General API",
"api.graphql": "GraphQL",
"api.graphqlPageTitle": "GraphQL",
"api.noClient": "Token of current user",
"api.pageTitle": "API",
"api.selectClient": "Select Client",
"api.selectClientDescription": "Select a client that should be used for authentication. Can be used to simulate permissions and headers.",
"api.title": "API",
"apps.allApps": "All Apps",
"apps.allTeams": "All Teams",
@ -245,11 +248,13 @@
"common.cancelAll": "Cancel All",
"common.category": "Category",
"common.clear": "Clear",
"common.client": "Client",
"common.clientId": "Client Id",
"common.clients": "Clients",
"common.clientSecret": "Client Secret",
"common.clipboardAdded": "Value has been added to your clipboard.",
"common.clone": "Clone",
"common.close": "Close",
"common.cluster": "Cluster",
"common.clusterPageTitle": "Cluster",
"common.comments": "Comments",

5
backend/i18n/frontend_fr.json

@ -3,7 +3,10 @@
"api.generalApi": "API générale",
"api.graphql": "GraphQL",
"api.graphqlPageTitle": "GraphQL",
"api.noClient": "Token of current user",
"api.pageTitle": "API",
"api.selectClient": "Select Client",
"api.selectClientDescription": "Select a client that should be used for authentication. Can be used to simulate permissions and headers.",
"api.title": "API",
"apps.allApps": "Toutes les applications",
"apps.allTeams": "Toutes les équipes",
@ -245,11 +248,13 @@
"common.cancelAll": "Annuler tout",
"common.category": "Catégorie",
"common.clear": "Clair",
"common.client": "Client",
"common.clientId": "Identité du client",
"common.clients": "Clients",
"common.clientSecret": "Clé secrète du client",
"common.clipboardAdded": "La valeur a été ajoutée à votre presse-papiers.",
"common.clone": "Cloner",
"common.close": "Close",
"common.cluster": "Grappe",
"common.clusterPageTitle": "Grappe",
"common.comments": "commentaires",

5
backend/i18n/frontend_it.json

@ -3,7 +3,10 @@
"api.generalApi": "API generali",
"api.graphql": "GraphQL",
"api.graphqlPageTitle": "GraphQL",
"api.noClient": "Token of current user",
"api.pageTitle": "API",
"api.selectClient": "Select Client",
"api.selectClientDescription": "Select a client that should be used for authentication. Can be used to simulate permissions and headers.",
"api.title": "API",
"apps.allApps": "Tutte le Apps",
"apps.allTeams": "All Teams",
@ -245,11 +248,13 @@
"common.cancelAll": "Cancel All",
"common.category": "Categoria",
"common.clear": "Pulisci",
"common.client": "Client",
"common.clientId": "Client Id",
"common.clients": "Client",
"common.clientSecret": "Client Secret",
"common.clipboardAdded": "Il valore è stato aggiunto nei tuoi appunti.",
"common.clone": "Clona",
"common.close": "Close",
"common.cluster": "Cluster",
"common.clusterPageTitle": "Cluster",
"common.comments": "Commenti",

5
backend/i18n/frontend_nl.json

@ -3,7 +3,10 @@
"api.generalApi": "Algemene API",
"api.graphql": "GraphQL",
"api.graphqlPageTitle": "GraphQL",
"api.noClient": "Token of current user",
"api.pageTitle": "API",
"api.selectClient": "Select Client",
"api.selectClientDescription": "Select a client that should be used for authentication. Can be used to simulate permissions and headers.",
"api.title": "API",
"apps.allApps": "Alle apps",
"apps.allTeams": "All Teams",
@ -245,11 +248,13 @@
"common.cancelAll": "Annulleer Alles",
"common.category": "Categorie",
"common.clear": "Wissen",
"common.client": "Client",
"common.clientId": "Client-ID",
"common.clients": "Clients",
"common.clientSecret": "Clientgeheim",
"common.clipboardAdded": "Waarde is toegevoegd aan uw klembord.",
"common.clone": "Kloon",
"common.close": "Close",
"common.cluster": "Cluster",
"common.clusterPageTitle": "Cluster",
"common.comments": "Reacties",

5
backend/i18n/frontend_pt.json

@ -3,7 +3,10 @@
"api.generalApi": "API Geral",
"api.graphql": "GraphQL",
"api.graphqlPageTitle": "GraphQL",
"api.noClient": "Token of current user",
"api.pageTitle": "API",
"api.selectClient": "Select Client",
"api.selectClientDescription": "Select a client that should be used for authentication. Can be used to simulate permissions and headers.",
"api.title": "API",
"apps.allApps": "Todas as aplicações",
"apps.allTeams": "Todas as Equipas",
@ -245,11 +248,13 @@
"common.cancelAll": "cancelar todos",
"common.category": "Categorias",
"common.clear": "Claro",
"common.client": "Client",
"common.clientId": "ID do cliente",
"common.clients": "Clientes",
"common.clientSecret": "Segredo do Cliente",
"common.clipboardAdded": "O valor foi adicionado à sua área de transferência.",
"common.clone": "Clone",
"common.close": "Close",
"common.cluster": "Cluster",
"common.clusterPageTitle": "Cluster",
"common.comments": "Comentários",

5
backend/i18n/frontend_zh.json

@ -3,7 +3,10 @@
"api.generalApi": "通用 API",
"api.graphql": "GraphQL",
"api.graphqlPageTitle": "GraphQL",
"api.noClient": "Token of current user",
"api.pageTitle": "API",
"api.selectClient": "Select Client",
"api.selectClientDescription": "Select a client that should be used for authentication. Can be used to simulate permissions and headers.",
"api.title": "API",
"apps.allApps": "所有应用程序",
"apps.allTeams": "All Teams",
@ -245,11 +248,13 @@
"common.cancelAll": "Cancel All",
"common.category": "类别",
"common.clear": "清除",
"common.client": "Client",
"common.clientId": "客户端 ID",
"common.clients": "客户端",
"common.clientSecret": "客户端密码",
"common.clipboardAdded": "值已添加到您的剪贴板。",
"common.clone": "克隆",
"common.close": "Close",
"common.cluster": "集群",
"common.clusterPageTitle": "集群",
"common.comments": "评论",

5
backend/i18n/source/frontend_en.json

@ -3,7 +3,10 @@
"api.generalApi": "General API",
"api.graphql": "GraphQL",
"api.graphqlPageTitle": "GraphQL",
"api.noClient": "Token of current user",
"api.pageTitle": "API",
"api.selectClient": "Select Client",
"api.selectClientDescription": "Select a client that should be used for authentication. Can be used to simulate permissions and headers.",
"api.title": "API",
"apps.allApps": "All Apps",
"apps.allTeams": "All Teams",
@ -245,11 +248,13 @@
"common.cancelAll": "Cancel All",
"common.category": "Category",
"common.clear": "Clear",
"common.client": "Client",
"common.clientId": "Client Id",
"common.clients": "Clients",
"common.clientSecret": "Client Secret",
"common.clipboardAdded": "Value has been added to your clipboard.",
"common.clone": "Clone",
"common.close": "Close",
"common.cluster": "Cluster",
"common.clusterPageTitle": "Cluster",
"common.comments": "Comments",

21
backend/src/Squidex/Areas/Api/Config/OpenApi/AcceptHeaderAttribute.cs

@ -59,8 +59,8 @@ public sealed class AcceptHeader_NoSlowTotal : AcceptHeaderAttribute
public class AcceptHeaderAttribute : OpenApiOperationProcessorAttribute
{
public AcceptHeaderAttribute(string name, string description, JsonObjectType type = JsonObjectType.String)
: base(typeof(Processor), name, description, type)
public AcceptHeaderAttribute(string name, string description, JsonObjectType schemaType = JsonObjectType.String)
: base(typeof(Processor), name, description, schemaType)
{
}
@ -68,27 +68,30 @@ public class AcceptHeaderAttribute : OpenApiOperationProcessorAttribute
{
private readonly string name;
private readonly string description;
private readonly JsonObjectType type;
private readonly JsonObjectType schemaType;
public Processor(string name, string description, JsonObjectType type)
public Processor(string name, string description, JsonObjectType schemaType)
{
this.name = name;
this.description = description;
this.type = type;
this.schemaType = schemaType;
}
public bool Process(OperationProcessorContext context)
{
context.OperationDescription.Operation.Parameters.Add(new OpenApiParameter
var parameter = new OpenApiParameter
{
Name = name,
Kind = OpenApiParameterKind.Header,
Schema = new JsonSchema
{
Type = type
Type = schemaType
},
Description = description,
});
Description = description
};
context.OperationDescription.Operation.Parameters.Add(parameter);
context.OperationDescription.Operation.SetPositions();
return true;
}

12
backend/src/Squidex/Areas/Api/Config/OpenApi/QueryExtensions.cs

@ -88,5 +88,17 @@ public static class QueryExtensions
Name = "ids",
Description = FieldDescriptions.QueryIds
});
operation.SetPositions();
}
public static void SetPositions(this OpenApiOperation operation)
{
var position = 0;
foreach (var parameter in operation.Parameters)
{
parameter.Position = position++;
}
}
}

1
backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/OperationBuilder.cs

@ -84,6 +84,7 @@ internal sealed class OperationBuilder
}
operation.Parameters.Add(parameter);
operation.SetPositions();
return this;
}

37
frontend/src/app/features/api/pages/graphql/graphql-page.component.html

@ -2,4 +2,39 @@
<sqx-layout layout="main" [hideHeader]="true" [hideSidebar]="true">
<div inner #graphiQLContainer></div>
</sqx-layout>
<button class="btn btn-simple btn-options" *ngIf="clientsReadable" (click)="clientsDialog.show()">
<i class="icon-clients"></i>
</button>
</sqx-layout>
<ng-container *sqxModal="clientsDialog">
<sqx-modal-dialog (close)="clientsDialog.hide()">
<ng-container title>
{{ 'api.selectClient' | sqxTranslate }}
</ng-container>
<ng-container content>
<sqx-form-hint>
{{ 'api.selectClientDescription' | sqxTranslate }}
</sqx-form-hint>
<div class="form-group">
<label for="client">{{ 'common.client' | sqxTranslate }}</label>
<select class="form-control" id="client"
[ngModel]="clientSelected"
(ngModelChange)="selectClient($event)">
<option [ngValue]="null">{{ 'api.noClient' | sqxTranslate }}</option>
<option *ngFor="let client of clientsState.clients | async" [ngValue]="client">{{client.id}}</option>
</select>
</div>
</ng-container>
<ng-container footer>
<button type="button" class="btn btn-text-secondary" (click)="clientsDialog.hide()">
{{ 'common.close' | sqxTranslate }}
</button>
</ng-container>
</sqx-modal-dialog>
</ng-container>

16
frontend/src/app/features/api/pages/graphql/graphql-page.component.scss

@ -12,6 +12,7 @@
--color-primary: #{hsl-str($color-theme-brand)};
--color-success: #{hsl-str($color-theme-success)};
--color-error: #{hsl-str($color-theme-error)};
--color-neutral: #{hsl-str($color-text)};
--border-radius-4: #{$border-radius};
--border-radius-8: #{$border-radius};
--border-radius-12: #{$border-radius};
@ -19,9 +20,24 @@
.graphiql-sessions {
border-radius: $border-radius;
}
* {
box-sizing: content-box;
}
}
.graphiql-logo {
display: none;
}
}
.btn-options {
@include absolute(null, null, 175px, 8px);
font-size: 1.2rem;
font-weight: normal;
width: 44px;
&:hover {
background: rgba(59, 75, 104, 7%);
}
}

51
frontend/src/app/features/api/pages/graphql/graphql-page.component.ts

@ -5,44 +5,77 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { createGraphiQLFetcher } from '@graphiql/toolkit';
import GraphiQL from 'graphiql';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { ApiUrlConfig, AppsState, AuthService } from '@app/shared';
import { ApiUrlConfig, AppsState, AuthService, ClientDto, ClientsService, ClientsState, DialogModel } from '@app/shared';
@Component({
selector: 'sqx-graphql-page',
styleUrls: ['./graphql-page.component.scss'],
templateUrl: './graphql-page.component.html',
})
export class GraphQLPageComponent implements AfterViewInit {
export class GraphQLPageComponent implements AfterViewInit, OnInit {
@ViewChild('graphiQLContainer', { static: false })
public graphiQLContainer!: ElementRef;
public clientsReadable = false;
public clientsDialog = new DialogModel();
public clientSelected: ClientDto | null = null;
constructor(
private readonly appsState: AppsState,
private readonly apiUrl: ApiUrlConfig,
private readonly authService: AuthService,
private readonly clientsService: ClientsService,
public readonly clientsState: ClientsState,
) {
}
public ngOnInit() {
this.clientsReadable = this.appsState.snapshot.selectedApp!.canReadClients;
if (this.clientsReadable) {
this.clientsState.load();
}
}
public ngAfterViewInit() {
const url = this.apiUrl.buildUrl(`api/content/${this.appsState.appName}/graphql`);
this.selectClient(null);
}
public selectClient(client: ClientDto | null) {
this.clientSelected = client;
if (!client) {
this.initOrUpdateGraphQL(this.authService.user?.accessToken!);
} else {
this.clientsService.createToken(this.appsState.appName, client)
.subscribe(token => {
if (this.clientSelected === client) {
this.initOrUpdateGraphQL(token.accessToken);
}
});
}
}
private initOrUpdateGraphQL(accessToken: string) {
const graphQLEndpoint = this.apiUrl.buildUrl(`api/content/${this.appsState.appName}/graphql`);
const subscriptionUrl =
url
graphQLEndpoint
.replace('http://', 'ws://')
.replace('https://', 'wss://') +
`?access_token=${this.authService.user?.accessToken}`;
`?access_token=${accessToken}`;
const fetcher = createGraphiQLFetcher({
url,
subscriptionUrl,
url: graphQLEndpoint,
headers: {
Authorization: `Bearer ${this.authService.user?.accessToken}`,
Authorization: `Bearer ${accessToken}`,
},
subscriptionUrl,
});
ReactDOM.render(

Loading…
Cancel
Save