Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
13a3f93
feat(developers): foundation components, dashboard period and onboard…
roncodes Oct 21, 2026
b50d303
feat(developers): webhooks list/form/detail with deliveries and attem…
roncodes Oct 21, 2026
91ab62d
test(developers): tests for the viewer, services, format utils and ne…
roncodes Oct 21, 2026
c8f91ea
fix(developers): violet badge tone, narrow header search, event picke…
roncodes Oct 21, 2026
99f052c
fix(developers): fixed-layout compact tables with responsive columns,…
roncodes Oct 21, 2026
4a62c35
fix(developers): plain action labels, compact modal steps and origins…
roncodes Oct 21, 2026
db4c404
test(developers): make the dummy-app suite runnable
roncodes Oct 21, 2026
e4c3265
test(developers): generator tasks, en-us only dummy locale, updated n…
roncodes Oct 21, 2026
952f239
test(developers): decorator tasks, KPI loading outside render, meanin…
roncodes Oct 21, 2026
5147ea7
test(developers): quick-resources asserts the button grid
roncodes Oct 21, 2026
921681b
docs(developers): PR notes for the test harness and the fixes found d…
roncodes Oct 21, 2026
a930e44
fix(developers): open the key drawer through the engine owner, ignore…
roncodes Oct 21, 2026
adb0c40
fix(developers): requests list shows credential names and a plain sou…
roncodes Oct 21, 2026
7a72cdd
docs(developers): log the request page check
roncodes Oct 21, 2026
0456b67
fix(developers): phone-width lists keep the identity column, dashboar…
roncodes Oct 21, 2026
b84e6d1
fix(developers): drawer identity actions and code toolbars wrap on ph…
roncodes Oct 21, 2026
bd860c5
fix(developers): restore the channel component module names, comment …
roncodes Oct 21, 2026
824c1a1
docs(developers): reviewer note on the lazy-engine dependency funnel
roncodes Oct 21, 2026
c005d68
fix(developers): channels list shows per-key channels, viewer handles…
roncodes Oct 21, 2026
64896d7
fix(developers): wider channel type column; log the search and channe…
roncodes Oct 21, 2026
fede734
fix(developers): dialog prefix-rule table readable in dark mode; log E3
roncodes Oct 21, 2026
16cffa2
docs(developers): final visual-check summary in the PR notes
roncodes Oct 21, 2026
97c45c6
docs(developers): custom channel dialog screenshot after the colour fix
roncodes Oct 21, 2026
e668cd2
feat(developers): key drawer with tabs and usage, mockup-faithful key…
roncodes Oct 22, 2026
6ea7c8e
fix(developers): request counts ignore blank filters and reload per r…
roncodes Oct 22, 2026
ea2c3d9
fix(developers): header gap from the hidden filter trigger, drawer re…
roncodes Oct 22, 2026
9ab19ed
fix(developers): xs header actions in the key drawer; round-two compa…
roncodes Oct 22, 2026
d3a3a39
fix(developers): keep the export button; only the search wormhole is …
roncodes Oct 22, 2026
a90df31
fix(developers): red delete-dialog title in both themes; round-two sc…
roncodes Oct 22, 2026
da2f732
docs(developers): round-two notes in the PR description
roncodes Oct 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 8 additions & 0 deletions addon/components/api-event/credential-cell.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{{#if @value}}
<span class="dev-cell-text dev-identity" title={{@value}}>
<span class="dev-identity-icon dev-identity-icon-sm is-green"><FaIcon @icon="key" @size="xs" /></span>
<span class="dev-identity-title is-regular">{{or (get @column.cellComponentArgs.names @value) (mask-key @value)}}</span>
</span>
{{else}}
<span class="dev-cell-text dev-muted">—</span>
{{/if}}
11 changes: 0 additions & 11 deletions addon/components/api-event/data.hbs

This file was deleted.

37 changes: 0 additions & 37 deletions addon/components/api-event/details.hbs

This file was deleted.

76 changes: 76 additions & 0 deletions addon/components/api-event/panel.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{{! template-lint-disable require-presentational-children no-invalid-interactive }}
<div class="dev-panel-content" ...attributes>
<div class="dev-panel-identity">
<span class="dev-identity-icon is-blue"><FaIcon @icon={{this.icon}} @size="xs" /></span>
<div class="dev-identity-text">
<span class="dev-identity-title dev-mono" title={{this.event.event}}>{{this.event.event}} <span class="dev-tag"><span>{{this.event.public_id}}</span></span></span>
<span class="dev-identity-sub" title={{this.event.description}}>{{this.event.description}} · {{this.createdAt}}</span>
</div>
<div class="dev-panel-identity-actions">
<Button @size="xs" @icon="copy" @helpText={{t "developers.events.actions.copy-id"}} @onClick={{this.copyId}} />
{{#unless @isPage}}
<Button @size="xs" @icon="arrow-up-right-from-square" @helpText={{t "developers.common.open-full-page"}} @onClick={{this.openFullPage}} />
{{/unless}}
</div>
</div>

<TabNavigation @tabs={{this.tabs}} @activeTabId={{this.activeTab}} @onTabChange={{this.setTab}} @style="github" @size="sm" @tablistClass="dev-tabs">
{{#if (eq this.activeTab "payload")}}
<CodeViewer @value={{this.event.data}} @title={{t "developers.events.panel.payload"}} @emptyText={{t "developers.component.api-event.data.no-data"}} class="mt-3" />
{{else if (eq this.activeTab "deliveries")}}
<SectionCard @flush={{true}} class="mt-3">
<:default>
{{#if this.load.isRunning}}
<div class="p-3"><Skeleton::Table @rows={{3}} @columns={{3}} @header={{false}} /></div>
{{else if this.error}}
<States::Error @message={{this.error}} class="py-4" />
{{else if this.deliveryRows.length}}
<table class="dev-widget-table">
<tbody>
{{#each this.deliveryRows as |row|}}
<tr class="dev-widget-row" role="button" tabindex="0" {{on "click" (fn this.openAttempt row.attempt)}}>
<td>
<div class="dev-identity">
<span class="dev-identity-icon {{if row.ok 'is-green' 'is-red'}}"><FaIcon @icon="globe" @size="xs" /></span>
<span class="dev-identity-text">
<span class="dev-identity-title" title={{row.attempt.url}}>{{row.shortUrl}}</span>
<span class="dev-identity-sub">{{t "developers.webhooks.deliveries.attempt-n" n=(or row.attempt.attempt 1)}} · {{or row.duration "—"}} · {{row.at}}</span>
</span>
</div>
</td>
<td><Badge @status={{if row.ok "success" "danger"}} @text={{row.statusText}} @size="xxs" @hideStatusDot={{true}} @disableHumanize={{true}} /></td>
<td class="text-right"><FaIcon @icon="chevron-right" @size="xs" class="text-gray-400" /></td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<States::Empty @icon="paper-plane" @title={{t "developers.events.panel.no-deliveries"}} @message={{t "developers.events.panel.no-deliveries-message"}} class="py-4" />
{{/if}}
</:default>
</SectionCard>
{{else}}
<dl class="dev-kv mt-3">
<dt>{{t "developers.common.source"}}</dt>
<dd><Badge @status="info" @text={{this.event.source}} @size="xs" @hideStatusDot={{true}} @disableHumanize={{true}} /> {{#if this.event.method}}<span class="dev-method dev-method-{{lowercase this.event.method}} ml-1">{{this.event.method}}</span>{{/if}}</dd>
<dt>{{t "developers.webhooks.index.credential"}}</dt>
<dd>{{#if this.credentialName}}{{this.credentialName}}{{else}}<span class="dev-muted">—</span>{{/if}}</dd>
<dt>{{t "developers.events.panel.resource"}}</dt>
<dd>{{#if this.resourceId}}<span class="dev-tag"><span>{{this.resourceId}}</span></span>{{else}}<span class="dev-muted">—</span>{{/if}}</dd>
<dt>{{t "developers.common.description"}}</dt>
<dd>{{this.event.description}}</dd>
<dt>{{t "developers.common.time"}}</dt>
<dd>{{this.createdAt}}</dd>
<dt>{{t "developers.application.sidebar.items.channels"}}</dt>
<dd class="dev-inline-wrap">
{{#each this.channels as |channel|}}
<button type="button" class="dev-tag dev-tag-button" {{on "click" (fn this.watchChannel channel)}}><span>{{channel}}</span><FaIcon @icon="tower-broadcast" @size="xs" /></button>
{{/each}}
</dd>
</dl>
<div class="mt-4">
<CodeViewer @value={{this.event.data}} @title={{t "developers.events.panel.payload"}} @emptyText={{t "developers.component.api-event.data.no-data"}} />
</div>
{{/if}}
</TabNavigation>
</div>
127 changes: 127 additions & 0 deletions addon/components/api-event/panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { task } from 'ember-concurrency';
import { shortUrl, timeStamp, formatSeconds } from '../../utils/format';
import { eventIcon } from '../../utils/resource-descriptors';

/**
* The event detail: facts, payload and the webhook deliveries this event
* produced. Used by the drawer and the full page.
*/
export default class ApiEventPanelComponent extends Component {
@service store;
@service intl;
@service hostRouter;
@service developerPanels;
@service notifications;

@tracked deliveries = [];
@tracked error = null;
@tracked activeTab = 'overview';

constructor() {
super(...arguments);
this.load.perform();
}

get event() {
return this.args.resource ?? this.args.model;
}

get icon() {
return eventIcon(this.event?.event);
}

get createdAt() {
return timeStamp(this.event?.created_at, 'MMM d, yyyy HH:mm:ss');
}

get resourceId() {
const data = this.event?.data;

return data && typeof data === 'object' ? (data.public_id ?? data.id ?? null) : null;
}

get credentialName() {
const id = this.event?.api_credential_uuid;

if (!id) {
return null;
}

return this.store.peekRecord('api-credential', id)?.name ?? id;
}

get channels() {
const channels = [];
const companyId = this.event?.company_uuid;

if (companyId) {
channels.push(`company.${companyId}`);
}

if (this.event?.api_credential_uuid) {
channels.push(`api.${this.event.api_credential_uuid}`);
}

return channels;
}

get tabs() {
return [
{ id: 'overview', label: this.intl.t('developers.common.overview'), icon: 'circle-info' },
{ id: 'payload', label: this.intl.t('developers.events.panel.payload'), icon: 'code' },
{ id: 'deliveries', label: this.intl.t('developers.webhooks.deliveries.title'), icon: 'paper-plane', badge: this.deliveries.length || null },
];
}

get deliveryRows() {
return this.deliveries.map((attempt) => ({
attempt,
shortUrl: shortUrl(attempt.url),
ok: attempt.status === 'successful',
statusText: [attempt.status_code, attempt.reason_phrase].filter(Boolean).join(' '),
duration: formatSeconds(attempt.duration),
at: timeStamp(attempt.sent_at ?? attempt.created_at),
}));
}

@task *load() {
const event = this.event;

if (!event?.id) {
return;
}

try {
const deliveries = yield this.store.query('webhook-request-log', { api_event_uuid: event.id, limit: 50, sort: '-created_at' });
this.deliveries = Array.from(deliveries);
this.error = null;
} catch (error) {
this.error = error?.message ?? null;
}
}

@action setTab(tab) {
this.activeTab = tab.id;
}

@action openAttempt(attempt) {
return this.developerPanels.openAttempt(attempt);
}

@action openFullPage() {
return this.developerPanels.openFullPage('events.view', this.event);
}

@action watchChannel(name) {
this.args.close?.();
return this.hostRouter.transitionTo('console.developers.sockets.view', name);
}

@action copyId() {
return navigator.clipboard?.writeText(this.event.public_id).then(() => this.notifications.info(this.intl.t('developers.code-viewer.copied')));
}
}
6 changes: 6 additions & 0 deletions addon/components/api-event/source-cell.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<span class="dev-cell-text">
<Badge @status="info" @text={{or @value "api"}} @size="xs" @hideStatusDot={{true}} @disableHumanize={{true}} />
{{#if @row.method}}
<span class="dev-method dev-method-{{lowercase @row.method}} ml-1">{{@row.method}}</span>
{{/if}}
</span>
19 changes: 19 additions & 0 deletions addon/components/api-key/drawer-header.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div class="dev-drawer-header" ...attributes>
<div class="dev-identity dev-drawer-identity">
<span class="dev-identity-icon dev-identity-icon-lg {{this.tone}}"><FaIcon @icon="key" /></span>
<span class="dev-identity-text">
<span class="dev-identity-title dev-drawer-title" title={{this.apiKey.name}}>
<span class="dev-truncate">{{or this.apiKey.name (mask-key this.apiKey.key)}}</span>
<Badge @status={{this.environment.status}} @text={{t this.environment.key}} @size="xs" @disableHumanize={{true}} />
</span>
<span class="dev-identity-sub">
<ClickToCopy @value={{this.apiKey.key}}><span class="dev-tag"><span>{{this.apiKey.key}}</span><FaIcon @icon="copy" @size="xs" /></span></ClickToCopy>
</span>
</span>
</div>
<div class="dev-drawer-actions">
<Layout::Resource::ActionButtons @buttons={{@actionButtons}} @dropdownIcon="ellipsis" @dropdownSize="xs" @renderInPlace={{true}} />
<Button @size="xs" @icon="arrow-up-right-from-square" @helpText={{t "developers.common.open-full-page"}} @onClick={{this.openFullPage}} />
<Button @size="xs" @icon="times" @helpText={{t "developers.common.close"}} @onClick={{@onPressCancel}} />
</div>
</div>
41 changes: 41 additions & 0 deletions addon/components/api-key/drawer-header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

/**
* Drawer header for an API key: identity block, header actions, open full page, close.
* Rendered by the resource context panel in place of its default header.
*/
export default class ApiKeyDrawerHeaderComponent extends Component {
@service developerPanels;

get apiKey() {
return this.args.resource ?? this.args.model;
}

get isExpired() {
const expiresAt = this.apiKey?.expires_at;

return expiresAt ? new Date(expiresAt).getTime() < Date.now() : false;
}

get tone() {
if (this.isExpired) {
return 'is-red';
}

return this.apiKey?.test_mode ? 'is-amber' : 'is-green';
}

get environment() {
if (this.isExpired) {
return { status: 'danger', key: 'developers.api-keys.badges.expired' };
}

return this.apiKey?.test_mode ? { status: 'warning', key: 'developers.common.test' } : { status: 'success', key: 'developers.common.live' };
}

@action openFullPage() {
return this.developerPanels.openFullPage('api-keys.index', { queryParams: { view_api_key: this.apiKey.id } });
}
}
7 changes: 7 additions & 0 deletions addon/components/api-key/expiry-cell.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<span class="dev-cell-text" title={{this.title}}>
{{#if this.showBadge}}
<Badge @status={{this.badgeStatus}} @text={{this.text}} @size="xs" @hideStatusDot={{true}} @disableHumanize={{true}} />
{{else}}
{{this.text}}
{{/if}}
</span>
Loading
Loading