Skip to content

Commit de88bfb

Browse files
committed
WIP
1 parent 985c9c7 commit de88bfb

7 files changed

Lines changed: 231 additions & 29 deletions

File tree

public/assets/css/src/default.css

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,31 @@ table.client-table {
104104
font-weight: bolder;
105105
}
106106

107-
.confirm-action {}
107+
108108

109109
form.pure-form-stacked .full-width {
110110
width: 100%;
111111
}
112+
113+
/* Form loading state */
114+
.form-loading-spinner {
115+
display: inline-block;
116+
width: 0.85em;
117+
height: 0.85em;
118+
border: 2px solid currentColor;
119+
border-top-color: transparent;
120+
border-radius: 50%;
121+
animation: form-spinner-rotate 0.7s linear infinite;
122+
vertical-align: middle;
123+
margin-right: 0.4em;
124+
opacity: 0.8;
125+
}
126+
127+
@keyframes form-spinner-rotate {
128+
to { transform: rotate(360deg); }
129+
}
130+
131+
button[disabled].loading {
132+
opacity: 0.7;
133+
cursor: not-allowed;
134+
}

public/assets/js/src/default.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
(function() {
32

43
// Attach `confirm-action` click event to all elements with the `confirm-action` class.
@@ -19,4 +18,17 @@
1918
}
2019
});
2120
});
21+
22+
// Handle forms with loading state
23+
document.querySelectorAll('form.form-with-loading-state').forEach(form => {
24+
form.addEventListener('submit', function (event) {
25+
const submitter = event.submitter || this.querySelector('button[type="submit"]');
26+
if (submitter) {
27+
const loadingText = submitter.getAttribute('data-loading-text') || 'Processing...';
28+
submitter.disabled = true;
29+
submitter.classList.add('loading');
30+
submitter.innerHTML = `<span class="form-loading-spinner"></span> ${loadingText}`;
31+
}
32+
});
33+
});
2234
})();

src/Controllers/Admin/FederationTestController.php

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,15 @@ public function federationDiscovery(Request $request): Response
182182
$isFormSubmitted = false;
183183
$entities = [];
184184
$forceRefresh = false;
185+
$filterEntityTypes = [];
186+
$filterTrustMarkTypes = '';
187+
$filterQuery = '';
188+
$sortBy = 'entity_id';
189+
$sortOrder = 'asc';
190+
$pageLimit = 50;
191+
$pageFrom = null;
192+
$nextPageToken = null;
193+
$totalCount = 0;
185194

