Skip to content

Commit 2de46df

Browse files
committed
Merge branch 'main' into ian-UID2-2084-add-disable-service-service-link
2 parents b5e389f + 9c900fb commit 2de46df

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+4384
-2110
lines changed

README.md

Lines changed: 171 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ When running locally, set the `is_auth_disabled` flag to `true` in [local-config
2121

2222
If you want to test with Okta OAuth, set the `is_auth_disabled` flag to `false`, and fill in the `okta_client_secret` with the value under "Okta localhost deployment" in 1Password.
2323

24-
### Working on the UI
25-
26-
Per the above setup steps, the UI runs on `http://localhost:8089/`. To see your local UI changes reflected in the browser, you will need to hard reload (`Crtl+Shift+R`) while on the specific web page you have changed.
27-
2824
## V2 API
2925

3026
The v2 API is based on individual route provider classes. Each class should provide exactly one endpoint and must implement IRouteProvider or IBlockingRouteProvider.
@@ -36,3 +32,174 @@ The v2 API is based on individual route provider classes. Each class should prov
3632
IRouteProvider requires a `getHandler` method, which should return a valid handler function - see `GetClientSideKeypairsBySite.java`. This method *must* be annotated with the Path, Method, and Roles annotations.
3733

3834
The route handler will automatically be wrapped by the Auth middleware based on the roles specified in the Roles annotation.
35+
36+
## Working on the UI
37+
Per the above setup steps, the UI runs on `http://localhost:8089/`. To see your local UI changes reflected in the browser, you will need to hard reload (`Crtl+Shift+R`) while on the specific web page you have changed.
38+
39+
### Page setup
40+
```html
41+
<html>
42+
<head>
43+
<!-- We use Unicode symbols for icons so need UTF-8 -->
44+
<meta charset="UTF-8">
45+
<link rel="stylesheet" href="/css/style.css">
46+
</head>
47+
<body>
48+
49+
<div class="main-content">
50+
<!-- Operations and controls will be added by `initializeOperations` here -->
51+
<div class="operations-container"></div>
52+
53+
<!-- Both success and error output will be added by `initializeOutput` here -->
54+
<div class="output-container"></div>
55+
</div>
56+
57+
<script type="module">
58+
document.addEventListener('DOMContentLoaded', function () {
59+
import { initializeOperations } from '/js/component/operations.js';
60+
import { initializeOutput } from '/js/component/output.js';
61+
62+
const operationConfig = {
63+
read: [ // Read-only operations (don't modify data)
64+
// See Operations example below
65+
],
66+
write: [ // Write operations (modify data)
67+
// ...
68+
],
69+
danger: [ // Dangerous operations (require explicit confirmation modal)
70+
// ...
71+
]
72+
}
73+
74+
initializeOperations(operationConfig);
75+
initializeOutput();
76+
});
77+
}
78+
</script>
79+
</body>
80+
</html>
81+
```
82+
83+
### Operations
84+
```javascript
85+
const myOperation = {
86+
id: 'operationId', // Required: Unique identifier
87+
title: 'Operation Title', // Required: Display name
88+
role: 'maintainer', // Required: `maintainer`, `elevated` or `superuser`
89+
inputs: [ // Optional: Inputs (see below)
90+
{
91+
name: 'field1', // Value of this input will be available as property matching name on `inputs` object below.
92+
label: 'Field 1', // Shown on the UI above the field
93+
type: 'text' // Optional, text by default
94+
}
95+
],
96+
description: 'text', // Optional: Description will appear within the accordion
97+
confirmationText: 'text', // Optional: Danger zone confirmation text, will replace stock text if used. Only works in danger zone.
98+
apiCall: { // Optional: API configuration
99+
method: 'POST',
100+
url: '', // Either hardcore URL here or use `getUrl` below.
101+
getUrl: (inputs) => { // Either construct a URL from the inputs or use `url` above.
102+
return `/api/my/api?field1=${encodeURIComponent(inputs.field1)}`
103+
},
104+
getPayload: (inputs) => { // Optional: Construct a JS object that will be sent as body
105+
return { field1: inputs.field1 };
106+
}
107+
},
108+
preProcess: async (inputs) => inputs, // Optional: Run before getUrl/getPayload and the API call
109+
postProcess: async (data, inputs) => data // Optional: Run after apiCall, if you want to adjust the response
110+
};
111+
112+
```
113+
114+
### Inputs
115+
- Inputs on the same page that share a `name` will have their values synced.
116+
117+
#### text (default)
118+
Basic small text input
119+
```javascript
120+
const myText = {
121+
name: 'fieldName',
122+
label: 'Field Label',
123+
required: true, // Optional: Exclude button won't be enabled until all fields with requireds are filled. Default false.
124+
size: 2, // Optional: grid span 1-3, grid has 3 columns. Default 1.
125+
placeholder: 'Enter text', // Optional
126+
defaultValue: 'default' // Optional
127+
};
128+
```
129+
130+
#### multi-line
131+
A larger multi-line text input box
132+
```javascript
133+
const myTextArea = {
134+
name: 'textArea',
135+
type: 'multi-line',
136+
label: 'Multi-line Text',
137+
placeholder: 'Enter text...',
138+
defaultValue: 'default text'
139+
};
140+
```
141+
142+
#### number
143+
Number-specific input, won't allow non-number text
144+
```javascript
145+
const myNum = {
146+
name: 'numField',
147+
type: 'number',
148+
label: 'Number Field'
149+
};
150+
```
151+
152+
#### date
153+
Date-specific input with date picker
154+
```javascript
155+
const myDate = {
156+
name: 'dateField',
157+
type: 'date',
158+
defaultValue: '2024-01-01'
159+
};
160+
```
161+
162+
#### checkbox
163+
```javascript
164+
const myCheckbox = {
165+
name: 'boolField',
166+
type: 'checkbox',
167+
label: 'Is it true?',
168+
defaultValue: true
169+
};
170+
```
171+
172+
#### select
173+
Dropdown to select one option from a list
174+
```javascript
175+
const myDropdown = {
176+
name: 'Dropdown',
177+
type: 'select',
178+
label: 'Select Option',
179+
options: [
180+
'simple', // Can pass as string if option values are same as UI text
181+
{ value: 'val', label: 'Display' } // Or as Objects if they are different
182+
],
183+
defaultValue: 'val'
184+
};
185+
```
186+
187+
#### multi-select
188+
Multiple checkboxes to allow selecting more than one option
189+
190+
```javascript
191+
const myMultiSelect = {
192+
name: 'Multi Select',
193+
type: 'multi-select',
194+
label: 'Multiple Options',
195+
required: true,
196+
options: [
197+
{
198+
value: 'OPTION_1',
199+
label: 'Option One',
200+
hint: 'Tooltip explanation' // Optional, appear as info icons next to options
201+
}
202+
],
203+
defaultValue: ['OPTION_1'] // Array or comma-separated string
204+
}
205+
```

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.uid2</groupId>
88
<artifactId>uid2-admin</artifactId>
9-
<version>6.6.0</version>
9+
<version>6.8.9</version>
1010

1111
<properties>
1212
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

src/main/java/com/uid2/admin/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ private static void setupMetrics(MicrometerMetricsOptions metricOptions) {
440440
// retrieve image version (will unify when uid2-common is used)
441441
String version = Optional.ofNullable(System.getenv("IMAGE_VERSION")).orElse("unknown");
442442
Gauge appStatus = Gauge
443-
.builder("app.status", () -> 1)
443+
.builder("app_status", () -> 1)
444444
.description("application version and status")
445445
.tags("version", version)
446446
.register(Metrics.globalRegistry);

src/main/java/com/uid2/admin/job/JobDispatcher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public JobDispatcher(
3939
this.maxRetries = maxRetries;
4040
this.clock = clock;
4141

42-
Gauge.builder("uid2.job_dispatcher.execution_duration_ms", this::getExecutionDuration)
42+
Gauge.builder("uid2_job_dispatcher_execution_duration_ms", this::getExecutionDuration)
4343
.tag("job_dispatcher", id)
4444
.description("gauge for " + id + " execution time")
4545
.register(Metrics.globalRegistry);

src/main/java/com/uid2/admin/vertx/AdminVerticle.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import com.uid2.admin.vertx.service.IService;
77
import com.uid2.shared.Const;
88
import com.uid2.shared.Utils;
9+
import com.uid2.shared.audit.Audit;
10+
import com.uid2.shared.audit.AuditParams;
911
import io.vertx.core.AbstractVerticle;
1012
import io.vertx.core.Promise;
1113
import io.vertx.core.http.HttpServerOptions;
@@ -34,6 +36,7 @@ public class AdminVerticle extends AbstractVerticle {
3436
private final TokenRefreshHandler tokenRefreshHandler;
3537
private final IService[] services;
3638
private final V2Router v2Router;
39+
private final Audit audit;
3740

3841
public AdminVerticle(JsonObject config,
3942
AuthProvider authProvider,
@@ -45,6 +48,7 @@ public AdminVerticle(JsonObject config,
4548
this.tokenRefreshHandler = tokenRefreshHandler;
4649
this.services = services;
4750
this.v2Router = v2Router;
51+
this.audit = new Audit("admin");
4852
}
4953

5054
public void start(Promise<Void> startPromise) {
@@ -113,6 +117,17 @@ private void handleUserinfo(RoutingContext rc) {
113117
List<String> groups = (List<String>) idJwt.getClaims().get("groups");
114118
jo.put("groups", new JsonArray(groups));
115119
jo.put("email", idJwt.getClaims().get("email"));
120+
121+
if (rc.get("user_details") == null) {
122+
JsonObject userDetails = new JsonObject();
123+
userDetails.put("email", idJwt.getClaims().get("email"));
124+
userDetails.put("sub", idJwt.getClaims().get("sub"));
125+
126+
LOGGER.info("Authenticated user accessing admin page - User: {}", userDetails.toString());
127+
rc.put("user_details", userDetails);
128+
this.audit.log(rc, new AuditParams());
129+
}
130+
116131
rc.response().setStatusCode(200).end(jo.toString());
117132
} catch (Exception e) {
118133
if (rc.session() != null) {

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", "version": "6.6", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/heads/v\\d+(?:\\.\\d+)?$" ], "cloudBuild": { "setVersionVariables": true, "buildNumber": { "enabled": true, "includeCommitId": { "when": "always" } } } }
1+
{ "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", "version": "6.8", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/heads/v\\d+(?:\\.\\d+)?$" ], "cloudBuild": { "setVersionVariables": true, "buildNumber": { "enabled": true, "includeCommitId": { "when": "always" } } } }

0 commit comments

Comments
 (0)