Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dist/2966.b713aec47d3b11ab.js

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions dist/assets/i18n/services/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"options": "Advanced Options",
"overview": {
"eyebrow": "Live API",
"description": "Your API is live. Copy a request and run it."
"description": "Copy a request and run it.",
"title": "Your API is live"
},
"controls": {
"serviceType": {
Expand Down Expand Up @@ -103,7 +104,8 @@
"title": "{{verb}} on {{service}}",
"hint": "This is everything the role reaches, not just this service. Grants marked inherited come from a wildcard rule; explicit grants target one service. Edit the role to change what agents and keys under it can touch.",
"close": "Close"
}
},
"summary": "{{granted}} of {{total}} roles can reach this service"
},
"pipeline": {
"eyebrow": "Pipeline",
Expand Down
2 changes: 1 addition & 1 deletion dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
<body class="mat-typography">
<df-root></df-root>
<script type="text/javascript" src="https://assets.calendly.com/assets/external/widget.js"></script>
<script src="runtime.8fe883520d3939f9.js" type="module"></script><script src="polyfills.acd6a468b9e08280.js" type="module"></script><script src="main.a8aea8bd0d10987a.js" type="module"></script></body>
<script src="runtime.96cdcc55757dc056.js" type="module"></script><script src="polyfills.acd6a468b9e08280.js" type="module"></script><script src="main.d8d4e8ba9afa4252.js" type="module"></script></body>
</html>
1 change: 1 addition & 0 deletions dist/main.d8d4e8ba9afa4252.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/runtime.96cdcc55757dc056.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -495,15 +495,16 @@ <h4 class="text-center">
[deprecated]="serviceData.deprecated">
</df-service-health-panel>

<!-- Meridian Phase 1: service Overview. Viewing an existing database
service opens on the Live API Card — a real base URL, a scoped key,
and a runnable curl — before the config form. -->
<ng-container *ngIf="edit && isDatabase && serviceData">
<df-page-header
[eyebrow]="'services.overview.eyebrow' | transloco"
[title]="serviceData.label || serviceData.name"
[description]="'services.overview.description' | transloco">
</df-page-header>
<!-- Live API first, closed: the base URL rides in the header so the
card is one click away without pushing the connection form down.
Pipeline and Access follow the form (see the end of this template). -->
<df-disclosure
*ngIf="edit && isDatabase && serviceData"
class="service-section"
[eyebrow]="'services.overview.eyebrow' | transloco"
[title]="'services.overview.title' | transloco"
[description]="'services.overview.description' | transloco"
[summary]="artifactBaseUrl">
<df-artifact-card
class="service-overview-card"
[serviceName]="serviceData.name"
Expand All @@ -512,48 +513,7 @@ <h4 class="text-center">
[keys]="artifactKeys"
(createKey)="onCreateApiKey()">
</df-artifact-card>

<!-- Meridian Phase 5: Pipeline. The effective request chain for this
service as hairline cards (key -> role gate -> rate limit -> field
filter -> generated handler), each node deep-linked to the config
that governs it. Sits above Access so the chain reads before the
roles x verbs detail.
-->
<section class="service-pipeline" *ngIf="serviceData.id">
<df-page-header
[eyebrow]="'services.pipeline.eyebrow' | transloco"
[title]="'services.pipeline.title' | transloco"
[description]="'services.pipeline.description' | transloco">
</df-page-header>
<p class="service-pipeline__hint">
{{ 'services.pipeline.hint' | transloco }}
</p>
<df-pipeline-strip
[serviceId]="serviceData.id"
[serviceName]="serviceData.name"
[serviceType]="serviceData.type">
</df-pipeline-strip>
</section>

<!-- Meridian Phase 3: Access. Governance made legible where you build
the API. The roles x verbs matrix is derived client-side from the
role graph; clicking a granted cell opens the role's resolved scope.
-->
<section class="service-access" *ngIf="serviceData.id">
<df-page-header
[eyebrow]="'services.access.eyebrow' | transloco"
[title]="'services.access.title' | transloco"
[description]="'services.access.description' | transloco">
</df-page-header>
<p class="service-access__hint">
{{ 'services.access.hint' | transloco }}
</p>
<df-scope-matrix
[serviceId]="serviceData.id"
(cellClick)="onScopeCellClick($event)">
</df-scope-matrix>
</section>
</ng-container>
</df-disclosure>

<mat-form-field
subscriptSizing="dynamic"
Expand Down Expand Up @@ -1780,6 +1740,41 @@ <h5>Parameters</h5>
</mat-accordion>
</ng-container>

<!-- Pipeline and Access after the form, closed. Access can be hundreds
of role rows on a real instance; its header says how many of them
reach this service so the matrix only renders on request. -->
<ng-container *ngIf="edit && isDatabase && serviceData?.id">
<df-disclosure
class="service-section"
[eyebrow]="'services.pipeline.eyebrow' | transloco"
[title]="'services.pipeline.title' | transloco"
[description]="'services.pipeline.description' | transloco">
<p class="service-section__hint">
{{ 'services.pipeline.hint' | transloco }}
</p>
<df-pipeline-strip
[serviceId]="serviceData.id"
[serviceName]="serviceData.name"
[serviceType]="serviceData.type">
</df-pipeline-strip>
</df-disclosure>

<df-disclosure
class="service-section"
[eyebrow]="'services.access.eyebrow' | transloco"
[title]="'services.access.title' | transloco"
[description]="'services.access.description' | transloco"
[summary]="accessSummary">
<p class="service-section__hint">
{{ 'services.access.hint' | transloco }}
</p>
<df-scope-matrix
[serviceId]="serviceData.id"
(cellClick)="onScopeCellClick($event)">
</df-scope-matrix>
</df-disclosure>
</ng-container>

<div class="full-width action-bar" *ngIf="!subscriptionRequired">
<button
mat-flat-button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@
margin-bottom: 2.4rem;
}