186195
if ($request->isMethod(Request::METHOD_POST)) {
187196
$isFormSubmitted = true;
@@ -190,12 +199,60 @@ public function federationDiscovery(Request $request): Response
190199
throw new OidcException('Empty Trust Anchor ID.');
191200

192201
$forceRefresh = $request->request->getBoolean('forceRefresh');
202+
$filterEntityTypes = $request->request->all('filterEntityTypes');
203+
$filterTrustMarkTypes = $request->request->getString('filterTrustMarkTypes');
204+
$filterQuery = $request->request->getString('filterQuery');
205+
$sortBy = $request->request->getString('sortBy', 'entity_id');
206+
$sortOrder = $request->request->getString('sortOrder', 'asc');
207+
$pageLimit = $request->request->getInt('pageLimit', 50);
208+
$pageFrom = $request->request->get('pageFrom');
193209

194210
try {
195-
$entities = $this->federationWithArrayLogger->federationDiscovery()->discoverEntityIds(
211+
$entityCollection = $this->federationWithArrayLogger->federationDiscovery()->discover(
196212
trustAnchorId: $trustAnchorId,
197213
forceRefresh: $forceRefresh,
198214
);
215+
216+
// 1. Filtering
217+
$criteria = array_filter([
218+
'entity_type' => $filterEntityTypes,
219+
'trust_mark_type' => $this->helpers->str()->convertTextToArray($filterTrustMarkTypes),
220+
'query' => $filterQuery,
221+
]);
222+
if (!empty($criteria)) {
223+
$entityCollection->filter($criteria);
224+
}
225+
226+
$totalCount = count($entityCollection->getEntities());
227+
228+
// 2. Sorting
229+
$claimPaths = match ($sortBy) {
230+
'display_name' => [
231+
['metadata', EntityTypesEnum::OpenIdProvider->value, 'display_name'],
232+
['metadata', EntityTypesEnum::FederationEntity->value, 'display_name'],
233+
['metadata', EntityTypesEnum::OpenIdRelyingParty->value, 'display_name'],
234+
],
235+
'organization_name' => [
236+
['metadata', EntityTypesEnum::OpenIdProvider->value, 'organization_name'],
237+
['metadata', EntityTypesEnum::FederationEntity->value, 'organization_name'],
238+
['metadata', EntityTypesEnum::OpenIdRelyingParty->value, 'organization_name'],
239+
],
240+
default => [['sub']],
241+
};
242+
$entityCollection->sort($claimPaths, $sortOrder);
243+
244+
// 3. Pagination
245+
/** @var positive-int $pageLimit */
246+
$entityCollection->paginate($pageLimit, $pageFrom);
247+
248+
$nextPageToken = $entityCollection->getNextPageToken();
249+
250+
foreach ($entityCollection->getEntities() as $id => $payload) {
251+
$entities[] = [
252+
'id' => $id,
253+
'payload' => $payload,
254+
];
255+
}
199256
} catch (\Throwable $exception) {
200257
$this->arrayLogger->error(sprintf(
201258
'Error during entity discovery under Trust Anchor %s. Error was %s',
@@ -214,6 +271,8 @@ public function federationDiscovery(Request $request): Response
214271
$trustAnchorIds = [];
215272
}
216273

274+
$entityTypeOptions = array_map(fn (EntityTypesEnum $enum) => $enum->value, EntityTypesEnum::cases());
275+
217276
return $this->templateFactory->build(
218277
'oidc:tests/federation-discovery.twig',
219278
compact(
@@ -223,6 +282,16 @@ public function federationDiscovery(Request $request): Response
223282
'entities',
224283
'trustAnchorIds',
225284
'forceRefresh',
285+
'filterEntityTypes',
286+
'filterTrustMarkTypes',
287+
'filterQuery',
288+
'sortBy',
289+
'sortOrder',
290+
'pageLimit',
291+
'pageFrom',
292+
'nextPageToken',
293+
'totalCount',
294+
'entityTypeOptions',
226295
),
227296
RoutesEnum::AdminTestFederationDiscovery->value,
228297
);

templates/tests/federation-discovery.twig

Lines changed: 118 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
{% endif %}
2222
</p>
2323

24+
2425
<form method="post"
2526
action="{{ routes.urlAdminTestFederationDiscovery }}"
26-
class="pure-form pure-form-stacked">
27+
class="pure-form pure-form-stacked form-with-loading-state">
2728

2829
<fieldset>
30+
<legend>{{ 'Basic options'|trans }}</legend>
2931
<label for="trustAnchorId">{{ 'Trust Anchor ID'|trans }}</label>
3032
<input type="text"
3133
name="trustAnchorId"
@@ -35,23 +37,64 @@
3537
value="{{ trustAnchorId|default }}"
3638
>
3739
<span class="pure-form-message">Use any Trust Anchor ID for discovery</span>
40+
</fieldset>
3841

39-
<label for="forceRefresh">{{ 'Force refresh?'|trans }}</label>
40-
<input type="checkbox"
41-
name="forceRefresh"
42-
id="forceRefresh" value="1"
43-
{{ forceRefresh ? 'checked' : '' }}
44-
>
45-
<span class="pure-form-message">
46-
{% trans %}Check if you want to force the refresh of entities in federation.{% endtrans %}
47-
</span>
42+
<details>
43+
<summary><strong>{{ 'Filtering'|trans }}</strong></summary>
44+
<fieldset>
45+
<label for="filterEntityTypes">{{ 'Entity Types'|trans }}</label>
46+
<select name="filterEntityTypes[]" id="filterEntityTypes" multiple class="full-width" style="height: 100px;">
47+
{% for option in entityTypeOptions %}
48+
<option value="{{ option }}" {{ option in filterEntityTypes ? 'selected' : '' }}>{{ option }}</option>
49+
{% endfor %}
50+
</select>
4851

49-
<br>
50-
<button type="submit" class="pure-button ">{{ (actionText|default('Submit'))|trans }}</button>
51-
</fieldset>
52+
<label for="filterTrustMarkTypes">{{ 'Trust Mark Types (one per line)'|trans }}</label>
53+
<textarea name="filterTrustMarkTypes" id="filterTrustMarkTypes" class="full-width">{{ filterTrustMarkTypes }}</textarea>
54+
55+
<label for="filterQuery">{{ 'Search Query'|trans }}</label>
56+
<input type="text" name="filterQuery" id="filterQuery" value="{{ filterQuery }}" class="full-width">
57+
</fieldset>
58+
</details>
59+
60+
<details>
61+
<summary><strong>{{ 'Sorting'|trans }}</strong></summary>
62+
<fieldset>
63+
<label for="sortBy">{{ 'Sort by'|trans }}</label>
64+
<select name="sortBy" id="sortBy" class="full-width">
65+
<option value="entity_id" {{ sortBy == 'entity_id' ? 'selected' : '' }}>{{ 'Entity ID'|trans }}</option>
66+
<option value="display_name" {{ sortBy == 'display_name' ? 'selected' : '' }}>{{ 'Display Name'|trans }}</option>
67+
<option value="organization_name" {{ sortBy == 'organization_name' ? 'selected' : '' }}>{{ 'Organization Name'|trans }}</option>
68+
</select>
69+
70+
<label for="sortOrder_asc" class="pure-radio">
71+
<input type="radio" name="sortOrder" id="sortOrder_asc" value="asc" {{ sortOrder == 'asc' ? 'checked' : '' }}>
72+
{{ 'Ascending'|trans }}
73+
</label>
74+
<label for="sortOrder_desc" class="pure-radio">
75+
<input type="radio" name="sortOrder" id="sortOrder_desc" value="desc" {{ sortOrder == 'desc' ? 'checked' : '' }}>
76+
{{ 'Descending'|trans }}
77+
</label>
78+
</fieldset>
79+
</details>
80+
81+
<details>
82+
<summary><strong>{{ 'Pagination'|trans }}</strong></summary>
83+
<fieldset>
84+
<label for="pageLimit">{{ 'Page limit'|trans }}</label>
85+
<input type="number" name="pageLimit" id="pageLimit" value="{{ pageLimit }}" min="1" max="500">
86+
<input type="hidden" name="pageFrom" value="">
87+
</fieldset>
88+
</details>
89+
90+
<br>
91+
<button type="submit" class="pure-button pure-button-primary" data-loading-text="{{ 'Discovering...'|trans }}">{{ (actionText|default('Submit'))|trans }}</button>
5292
</form>
5393

5494
{% if isFormSubmitted|default %}
95+
<br>
96+
97+
<hr>
5598
<h4>{{ 'Log messages'|trans }}</h4>
5699
<p>
57100
{% if logMessages|default %}
@@ -63,16 +106,71 @@
63106
{% endif %}
64107
</p>
65108

66-
<h4>{{ 'Entities'|trans }}</h4>
67-
<p>
109+
<div class="pure-g">
110+
<div class="pure-u-1-2">
111+
<h4>{{ 'Entities'|trans }}</h4>
112+
</div>
113+
<div class="pure-u-1-2" style="text-align: right;">
114+
<p><strong>{{ 'Total matching entities:'|trans }} {{ totalCount }}</strong></p>
115+
</div>
116+
</div>
117+
68118
{% if entities|default %}
69-
<code class="code-box code-box-content">
70-
{{- entities|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES')) -}}
71-
</code>
119+
<table class="pure-table pure-table-horizontal full-width">
120+
<thead>
121+
<tr>
122+
<th>{{ 'Entity ID'|trans }}</th>
123+
<th>{{ 'Display Name'|trans }}</th>
124+
<th>{{ 'Types'|trans }}</th>
125+
</tr>
126+
</thead>
127+
<tbody>
128+
{% for entity in entities %}
129+
<tr>
130+
<td style="word-break: break-all;">{{ entity.id }}</td>
131+
<td>
132+
{% set displayName = '' %}
133+
{% for type in entityTypeOptions %}
134+
{% if entity.payload.metadata[type].display_name is defined and displayName == '' %}
135+
{% set displayName = entity.payload.metadata[type].display_name %}
136+
{% endif %}
137+
{% endfor %}
138+
{{ displayName }}
139+
</td>
140+
<td>
141+
{% set types = [] %}
142+
{% for type in entityTypeOptions %}
143+
{% if entity.payload.metadata[type] is defined %}
144+
{% set types = types|merge([type]) %}
145+
{% endif %}
146+
{% endfor %}
147+
{{ types|join(', ') }}
148+
</td>
149+
</tr>
150+
{% endfor %}
151+
</tbody>
152+
</table>
153+
154+
{% if nextPageToken %}
155+
<br>
156+
<form method="post" action="{{ routes.urlAdminTestFederationDiscovery }}" class="form-with-loading-state">
157+
<input type="hidden" name="trustAnchorId" value="{{ trustAnchorId }}">
158+
<input type="hidden" name="forceRefresh" value="{{ forceRefresh ? '1' : '0' }}">
159+
{% for type in filterEntityTypes %}
160+
<input type="hidden" name="filterEntityTypes[]" value="{{ type }}">
161+
{% endfor %}
162+
<input type="hidden" name="filterTrustMarkTypes" value="{{ filterTrustMarkTypes }}">
163+
<input type="hidden" name="filterQuery" value="{{ filterQuery }}">
164+
<input type="hidden" name="sortBy" value="{{ sortBy }}">
165+
<input type="hidden" name="sortOrder" value="{{ sortOrder }}">
166+
<input type="hidden" name="pageLimit" value="{{ pageLimit }}">
167+
<input type="hidden" name="pageFrom" value="{{ nextPageToken }}">
168+
<button type="submit" class="pure-button pure-button-primary" data-loading-text="{{ 'Loading...'|trans }}">{{ 'Next page'|trans }}</button>
169+
</form>
170+
{% endif %}
72171
{% else %}
73-
{{ 'No entities were found during the process.'|trans }}
172+
<p>{{ 'No entities were found matching the criteria.'|trans }}</p>
74173
{% endif %}
75-
</p>
76174

77175
{% endif %}
78176

templates/tests/trust-chain-resolution.twig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<form method="post"
1414
action="{{ routes.urlAdminTestTrustChainResolution }}"
15-
class="pure-form pure-form-stacked">
15+
class="pure-form pure-form-stacked form-with-loading-state">
1616

1717
<fieldset>
1818
<label for="leafEntityId">{{ 'Leaf Entity ID'|trans }}</label>
@@ -34,7 +34,7 @@
3434
<span class="pure-form-message">{{ 'Enter one Trust Anchor ID per line.'|trans }}</span>
3535

3636
<br>
37-
<button type="submit" class="pure-button ">{{ (actionText|default('Submit'))|trans }}</button>
37+
<button type="submit" class="pure-button " data-loading-text="{{ 'Resolving...'|trans }}">{{ (actionText|default('Submit'))|trans }}</button>
3838
</fieldset>
3939
</form>
4040

templates/tests/trust-mark-validation.twig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<form method="post"
1414
action="{{ routes.urlAdminTestTrustMarkValidation }}"
15-
class="pure-form pure-form-stacked">
15+
class="pure-form pure-form-stacked form-with-loading-state">
1616

1717
<fieldset>
1818
<label for="trustMarkType">{{ 'Trust Mark Type'|trans }}</label>
@@ -46,7 +46,7 @@
4646
<span class="pure-form-message"></span>
4747

4848
<br>
49-
<button type="submit" class="pure-button ">{{ (actionText|default('Submit'))|trans }}</button>
49+
<button type="submit" class="pure-button " data-loading-text="{{ 'Validating...'|trans }}">{{ (actionText|default('Submit'))|trans }}</button>
5050
</fieldset>
5151
</form>
5252

templates/tests/verifiable-credential-issuance.twig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
{{ 'You will be presented with a Credential Offer which you can use to test credential issuance.'|trans }}
2020
</p>
2121

22-
<form method="post" action="{{ authSourceActionRoute }}" class="pure-form pure-form-stacked">
22+
<form method="post" action="{{ authSourceActionRoute }}" class="pure-form pure-form-stacked form-with-loading-state">
2323
<fieldset>
2424
<label for="authSourceId">{{ 'Auth Source ID'|trans }}</label>
2525
<select name="authSourceId" id="authSourceId">
@@ -74,7 +74,7 @@
7474
</div>
7575

7676
<br>
77-
<button type="submit" class="pure-button">{{ 'Proceed'|trans }}</button>
77+
<button type="submit" class="pure-button" data-loading-text="{{ 'Proceeding...'|trans }}">{{ 'Proceed'|trans }}</button>
7878
</fieldset>
7979
</form>
8080
{% else %}

0 commit comments

Comments
 (0)