@oneuptime/common 10.2.0 → 10.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/ProjectOidc.ts +705 -0
- package/Server/API/ProjectOIDC.ts +73 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1778506655291-AddProjectOIDC.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AlertLabelRuleEngineService.ts +16 -0
- package/Server/Services/IncidentLabelRuleEngineService.ts +16 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +139 -26
- package/Server/Services/ProjectOidcService.ts +10 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +23 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +98 -3
- package/Tests/Utils/MetricUnitUtil.test.ts +38 -1
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +8 -0
- package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +155 -0
- package/Types/Permission.ts +42 -0
- package/UI/Components/Calendar/Calendar.css +257 -0
- package/UI/Components/Calendar/Calendar.tsx +22 -11
- package/Utils/MetricUnitUtil.ts +24 -0
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectOidc.js +727 -0
- package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -0
- package/build/dist/Server/API/ProjectOIDC.js +45 -0
- package/build/dist/Server/API/ProjectOIDC.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778506655291-AddProjectOIDC.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778506655291-AddProjectOIDC.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertLabelRuleEngineService.js +16 -0
- package/build/dist/Server/Services/AlertLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentLabelRuleEngineService.js +16 -0
- package/build/dist/Server/Services/IncidentLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +106 -17
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/ProjectOidcService.js +9 -0
- package/build/dist/Server/Services/ProjectOidcService.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +25 -8
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +71 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Tests/Utils/MetricUnitUtil.test.js +29 -1
- package/build/dist/Tests/Utils/MetricUnitUtil.test.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +86 -0
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -0
- package/build/dist/Types/Permission.js +40 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js +12 -10
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +22 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/package.json +1 -1
|
@@ -49,7 +49,19 @@ describe("MetricUnitUtil", () => {
|
|
|
49
49
|
test("returns percent family for '%'", () => {
|
|
50
50
|
const options: Array<{ value: string; label: string }> =
|
|
51
51
|
MetricUnitUtil.getCompatibleUnits("%");
|
|
52
|
-
expect(options).toEqual([
|
|
52
|
+
expect(options).toEqual([
|
|
53
|
+
{ value: "%", label: "Percent (%)" },
|
|
54
|
+
{ value: "1", label: "Fraction (0-1)" },
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("returns percent family for UCUM '1' (dimensionless / ratio)", () => {
|
|
59
|
+
const options: Array<{ value: string; label: string }> =
|
|
60
|
+
MetricUnitUtil.getCompatibleUnits("1");
|
|
61
|
+
expect(options).toEqual([
|
|
62
|
+
{ value: "%", label: "Percent (%)" },
|
|
63
|
+
{ value: "1", label: "Fraction (0-1)" },
|
|
64
|
+
]);
|
|
53
65
|
});
|
|
54
66
|
|
|
55
67
|
test("returns raw unit as sole option for unknown unit", () => {
|
|
@@ -69,6 +81,10 @@ describe("MetricUnitUtil", () => {
|
|
|
69
81
|
expect(MetricUnitUtil.getCanonicalUnitValue("percent")).toBe("%");
|
|
70
82
|
});
|
|
71
83
|
|
|
84
|
+
test("returns '%' for UCUM dimensionless '1' so the threshold UI doesn't default to a literal '1'", () => {
|
|
85
|
+
expect(MetricUnitUtil.getCanonicalUnitValue("1")).toBe("%");
|
|
86
|
+
});
|
|
87
|
+
|
|
72
88
|
test("passes through unknown units", () => {
|
|
73
89
|
expect(MetricUnitUtil.getCanonicalUnitValue("widgets")).toBe("widgets");
|
|
74
90
|
});
|
|
@@ -197,6 +213,26 @@ describe("MetricUnitUtil", () => {
|
|
|
197
213
|
}),
|
|
198
214
|
).toBe(2e9);
|
|
199
215
|
});
|
|
216
|
+
|
|
217
|
+
test("converts '%' to the fraction unit '1' by dividing by 100", () => {
|
|
218
|
+
expect(
|
|
219
|
+
MetricUnitUtil.convertToMetricUnit({
|
|
220
|
+
value: 50,
|
|
221
|
+
fromUnit: "%",
|
|
222
|
+
metricUnit: "1",
|
|
223
|
+
}),
|
|
224
|
+
).toBe(0.5);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("converts the fraction unit '1' to '%' by multiplying by 100", () => {
|
|
228
|
+
expect(
|
|
229
|
+
MetricUnitUtil.convertToMetricUnit({
|
|
230
|
+
value: 0.05,
|
|
231
|
+
fromUnit: "1",
|
|
232
|
+
metricUnit: "%",
|
|
233
|
+
}),
|
|
234
|
+
).toBe(5);
|
|
235
|
+
});
|
|
200
236
|
});
|
|
201
237
|
|
|
202
238
|
describe("hasCompatibleUnitFamily", () => {
|
|
@@ -204,6 +240,7 @@ describe("MetricUnitUtil", () => {
|
|
|
204
240
|
expect(MetricUnitUtil.hasCompatibleUnitFamily("bytes")).toBe(true);
|
|
205
241
|
expect(MetricUnitUtil.hasCompatibleUnitFamily("ms")).toBe(true);
|
|
206
242
|
expect(MetricUnitUtil.hasCompatibleUnitFamily("%")).toBe(true);
|
|
243
|
+
expect(MetricUnitUtil.hasCompatibleUnitFamily("1")).toBe(true);
|
|
207
244
|
expect(MetricUnitUtil.hasCompatibleUnitFamily("GB")).toBe(true);
|
|
208
245
|
});
|
|
209
246
|
|
|
@@ -41,4 +41,12 @@ export default interface MetricMonitorResponse {
|
|
|
41
41
|
* `metricResult` as before.
|
|
42
42
|
*/
|
|
43
43
|
seriesBreakdown?: Array<MetricSeriesResult> | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Native units (UCUM / OTel) per referenced metric name, lowercased.
|
|
46
|
+
* Loaded once when the monitor data is fetched. The criteria
|
|
47
|
+
* evaluator falls back to this when the query alias has no explicit
|
|
48
|
+
* `legendUnit` so threshold unit conversion (e.g. % vs the
|
|
49
|
+
* dimensionless "1" used by ratio metrics) still works.
|
|
50
|
+
*/
|
|
51
|
+
nativeUnitsByMetricName?: Dictionary<string> | undefined;
|
|
44
52
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import CalendarEvent from "../Calendar/CalendarEvent";
|
|
2
|
+
import OneUptimeDate from "../Date";
|
|
3
|
+
|
|
4
|
+
export interface UserOverrideRecord {
|
|
5
|
+
overrideUserId: string;
|
|
6
|
+
routeAlertsToUserId: string;
|
|
7
|
+
startsAt: Date;
|
|
8
|
+
endsAt: Date;
|
|
9
|
+
// null/undefined means global override (applies to all on-call duty policies)
|
|
10
|
+
onCallDutyPolicyId?: string | null | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface OverrideEventMeta {
|
|
14
|
+
isOverride: true;
|
|
15
|
+
originalUserId: string;
|
|
16
|
+
overrideUserId: string;
|
|
17
|
+
overrideStartsAt: Date;
|
|
18
|
+
overrideEndsAt: Date;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* CalendarEvent extends JSONObject so it accepts string-indexed metadata.
|
|
23
|
+
* We attach the override info under a known key so downstream consumers can
|
|
24
|
+
* detect and render the substitution distinctly.
|
|
25
|
+
*/
|
|
26
|
+
export const OVERRIDE_META_KEY: string = "_override";
|
|
27
|
+
|
|
28
|
+
export default class UserOverrideUtil {
|
|
29
|
+
/**
|
|
30
|
+
* Returns true when this override should be applied on top of the schedule
|
|
31
|
+
* events. Global overrides always apply; policy-scoped overrides apply to
|
|
32
|
+
* any schedule, since a schedule's calendar shows coverage information for
|
|
33
|
+
* every user who could potentially be paged through it.
|
|
34
|
+
*/
|
|
35
|
+
public static isOverrideApplicable(_override: UserOverrideRecord): boolean {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public static applyOverridesToEvents(data: {
|
|
40
|
+
events: Array<CalendarEvent>;
|
|
41
|
+
overrides: Array<UserOverrideRecord>;
|
|
42
|
+
}): Array<CalendarEvent> {
|
|
43
|
+
const applicable: Array<UserOverrideRecord> = data.overrides.filter(
|
|
44
|
+
UserOverrideUtil.isOverrideApplicable,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (applicable.length === 0) {
|
|
48
|
+
return data.events;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let working: Array<CalendarEvent> = data.events;
|
|
52
|
+
|
|
53
|
+
for (const override of applicable) {
|
|
54
|
+
const next: Array<CalendarEvent> = [];
|
|
55
|
+
for (const event of working) {
|
|
56
|
+
next.push(...UserOverrideUtil.splitEventByOverride(event, override));
|
|
57
|
+
}
|
|
58
|
+
working = next;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return UserOverrideUtil.reassignEventIds(working);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private static splitEventByOverride(
|
|
65
|
+
event: CalendarEvent,
|
|
66
|
+
override: UserOverrideRecord,
|
|
67
|
+
): Array<CalendarEvent> {
|
|
68
|
+
if (event.title !== override.overrideUserId) {
|
|
69
|
+
return [event];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Override window doesn't overlap event window at all.
|
|
73
|
+
if (
|
|
74
|
+
OneUptimeDate.isAfter(override.startsAt, event.end) ||
|
|
75
|
+
OneUptimeDate.isSame(override.startsAt, event.end) ||
|
|
76
|
+
OneUptimeDate.isBefore(override.endsAt, event.start) ||
|
|
77
|
+
OneUptimeDate.isSame(override.endsAt, event.start)
|
|
78
|
+
) {
|
|
79
|
+
return [event];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const overrideStart: Date = OneUptimeDate.isAfter(
|
|
83
|
+
override.startsAt,
|
|
84
|
+
event.start,
|
|
85
|
+
)
|
|
86
|
+
? override.startsAt
|
|
87
|
+
: event.start;
|
|
88
|
+
|
|
89
|
+
const overrideEnd: Date = OneUptimeDate.isBefore(override.endsAt, event.end)
|
|
90
|
+
? override.endsAt
|
|
91
|
+
: event.end;
|
|
92
|
+
|
|
93
|
+
const segments: Array<CalendarEvent> = [];
|
|
94
|
+
|
|
95
|
+
// Segment before the override window — original user remains on call.
|
|
96
|
+
if (OneUptimeDate.isBefore(event.start, overrideStart)) {
|
|
97
|
+
segments.push({
|
|
98
|
+
...event,
|
|
99
|
+
end: overrideStart,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Override window — substitute user takes over.
|
|
104
|
+
const meta: OverrideEventMeta = {
|
|
105
|
+
isOverride: true,
|
|
106
|
+
originalUserId: override.overrideUserId,
|
|
107
|
+
overrideUserId: override.routeAlertsToUserId,
|
|
108
|
+
overrideStartsAt: override.startsAt,
|
|
109
|
+
overrideEndsAt: override.endsAt,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
segments.push({
|
|
113
|
+
...event,
|
|
114
|
+
start: overrideStart,
|
|
115
|
+
end: overrideEnd,
|
|
116
|
+
title: override.routeAlertsToUserId,
|
|
117
|
+
[OVERRIDE_META_KEY]: meta as unknown as never,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Segment after the override window — original user resumes.
|
|
121
|
+
if (OneUptimeDate.isAfter(event.end, overrideEnd)) {
|
|
122
|
+
segments.push({
|
|
123
|
+
...event,
|
|
124
|
+
start: overrideEnd,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return segments;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private static reassignEventIds(
|
|
132
|
+
events: Array<CalendarEvent>,
|
|
133
|
+
): Array<CalendarEvent> {
|
|
134
|
+
let id: number = 1;
|
|
135
|
+
return events.map((event: CalendarEvent) => {
|
|
136
|
+
return { ...event, id: id++ };
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public static getOverrideMeta(
|
|
141
|
+
event: CalendarEvent,
|
|
142
|
+
): OverrideEventMeta | null {
|
|
143
|
+
const meta: unknown = (event as unknown as Record<string, unknown>)[
|
|
144
|
+
OVERRIDE_META_KEY
|
|
145
|
+
];
|
|
146
|
+
if (
|
|
147
|
+
meta &&
|
|
148
|
+
typeof meta === "object" &&
|
|
149
|
+
(meta as { isOverride?: boolean }).isOverride === true
|
|
150
|
+
) {
|
|
151
|
+
return meta as OverrideEventMeta;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
package/Types/Permission.ts
CHANGED
|
@@ -527,6 +527,11 @@ enum Permission {
|
|
|
527
527
|
EditProjectSSO = "EditProjectSSO",
|
|
528
528
|
ReadProjectSSO = "ReadProjectSSO",
|
|
529
529
|
|
|
530
|
+
CreateProjectOIDC = "CreateProjectOIDC",
|
|
531
|
+
DeleteProjectOIDC = "DeleteProjectOIDC",
|
|
532
|
+
EditProjectOIDC = "EditProjectOIDC",
|
|
533
|
+
ReadProjectOIDC = "ReadProjectOIDC",
|
|
534
|
+
|
|
530
535
|
CreateStatusPageSSO = "CreateStatusPageSSO",
|
|
531
536
|
DeleteStatusPageSSO = "DeleteStatusPageSSO",
|
|
532
537
|
EditStatusPageSSO = "EditStatusPageSSO",
|
|
@@ -2949,6 +2954,43 @@ export class PermissionHelper {
|
|
|
2949
2954
|
group: PermissionGroup.Settings,
|
|
2950
2955
|
},
|
|
2951
2956
|
|
|
2957
|
+
{
|
|
2958
|
+
permission: Permission.CreateProjectOIDC,
|
|
2959
|
+
title: "Create Project OIDC",
|
|
2960
|
+
description: "This permission can create Project OIDC in this project.",
|
|
2961
|
+
isAssignableToTenant: true,
|
|
2962
|
+
isAccessControlPermission: false,
|
|
2963
|
+
isRolePermission: false,
|
|
2964
|
+
group: PermissionGroup.Settings,
|
|
2965
|
+
},
|
|
2966
|
+
{
|
|
2967
|
+
permission: Permission.DeleteProjectOIDC,
|
|
2968
|
+
title: "Delete Project OIDC",
|
|
2969
|
+
description: "This permission can delete Project OIDC in this project.",
|
|
2970
|
+
isAssignableToTenant: true,
|
|
2971
|
+
isAccessControlPermission: false,
|
|
2972
|
+
isRolePermission: false,
|
|
2973
|
+
group: PermissionGroup.Settings,
|
|
2974
|
+
},
|
|
2975
|
+
{
|
|
2976
|
+
permission: Permission.EditProjectOIDC,
|
|
2977
|
+
title: "Edit Project OIDC",
|
|
2978
|
+
description: "This permission can edit Project OIDC in this project.",
|
|
2979
|
+
isAssignableToTenant: true,
|
|
2980
|
+
isAccessControlPermission: false,
|
|
2981
|
+
isRolePermission: false,
|
|
2982
|
+
group: PermissionGroup.Settings,
|
|
2983
|
+
},
|
|
2984
|
+
{
|
|
2985
|
+
permission: Permission.ReadProjectOIDC,
|
|
2986
|
+
title: "Read Project OIDC",
|
|
2987
|
+
description: "This permission can read Project OIDC in this project.",
|
|
2988
|
+
isAssignableToTenant: true,
|
|
2989
|
+
isAccessControlPermission: false,
|
|
2990
|
+
isRolePermission: false,
|
|
2991
|
+
group: PermissionGroup.Settings,
|
|
2992
|
+
},
|
|
2993
|
+
|
|
2952
2994
|
{
|
|
2953
2995
|
permission: Permission.CreateStatusPageSSO,
|
|
2954
2996
|
title: "Create Status Page SSO",
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/* OneUptime Calendar – modern, minimal overrides for react-big-calendar.
|
|
2
|
+
Scoped via the .oneuptime-calendar wrapper so we don't leak styles. */
|
|
3
|
+
|
|
4
|
+
.oneuptime-calendar .rbc-calendar {
|
|
5
|
+
font-family: inherit;
|
|
6
|
+
color: #111827; /* gray-900 */
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* ---------- Toolbar ---------- */
|
|
10
|
+
.oneuptime-calendar .rbc-toolbar {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-wrap: wrap;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: space-between;
|
|
15
|
+
gap: 0.75rem;
|
|
16
|
+
margin-bottom: 1rem;
|
|
17
|
+
padding: 0;
|
|
18
|
+
font-size: 0.875rem;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.oneuptime-calendar .rbc-toolbar .rbc-toolbar-label {
|
|
22
|
+
flex: 1 1 auto;
|
|
23
|
+
text-align: center;
|
|
24
|
+
font-size: 1rem;
|
|
25
|
+
font-weight: 600;
|
|
26
|
+
color: #111827; /* gray-900 */
|
|
27
|
+
letter-spacing: -0.01em;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.oneuptime-calendar .rbc-toolbar .rbc-btn-group {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
border: 1px solid #e5e7eb; /* gray-200 */
|
|
33
|
+
background-color: #ffffff;
|
|
34
|
+
border-radius: 0.5rem;
|
|
35
|
+
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.04);
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.oneuptime-calendar .rbc-toolbar .rbc-btn-group button {
|
|
40
|
+
border: none;
|
|
41
|
+
background-color: transparent;
|
|
42
|
+
color: #4b5563; /* gray-600 */
|
|
43
|
+
padding: 0.4rem 0.85rem;
|
|
44
|
+
font-size: 0.8125rem;
|
|
45
|
+
font-weight: 500;
|
|
46
|
+
line-height: 1.25rem;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
transition:
|
|
49
|
+
background-color 120ms ease,
|
|
50
|
+
color 120ms ease;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.oneuptime-calendar .rbc-toolbar .rbc-btn-group button + button {
|
|
54
|
+
border-left: 1px solid #e5e7eb;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.oneuptime-calendar .rbc-toolbar .rbc-btn-group button:hover,
|
|
58
|
+
.oneuptime-calendar .rbc-toolbar .rbc-btn-group button:focus {
|
|
59
|
+
background-color: #f9fafb; /* gray-50 */
|
|
60
|
+
color: #111827;
|
|
61
|
+
outline: none;
|
|
62
|
+
box-shadow: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.oneuptime-calendar .rbc-toolbar .rbc-btn-group button.rbc-active,
|
|
66
|
+
.oneuptime-calendar .rbc-toolbar .rbc-btn-group button.rbc-active:hover,
|
|
67
|
+
.oneuptime-calendar .rbc-toolbar .rbc-btn-group button.rbc-active:focus {
|
|
68
|
+
background-color: #4f46e5; /* indigo-600 */
|
|
69
|
+
color: #ffffff;
|
|
70
|
+
box-shadow: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ---------- Calendar surface ---------- */
|
|
74
|
+
.oneuptime-calendar .rbc-month-view,
|
|
75
|
+
.oneuptime-calendar .rbc-time-view,
|
|
76
|
+
.oneuptime-calendar .rbc-agenda-view {
|
|
77
|
+
border: 1px solid #e5e7eb; /* gray-200 */
|
|
78
|
+
border-radius: 0.75rem;
|
|
79
|
+
background-color: #ffffff;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.oneuptime-calendar .rbc-month-view {
|
|
84
|
+
background-color: #ffffff;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ---------- Headers ---------- */
|
|
88
|
+
.oneuptime-calendar .rbc-header,
|
|
89
|
+
.oneuptime-calendar .rbc-time-header-content > .rbc-row > .rbc-header,
|
|
90
|
+
.oneuptime-calendar .rbc-time-header .rbc-header {
|
|
91
|
+
padding: 0.625rem 0.5rem;
|
|
92
|
+
font-size: 0.75rem;
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
color: #6b7280; /* gray-500 */
|
|
95
|
+
text-transform: uppercase;
|
|
96
|
+
letter-spacing: 0.04em;
|
|
97
|
+
background-color: #f9fafb; /* gray-50 */
|
|
98
|
+
border-bottom: 1px solid #e5e7eb;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.oneuptime-calendar .rbc-header + .rbc-header,
|
|
102
|
+
.oneuptime-calendar .rbc-time-header-content .rbc-header + .rbc-header {
|
|
103
|
+
border-left: 1px solid #f3f4f6; /* gray-100 */
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.oneuptime-calendar .rbc-time-header.rbc-overflowing {
|
|
107
|
+
border-right: none;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.oneuptime-calendar .rbc-time-header-content {
|
|
111
|
+
border-left: 1px solid #e5e7eb;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ---------- Month grid ---------- */
|
|
115
|
+
.oneuptime-calendar .rbc-month-row + .rbc-month-row {
|
|
116
|
+
border-top: 1px solid #f3f4f6; /* gray-100 */
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.oneuptime-calendar .rbc-day-bg + .rbc-day-bg {
|
|
120
|
+
border-left: 1px solid #f3f4f6;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.oneuptime-calendar .rbc-date-cell {
|
|
124
|
+
padding: 0.35rem 0.5rem;
|
|
125
|
+
text-align: right;
|
|
126
|
+
font-size: 0.8125rem;
|
|
127
|
+
font-weight: 500;
|
|
128
|
+
color: #374151; /* gray-700 */
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.oneuptime-calendar .rbc-date-cell.rbc-now {
|
|
132
|
+
color: #4f46e5; /* indigo-600 */
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.oneuptime-calendar .rbc-off-range {
|
|
137
|
+
color: #d1d5db; /* gray-300 */
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.oneuptime-calendar .rbc-off-range-bg {
|
|
141
|
+
background-color: #fafafa;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.oneuptime-calendar .rbc-today {
|
|
145
|
+
background-color: #eef2ff; /* indigo-50 */
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* ---------- Time view (week/day) ---------- */
|
|
149
|
+
.oneuptime-calendar .rbc-time-view .rbc-time-gutter,
|
|
150
|
+
.oneuptime-calendar .rbc-time-view .rbc-time-content > * + * > * {
|
|
151
|
+
border-color: #f3f4f6;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.oneuptime-calendar .rbc-time-view .rbc-time-gutter .rbc-timeslot-group {
|
|
155
|
+
font-size: 0.75rem;
|
|
156
|
+
color: #6b7280;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.oneuptime-calendar .rbc-timeslot-group {
|
|
160
|
+
border-bottom: 1px solid #f3f4f6;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.oneuptime-calendar .rbc-time-content {
|
|
164
|
+
border-top: 1px solid #e5e7eb;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.oneuptime-calendar .rbc-time-content > * + * > * {
|
|
168
|
+
border-left: 1px solid #f3f4f6;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.oneuptime-calendar .rbc-current-time-indicator {
|
|
172
|
+
background-color: #ef4444; /* red-500 */
|
|
173
|
+
height: 2px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.oneuptime-calendar .rbc-time-slot {
|
|
177
|
+
color: #9ca3af; /* gray-400 */
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.oneuptime-calendar .rbc-label {
|
|
181
|
+
padding: 0 0.5rem;
|
|
182
|
+
font-size: 0.75rem;
|
|
183
|
+
color: #6b7280;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* ---------- Events ---------- */
|
|
187
|
+
.oneuptime-calendar .rbc-event,
|
|
188
|
+
.oneuptime-calendar .rbc-day-slot .rbc-background-event {
|
|
189
|
+
border: none;
|
|
190
|
+
border-radius: 0.375rem;
|
|
191
|
+
padding: 0.2rem 0.5rem;
|
|
192
|
+
font-size: 0.75rem;
|
|
193
|
+
font-weight: 500;
|
|
194
|
+
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.08);
|
|
195
|
+
transition:
|
|
196
|
+
transform 120ms ease,
|
|
197
|
+
box-shadow 120ms ease,
|
|
198
|
+
filter 120ms ease;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.oneuptime-calendar .rbc-event:hover,
|
|
202
|
+
.oneuptime-calendar .rbc-day-slot .rbc-background-event:hover {
|
|
203
|
+
filter: brightness(0.97);
|
|
204
|
+
box-shadow: 0 4px 10px -2px rgb(0 0 0 / 0.12);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.oneuptime-calendar .rbc-event:focus,
|
|
208
|
+
.oneuptime-calendar .rbc-event.rbc-selected {
|
|
209
|
+
outline: 2px solid #4f46e5; /* indigo-600 */
|
|
210
|
+
outline-offset: 1px;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.oneuptime-calendar .rbc-event-label {
|
|
214
|
+
font-size: 0.6875rem;
|
|
215
|
+
opacity: 0.85;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.oneuptime-calendar .rbc-show-more {
|
|
219
|
+
color: #4f46e5;
|
|
220
|
+
font-weight: 500;
|
|
221
|
+
background-color: transparent;
|
|
222
|
+
padding: 0.125rem 0.375rem;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* ---------- Agenda view ---------- */
|
|
226
|
+
.oneuptime-calendar .rbc-agenda-view table.rbc-agenda-table {
|
|
227
|
+
border: none;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.oneuptime-calendar .rbc-agenda-view table.rbc-agenda-table thead > tr > th {
|
|
231
|
+
background-color: #f9fafb;
|
|
232
|
+
color: #6b7280;
|
|
233
|
+
font-size: 0.75rem;
|
|
234
|
+
font-weight: 600;
|
|
235
|
+
text-transform: uppercase;
|
|
236
|
+
letter-spacing: 0.04em;
|
|
237
|
+
padding: 0.625rem 0.75rem;
|
|
238
|
+
border-bottom: 1px solid #e5e7eb;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.oneuptime-calendar .rbc-agenda-view table.rbc-agenda-table tbody > tr > td {
|
|
242
|
+
padding: 0.625rem 0.75rem;
|
|
243
|
+
font-size: 0.8125rem;
|
|
244
|
+
color: #374151;
|
|
245
|
+
border-top: 1px solid #f3f4f6;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.oneuptime-calendar .rbc-agenda-view table.rbc-agenda-table tbody > tr + tr {
|
|
249
|
+
border-top: none;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.oneuptime-calendar .rbc-agenda-empty {
|
|
253
|
+
padding: 1.5rem;
|
|
254
|
+
text-align: center;
|
|
255
|
+
color: #9ca3af;
|
|
256
|
+
font-size: 0.875rem;
|
|
257
|
+
}
|
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
DateLocalizer,
|
|
11
11
|
EventPropGetter,
|
|
12
12
|
momentLocalizer,
|
|
13
|
+
View,
|
|
13
14
|
} from "react-big-calendar";
|
|
14
15
|
import "react-big-calendar/lib/css/react-big-calendar.css";
|
|
16
|
+
import "./Calendar.css";
|
|
15
17
|
|
|
16
18
|
const localizer: DateLocalizer = momentLocalizer(moment);
|
|
17
19
|
|
|
@@ -29,6 +31,8 @@ export enum DefaultCalendarView {
|
|
|
29
31
|
Agenda = "agenda",
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
const CALENDAR_VIEWS: View[] = ["month", "week", "day", "agenda"];
|
|
35
|
+
|
|
32
36
|
const CalendarElement: FunctionComponent<ComponentProps> = (
|
|
33
37
|
props: ComponentProps,
|
|
34
38
|
): ReactElement => {
|
|
@@ -43,33 +47,40 @@ const CalendarElement: FunctionComponent<ComponentProps> = (
|
|
|
43
47
|
): { className?: string | undefined; style?: React.CSSProperties } => {
|
|
44
48
|
const backgroundColor: string =
|
|
45
49
|
event.color?.toString() || Blue500.toString();
|
|
50
|
+
|
|
51
|
+
const computedTextColor: string =
|
|
52
|
+
event.textColor?.toString() ||
|
|
53
|
+
(Color.shouldUseDarkText(new Color(backgroundColor))
|
|
54
|
+
? "#111827"
|
|
55
|
+
: "#ffffff");
|
|
56
|
+
|
|
46
57
|
const style: React.CSSProperties = {
|
|
47
|
-
backgroundColor
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
color:
|
|
51
|
-
event.textColor?.toString() ||
|
|
52
|
-
Color.shouldUseDarkText(new Color(backgroundColor))
|
|
53
|
-
? "#000000"
|
|
54
|
-
: "#ffffff",
|
|
58
|
+
backgroundColor,
|
|
59
|
+
color: computedTextColor,
|
|
60
|
+
borderRadius: "0.375rem",
|
|
55
61
|
border: "0px",
|
|
56
62
|
display: "block",
|
|
57
63
|
};
|
|
58
64
|
|
|
59
65
|
return {
|
|
60
|
-
style
|
|
66
|
+
style,
|
|
61
67
|
};
|
|
62
68
|
};
|
|
63
69
|
|
|
64
70
|
return (
|
|
65
|
-
<div
|
|
71
|
+
<div
|
|
72
|
+
id={props.id}
|
|
73
|
+
className="oneuptime-calendar mt-5 h-[42rem] rounded-xl bg-white"
|
|
74
|
+
>
|
|
66
75
|
<Calendar
|
|
67
76
|
defaultDate={defaultDate}
|
|
68
77
|
events={props.events}
|
|
69
78
|
localizer={localizer}
|
|
70
79
|
showMultiDayTimes
|
|
71
|
-
|
|
80
|
+
views={CALENDAR_VIEWS}
|
|
81
|
+
defaultView={props.defaultCalendarView || "week"}
|
|
72
82
|
eventPropGetter={eventStyleGetter}
|
|
83
|
+
popup
|
|
73
84
|
onRangeChange={(range: Date[] | { start: Date; end: Date }) => {
|
|
74
85
|
if (Array.isArray(range)) {
|
|
75
86
|
return props.onRangeChange({
|
package/Utils/MetricUnitUtil.ts
CHANGED
|
@@ -113,6 +113,20 @@ const percentUnits: Array<UnitDefinition> = [
|
|
|
113
113
|
aliases: ["%", "percent"],
|
|
114
114
|
toCanonical: 1,
|
|
115
115
|
},
|
|
116
|
+
/*
|
|
117
|
+
* UCUM "1" is OTel's dimensionless marker for ratio metrics — values in
|
|
118
|
+
* [0, 1] that represent a fraction (e.g. system.filesystem.utilization).
|
|
119
|
+
* Treating it as part of the percent family lets the threshold UI offer
|
|
120
|
+
* both "%" and "Fraction (0-1)" as input units. One unit of "1" equals
|
|
121
|
+
* 100% in the canonical, so a fraction threshold of 0.5 converts to a
|
|
122
|
+
* stored canonical value of 50.
|
|
123
|
+
*/
|
|
124
|
+
{
|
|
125
|
+
value: "1",
|
|
126
|
+
label: "Fraction (0-1)",
|
|
127
|
+
aliases: ["1"],
|
|
128
|
+
toCanonical: 100,
|
|
129
|
+
},
|
|
116
130
|
];
|
|
117
131
|
|
|
118
132
|
const bitUnits: Array<UnitDefinition> = [
|
|
@@ -206,6 +220,11 @@ export default class MetricUnitUtil {
|
|
|
206
220
|
* is what the threshold dropdown should default to when nothing is
|
|
207
221
|
* selected yet. Returns the input unit unchanged if the family is
|
|
208
222
|
* unknown.
|
|
223
|
+
*
|
|
224
|
+
* Special case for fraction metrics (unit "1"): users think in percent,
|
|
225
|
+
* not 0-1 fractions, so the dropdown defaults to "%" rather than the
|
|
226
|
+
* raw "1" — which used to render an unlabelled "1" next to the value
|
|
227
|
+
* input and read as a typo.
|
|
209
228
|
*/
|
|
210
229
|
public static getCanonicalUnitValue(
|
|
211
230
|
metricUnit: string | undefined,
|
|
@@ -223,6 +242,11 @@ export default class MetricUnitUtil {
|
|
|
223
242
|
metricUnit,
|
|
224
243
|
family,
|
|
225
244
|
);
|
|
245
|
+
|
|
246
|
+
if (family === percentUnits && def?.value === "1") {
|
|
247
|
+
return "%";
|
|
248
|
+
}
|
|
249
|
+
|
|
226
250
|
return def?.value || metricUnit;
|
|
227
251
|
}
|
|
228
252
|
|
|
@@ -118,6 +118,7 @@ import ProjectUserProfile from "./ProjectUserProfile";
|
|
|
118
118
|
import ProjectSmtpConfig from "./ProjectSmtpConfig";
|
|
119
119
|
//SSO
|
|
120
120
|
import ProjectSSO from "./ProjectSso";
|
|
121
|
+
import ProjectOIDC from "./ProjectOidc";
|
|
121
122
|
import PromoCode from "./PromoCode";
|
|
122
123
|
import EnterpriseLicense from "./EnterpriseLicense";
|
|
123
124
|
import OpenSourceDeployment from "./OpenSourceDeployment";
|
|
@@ -405,6 +406,7 @@ const AllModelTypes = [
|
|
|
405
406
|
WorkflowVariables,
|
|
406
407
|
WorkflowLog,
|
|
407
408
|
ProjectSSO,
|
|
409
|
+
ProjectOIDC,
|
|
408
410
|
StatusPageSSO,
|
|
409
411
|
StatusPageSCIM,
|
|
410
412
|
MonitorProbe,
|