// ---- Meridian Phase 5: Pipeline section (request chain under the overview) ---
// ---- Meridian Phase 3: Access section (scope matrix under the overview) ----
.service-pipeline,
.service-access {
margin-top: 3.2rem;
padding-top: 3.2rem;
border-top: 1px solid var(--df-border-2);
// ---- Live API / Pipeline / Access as closed sections ----
// Each df-disclosure owns its top hairline; this only spaces them in the
// form's flex flow and styles the one-line hint inside an open section.
.service-section {
margin-top: var(--df-space-5);

&__hint {
margin: 0 0 1.6rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
OnInit,
TemplateRef,
ViewChild,
inject,
} from '@angular/core';
import {
FormArray,
Expand All @@ -25,7 +26,7 @@ import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTabsModule } from '@angular/material/tabs';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslocoPipe } from '@ngneat/transloco';
import { TranslocoPipe, TranslocoService } from '@ngneat/transloco';
import { DfArrayFieldComponent } from 'src/app/shared/components/df-field-array/df-array-field.component';
import { DfDynamicFieldComponent } from 'src/app/shared/components/df-dynamic-field/df-dynamic-field.component';
import { DfRoleScopeComponent } from 'src/app/shared/components/df-role-scope/df-role-scope.component';
Expand Down Expand Up @@ -114,12 +115,13 @@ import { DfSystemService } from 'src/app/shared/services/df-system.service';
import { DfPaywallModal } from 'src/app/shared/components/df-paywall-modal/df-paywall-modal.component';
import { DfAnalyticsService } from 'src/app/shared/services/df-analytics.service';
import { DfArtifactResolverService } from 'src/app/shared/services/df-artifact-resolver.service';
import { DfPageHeaderComponent } from 'src/app/shared/components/df-page-header/df-page-header.component';
import { DfDisclosureComponent } from 'src/app/shared/components/df-disclosure/df-disclosure.component';
import {
DfArtifactCardComponent,
ArtifactKeyOption,
} from 'src/app/shared/components/df-artifact-card/df-artifact-card.component';
import { DfScopeMatrixComponent } from 'src/app/shared/components/df-scope-matrix/df-scope-matrix.component';
import { DfScopeService } from 'src/app/shared/services/df-scope.service';
import { DfServiceHealthPanelComponent } from '../df-service-health-panel/df-service-health-panel.component';
import { DfPipelineStripComponent } from 'src/app/shared/components/df-pipeline-strip/df-pipeline-strip.component';
import { ScopeVerb } from 'src/app/shared/services/df-scope.service';
Expand Down Expand Up @@ -202,7 +204,7 @@ interface ServiceResponse {
DfSecurityConfigComponent,
MatMenuModule,
MatDialogModule,
DfPageHeaderComponent,
DfDisclosureComponent,
DfArtifactCardComponent,
DfScopeMatrixComponent,
DfPipelineStripComponent,
Expand All @@ -212,6 +214,10 @@ interface ServiceResponse {
export class DfServiceDetailsComponent implements OnInit {
edit = false;
isDatabase = false;
/** Header line for the closed Access section: "12 of 313 roles ...". */
accessSummary: string | null = null;
private readonly scope = inject(DfScopeService);
private readonly transloco = inject(TranslocoService);
/** DreamFactory Platform APIs route (data.system). Those services are not
* role-governed the way a user service is, so they are not health-scored. */
isPlatformService = false;
Expand Down Expand Up @@ -591,6 +597,7 @@ export class DfServiceDetailsComponent implements OnInit {
}
}
this.serviceData = data;
this.loadAccessSummary();
if (data) {
// For script services, use script content, not service definition
if (this.isScriptService) {
Expand Down Expand Up @@ -2292,6 +2299,23 @@ export class DfServiceDetailsComponent implements OnInit {
// Meridian Phase 3: a granted scope-matrix cell opens the role's resolved
// scope in a dialog. df-scope-matrix only emits for non-'none' cells, so
// there is always a real grant to inspect here.
private loadAccessSummary(): void {
this.accessSummary = null;
const id = this.serviceData?.id;
if (!this.edit || !this.isDatabase || !id) {
return;
}
this.scope.matrixForService(id).subscribe(matrix => {
const granted = matrix.roles.filter(row =>
Object.values(row.verbs).some(state => state !== 'none')
).length;
this.accessSummary = this.transloco.translate('services.access.summary', {
granted,
total: matrix.roles.length,
});
});
}

onScopeCellClick(event: { roleId: number; verb: ScopeVerb }): void {
this.dialog.open(DfServiceRoleScopeDialogComponent, {
width: '640px',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<section class="disclosure" [class.disclosure--open]="open">
<button
type="button"
class="disclosure__header"
[attr.aria-expanded]="open"
[attr.aria-controls]="panelId"
(click)="toggle()">
<span class="disclosure__heading">
<span class="disclosure__eyebrow" *ngIf="eyebrow">{{ eyebrow }}</span>
<span class="disclosure__title">{{ title }}</span>
</span>
<span class="disclosure__summary" *ngIf="summary">{{ summary }}</span>
<mat-icon class="disclosure__chevron" aria-hidden="true"
>expand_more</mat-icon
>
</button>
<div class="disclosure__body" [id]="panelId" *ngIf="open">
<p class="disclosure__description" *ngIf="description">
{{ description }}
</p>
<ng-content></ng-content>
</div>
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// df-disclosure: hairline section with a closed-by-default body.
// Structure is the only decoration: one top rule, no card, no shadow.
:host {
display: block;
width: 100%;
}

.disclosure {
border-top: 1px solid var(--df-border-2);
}

.disclosure__header {
align-items: center;
background: none;
border: 0;
color: inherit;
cursor: pointer;
display: flex;
font: inherit;
gap: var(--df-space-4);
padding: var(--df-space-4) 0;
text-align: left;
width: 100%;

&:hover .disclosure__title {
color: var(--df-accent);
}

&:focus-visible {
outline: 2px solid var(--df-accent);
outline-offset: 2px;
border-radius: 2px;
}
}

.disclosure__heading {
display: flex;
flex-direction: column;
gap: var(--df-space-1);
min-width: 0;
}

.disclosure__eyebrow {
color: var(--df-text-muted);
font-size: var(--df-font-size-xs);
font-weight: var(--df-font-weight-medium);
letter-spacing: var(--df-tracking-eyebrow);
text-transform: uppercase;
}

.disclosure__title {
color: var(--df-text);
font-size: var(--df-font-size-lg);
font-weight: var(--df-font-weight-heading);
letter-spacing: var(--df-tracking-tight);
line-height: var(--df-lh-tight);
}

.disclosure__summary {
color: var(--df-text-muted);
font-size: var(--df-font-size-sm);
margin-left: auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.disclosure__chevron {
color: var(--df-text-faint);
flex-shrink: 0;
transition: transform 120ms ease;

.disclosure--open & {
transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
transition: none;
}
}

.disclosure__body {
padding: 0 0 var(--df-space-5);
}

.disclosure__description {
color: var(--df-text-2);
font-size: var(--df-font-size-sm);
line-height: var(--df-lh-base);
margin: 0 0 var(--df-space-4);
max-width: 68ch;
}
Loading
Loading