diff --git a/frontend/package.json b/frontend/package.json
index ee74ec79..22652405 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -5,6 +5,7 @@
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
+ "@hookform/resolvers": "^3.6.0",
"@mui/icons-material": "^5.15.11",
"@mui/material": "^5.15.11",
"@types/node": "^20.11.24",
@@ -15,16 +16,19 @@
"dayjs": "^1.11.10",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-hook-form": "^7.51.5",
"react-router-dom": "^6.22.2",
"typescript": "^5.3.3",
- "vite": "^5.1.7",
- "web-vitals": "^3.5.2"
+ "vite": "^5.1.4",
+ "web-vitals": "^3.5.2",
+ "zod": "^3.23.8"
},
"scripts": {
"start": "vite",
"build": "vite build",
"preview": "vite preview",
- "format": "prettier --write 'src/**/*.{js,jsx,tsx,css,md,json}' --config ./.prettierrc"
+ "format": "prettier --write 'src/**/*.{js,jsx,tsx,css,md,json}' --config ./.prettierrc",
+ "test": "vitest"
},
"eslintConfig": {
"extends": [
@@ -54,7 +58,8 @@
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.0",
- "prettier": "^3.2.5"
+ "prettier": "^3.2.5",
+ "vitest": "^2.0.2"
},
"packageManager": "yarn@4.1.0"
}
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
deleted file mode 100644
index d76787ed..00000000
--- a/frontend/src/App.test.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from "react";
-import { render, screen } from "@testing-library/react";
-import App from "./App";
-
-test("renders learn react link", () => {
- render();
- const linkElement = screen.getByText(/learn react/i);
- expect(linkElement).toBeInTheDocument();
-});
diff --git a/frontend/src/hooks/subscription-form.tsx b/frontend/src/hooks/subscription-form.tsx
new file mode 100644
index 00000000..8203fea2
--- /dev/null
+++ b/frontend/src/hooks/subscription-form.tsx
@@ -0,0 +1,43 @@
+import { ReactNode } from "react";
+import { Subscription } from "../api";
+import { FormProvider, UseFormProps, useForm, useFormContext } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { defaultSubscriptionDTO, subscriptionDTOSchema, type SubscriptionDTO } from "../lib/dtos/subscription";
+
+const SubscriberFormOptions = {
+ mode: "onBlur",
+ reValidateMode: "onChange",
+ resolver: zodResolver(subscriptionDTOSchema),
+ defaultValues: defaultSubscriptionDTO(),
+} satisfies UseFormProps;
+
+export const SubscriberFormProvider = ({ children }: { children: ReactNode }) => {
+ const method = useForm(SubscriberFormOptions);
+ return {children};
+};
+
+export const useSubscriptionForm = () => {
+ const {
+ register,
+ handleSubmit,
+ watch,
+ getValues,
+ setValue,
+ setFocus,
+ reset,
+ control,
+ formState: { errors: validationErrors },
+ } = useFormContext();
+
+ return {
+ register,
+ validationErrors,
+ handleSubmit,
+ watch,
+ getValues,
+ control,
+ setValue,
+ setFocus,
+ reset,
+ };
+};
diff --git a/frontend/src/lib/const.ts b/frontend/src/lib/const.ts
new file mode 100644
index 00000000..8a419a9a
--- /dev/null
+++ b/frontend/src/lib/const.ts
@@ -0,0 +1 @@
+export const DEFAULT_5QI = 9;
diff --git a/frontend/src/lib/dtos/subscription.test.ts b/frontend/src/lib/dtos/subscription.test.ts
new file mode 100644
index 00000000..ebef264e
--- /dev/null
+++ b/frontend/src/lib/dtos/subscription.test.ts
@@ -0,0 +1,351 @@
+import { test, describe } from "vitest";
+import { Subscription } from "../../api";
+import {
+ FlowsMapperImpl,
+ SubscriptionMapperImpl,
+ SubscriptionDTO,
+ defaultSubscriptionDTO,
+} from "./subscription";
+import assert from "node:assert";
+
+const defaultSubscription = (): Subscription => ({
+ userNumber: 1,
+ plmnID: "20893",
+ ueId: "imsi-208930000000001",
+ AuthenticationSubscription: {
+ authenticationMethod: "5G_AKA",
+ sequenceNumber: "000000000023",
+ authenticationManagementField: "8000",
+ permanentKey: {
+ permanentKeyValue: "8baf473f2f8fd09487cccbd7097c6862",
+ encryptionKey: 0,
+ encryptionAlgorithm: 0,
+ },
+ milenage: {
+ op: {
+ opValue: "",
+ encryptionKey: 0,
+ encryptionAlgorithm: 0,
+ },
+ },
+ opc: {
+ opcValue: "8e27b6af0e692e750f32667a3b14605d",
+ encryptionKey: 0,
+ encryptionAlgorithm: 0,
+ },
+ },
+ AccessAndMobilitySubscriptionData: {
+ gpsis: ["msisdn-"],
+ subscribedUeAmbr: {
+ uplink: "1 Gbps",
+ downlink: "2 Gbps",
+ },
+ nssai: {
+ defaultSingleNssais: [
+ {
+ sst: 1,
+ sd: "010203",
+ },
+ ],
+ singleNssais: [
+ {
+ sst: 1,
+ sd: "112233",
+ },
+ ],
+ },
+ },
+ SessionManagementSubscriptionData: [
+ {
+ singleNssai: {
+ sst: 1,
+ sd: "010203",
+ },
+ dnnConfigurations: {
+ internet: {
+ pduSessionTypes: {
+ defaultSessionType: "IPV4",
+ allowedSessionTypes: ["IPV4"],
+ },
+ sscModes: {
+ defaultSscMode: "SSC_MODE_1",
+ allowedSscModes: ["SSC_MODE_2", "SSC_MODE_3"],
+ },
+ "5gQosProfile": {
+ "5qi": 9,
+ arp: {
+ priorityLevel: 8,
+ preemptCap: "",
+ preemptVuln: "",
+ },
+ priorityLevel: 8,
+ },
+ sessionAmbr: {
+ uplink: "1000 Mbps",
+ downlink: "1000 Mbps",
+ },
+ staticIpAddress: [],
+ },
+ },
+ },
+ {
+ singleNssai: {
+ sst: 1,
+ sd: "112233",
+ },
+ dnnConfigurations: {
+ internet: {
+ pduSessionTypes: {
+ defaultSessionType: "IPV4",
+ allowedSessionTypes: ["IPV4"],
+ },
+ sscModes: {
+ defaultSscMode: "SSC_MODE_1",
+ allowedSscModes: ["SSC_MODE_2", "SSC_MODE_3"],
+ },
+ "5gQosProfile": {
+ "5qi": 8,
+ arp: {
+ priorityLevel: 8,
+ preemptCap: "",
+ preemptVuln: "",
+ },
+ priorityLevel: 8,
+ },
+ sessionAmbr: {
+ uplink: "1000 Mbps",
+ downlink: "1000 Mbps",
+ },
+ staticIpAddress: [],
+ },
+ },
+ },
+ ],
+ SmfSelectionSubscriptionData: {
+ subscribedSnssaiInfos: {
+ "01010203": {
+ dnnInfos: [
+ {
+ dnn: "internet",
+ },
+ ],
+ },
+ "01112233": {
+ dnnInfos: [
+ {
+ dnn: "internet",
+ },
+ ],
+ },
+ },
+ },
+ AmPolicyData: {
+ subscCats: ["free5gc"],
+ },
+ SmPolicyData: {
+ smPolicySnssaiData: {
+ "01010203": {
+ snssai: {
+ sst: 1,
+ sd: "010203",
+ },
+ smPolicyDnnData: {
+ internet: {
+ dnn: "internet",
+ },
+ },
+ },
+ "01112233": {
+ snssai: {
+ sst: 1,
+ sd: "112233",
+ },
+ smPolicyDnnData: {
+ internet: {
+ dnn: "internet",
+ },
+ },
+ },
+ },
+ },
+ FlowRules: [
+ {
+ filter: "1.1.1.1/32",
+ precedence: 128,
+ snssai: "01010203",
+ dnn: "internet",
+ qosRef: 1,
+ },
+ {
+ filter: "1.1.1.1/32",
+ precedence: 127,
+ snssai: "01112233",
+ dnn: "internet",
+ qosRef: 2,
+ },
+ ],
+ QosFlows: [
+ {
+ snssai: "01010203",
+ dnn: "internet",
+ qosRef: 1,
+ "5qi": 8,
+ mbrUL: "208 Mbps",
+ mbrDL: "208 Mbps",
+ gbrUL: "108 Mbps",
+ gbrDL: "108 Mbps",
+ },
+ {
+ snssai: "01112233",
+ dnn: "internet",
+ qosRef: 2,
+ "5qi": 7,
+ mbrUL: "407 Mbps",
+ mbrDL: "407 Mbps",
+ gbrUL: "207 Mbps",
+ gbrDL: "207 Mbps",
+ },
+ ],
+ ChargingDatas: [
+ {
+ snssai: "01010203",
+ dnn: "",
+ filter: "",
+ chargingMethod: "Offline",
+ quota: "100000",
+ unitCost: "1",
+ },
+ {
+ snssai: "01010203",
+ dnn: "internet",
+ qosRef: 1,
+ filter: "1.1.1.1/32",
+ chargingMethod: "Offline",
+ quota: "100000",
+ unitCost: "1",
+ },
+ {
+ snssai: "01112233",
+ dnn: "",
+ filter: "",
+ chargingMethod: "Online",
+ quota: "100000",
+ unitCost: "1",
+ },
+ {
+ snssai: "01112233",
+ dnn: "internet",
+ qosRef: 2,
+ filter: "1.1.1.1/32",
+ chargingMethod: "Online",
+ quota: "5000",
+ unitCost: "1",
+ },
+ ],
+});
+
+describe("SubscriptionDTO", ({ extend }) => {
+ describe("default subscription", () => {
+ test("build", ({ expect }) => {
+ const subscriptionBuilder = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriptionBuilder.mapFromDto(defaultSubscriptionDTO());
+ assert.deepEqual(JSON.parse(JSON.stringify(subscription)), defaultSubscription());
+ });
+
+ test("parse", ({ expect }) => {
+ const subscriptionBuilder = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriptionBuilder.mapFromSubscription(defaultSubscription());
+ assert.deepEqual(JSON.parse(JSON.stringify(subscription)), defaultSubscriptionDTO());
+ });
+ });
+
+ describe("subscription with up security", () => {
+ const subscriptionWithUpSecurity: Subscription = defaultSubscription();
+ subscriptionWithUpSecurity.SessionManagementSubscriptionData[0].dnnConfigurations![
+ "internet"
+ ].upSecurity = {
+ upIntegr: "1 Gbps",
+ upConfid: "2 Gbps",
+ };
+
+ const subscriptionWithUpSecurityDTO: SubscriptionDTO = defaultSubscriptionDTO();
+ subscriptionWithUpSecurityDTO.SnssaiConfigurations[0].dnnConfigurations["internet"].upSecurity =
+ {
+ upIntegr: "1 Gbps",
+ upConfid: "2 Gbps",
+ };
+
+ test("build", ({ expect }) => {
+ const subscriptionBuilder = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriptionBuilder.mapFromDto(subscriptionWithUpSecurityDTO);
+ expect(subscription).toEqual(subscriptionWithUpSecurity);
+ });
+
+ test("parse", () => {
+ const subscriptionBuilder = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriptionBuilder.mapFromSubscription(subscriptionWithUpSecurity);
+ assert.deepEqual(
+ JSON.parse(JSON.stringify(subscription)),
+ JSON.parse(JSON.stringify(subscriptionWithUpSecurityDTO)),
+ );
+ });
+ });
+
+ describe("OPc subscription", () => {
+ const subscriptionWithUpSecurity: Subscription = defaultSubscription();
+ subscriptionWithUpSecurity.AuthenticationSubscription.milenage!.op!.opValue = "";
+ subscriptionWithUpSecurity.AuthenticationSubscription!.opc!.opcValue =
+ "8e27b6af0e692e750f32667a3b14605d";
+
+ const subscriptionWithUpSecurityDTO: SubscriptionDTO = defaultSubscriptionDTO();
+ subscriptionWithUpSecurityDTO.auth.operatorCodeType = "OPc";
+ subscriptionWithUpSecurityDTO.auth.operatorCode = "8e27b6af0e692e750f32667a3b14605d";
+
+ test("build", () => {
+ const subscriptionBuilder = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriptionBuilder.mapFromDto(subscriptionWithUpSecurityDTO);
+ assert.deepEqual(
+ JSON.parse(JSON.stringify(subscription)),
+ JSON.parse(JSON.stringify(subscriptionWithUpSecurity)),
+ );
+ });
+
+ test("parse", () => {
+ const subscriptionBuilder = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriptionBuilder.mapFromSubscription(subscriptionWithUpSecurity);
+ assert.deepEqual(
+ JSON.parse(JSON.stringify(subscription)),
+ JSON.parse(JSON.stringify(subscriptionWithUpSecurityDTO)),
+ );
+ });
+ });
+
+ describe("OP subscription", () => {
+ const subscriptionWithUpSecurity: Subscription = defaultSubscription();
+ subscriptionWithUpSecurity.AuthenticationSubscription.milenage!.op!.opValue =
+ "8baf473f2f8fd09487cccbd7097c6862";
+ subscriptionWithUpSecurity.AuthenticationSubscription!.opc!.opcValue = "";
+
+ const subscriptionWithUpSecurityDTO: SubscriptionDTO = defaultSubscriptionDTO();
+ subscriptionWithUpSecurityDTO.auth.operatorCodeType = "OP";
+ subscriptionWithUpSecurityDTO.auth.operatorCode = "8baf473f2f8fd09487cccbd7097c6862";
+
+ test("build", () => {
+ const subscriptionBuilder = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriptionBuilder.mapFromDto(subscriptionWithUpSecurityDTO);
+ assert.deepEqual(
+ JSON.parse(JSON.stringify(subscription)),
+ JSON.parse(JSON.stringify(subscriptionWithUpSecurity)),
+ );
+ });
+
+ test("parse", () => {
+ const subscriptionBuilder = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriptionBuilder.mapFromSubscription(subscriptionWithUpSecurity);
+ assert.deepEqual(
+ JSON.parse(JSON.stringify(subscription)),
+ JSON.parse(JSON.stringify(subscriptionWithUpSecurityDTO)),
+ );
+ });
+ });
+});
diff --git a/frontend/src/lib/dtos/subscription.ts b/frontend/src/lib/dtos/subscription.ts
new file mode 100644
index 00000000..397e0009
--- /dev/null
+++ b/frontend/src/lib/dtos/subscription.ts
@@ -0,0 +1,650 @@
+import { z } from "zod";
+import {
+ AuthenticationSubscription,
+ ChargingData,
+ DnnConfiguration,
+ FlowRules,
+ Nssai,
+ QosFlows,
+ SessionManagementSubscriptionData,
+ SubscribedUeAmbr,
+ Subscription,
+ UpSecurity,
+} from "../../api";
+import { DEFAULT_5QI } from "../const";
+
+export const subscriberAuthDTOSchema = z.object({
+ authenticationManagementField: z.string().regex(/^[A-Fa-f0-9]{4}$/), // 16 bit hex string
+ authenticationMethod: z.enum(["5G_AKA", "EAP_AKA_PRIME"]),
+ sequenceNumber: z.string().regex(/^[A-Fa-f0-9]{12}$/), // 48 bit hex string
+ permanentKey: z.string(),
+ operatorCodeType: z.enum(["OP", "OPc"]),
+ operatorCode: z.string(),
+})
+
+interface SubscriberAuthDTO {
+ authenticationManagementField: string;
+ authenticationMethod: string;
+ sequenceNumber: string;
+ permanentKey: string;
+ operatorCodeType: "OP" | "OPc";
+ operatorCode: string;
+}
+
+export const ambrDTOSchema = z.object({
+ uplink: z.string(),
+ downlink: z.string(),
+})
+
+interface AmbrDTO {
+ uplink: string;
+ downlink: string;
+}
+
+export const chargingDataDTOSchema = z.object({
+ chargingMethod: z.enum(["Online", "Offline"]),
+ quota: z.string(),
+ unitCost: z.string(),
+})
+
+interface ChargingDataDTO {
+ chargingMethod: "Online" | "Offline";
+ quota: string;
+ unitCost: string;
+}
+
+export const flowRulesDTOSchema = z.object({
+ filter: z.string(),
+ precedence: z.number(),
+ "5qi": z.number(),
+ gbrUL: z.string(),
+ gbrDL: z.string(),
+ mbrUL: z.string(),
+ mbrDL: z.string(),
+ chargingData: chargingDataDTOSchema,
+})
+
+interface FlowRulesDTO {
+ filter: string;
+ precedence: number;
+ "5qi": number;
+ gbrUL: string;
+ gbrDL: string;
+ mbrUL: string;
+ mbrDL: string;
+ chargingData: ChargingDataDTO;
+}
+
+export const upSecurityDTOSchema = z.object({
+ upIntegr: z.string(),
+ upConfid: z.string(),
+})
+
+interface UpSecurityDTO {
+ upIntegr: string;
+ upConfid: string;
+}
+
+export const DnnConfigurationDTOSchema = z.object({
+ default5qi: z.number(),
+ sessionAmbr: ambrDTOSchema,
+ enableStaticIpv4Address: z.boolean(),
+ staticIpv4Address: z.string().optional(),
+ flowRules: z.array(flowRulesDTOSchema),
+ upSecurity: upSecurityDTOSchema.optional(),
+})
+
+interface DnnConfigurationDTO {
+ default5qi: number;
+ sessionAmbr: AmbrDTO;
+ enableStaticIpv4Address: boolean;
+ staticIpv4Address?: string;
+ flowRules: FlowRulesDTO[];
+ upSecurity?: UpSecurityDTO;
+}
+
+
+export const SnssaiConfigurationDTOSchema = z.object({
+ sst: z.number(),
+ sd: z.string().optional(),
+ isDefault: z.boolean(),
+ chargingData: chargingDataDTOSchema,
+ dnnConfigurations: z.record(DnnConfigurationDTOSchema),
+});
+
+interface SnssaiConfiurationDTO {
+ sst: number;
+ sd: string;
+ isDefault: boolean;
+ chargingData: ChargingDataDTO;
+ dnnConfigurations: { [key: string]: DnnConfigurationDTO };
+}
+
+
+export const subscriptionDTOSchema = z.object({
+ userNumber: z.number().positive(),
+ ueId: z.string().length(20).startsWith("imsi-"),
+ plmnID: z.string().length(5),
+ gpsi: z.string().optional(),
+ auth: subscriberAuthDTOSchema,
+ subscribedUeAmbr: ambrDTOSchema,
+ SnssaiConfigurations: z.array(SnssaiConfigurationDTOSchema),
+})
+
+interface SubscriptionDTO {
+ userNumber: number;
+ ueId: string;
+ plmnID: string;
+ gpsi?: string;
+ auth: SubscriberAuthDTO;
+ subscribedUeAmbr: AmbrDTO;
+ SnssaiConfigurations: SnssaiConfiurationDTO[];
+}
+
+interface FlowsDTO {
+ flowRules: FlowRules[];
+ qosFlows: QosFlows[];
+ chargingDatas: ChargingData[];
+}
+
+interface FlowsMapper {
+ map(subscription: SubscriptionDTO): FlowsDTO;
+}
+
+class FlowsMapperImpl implements FlowsMapper {
+ refNumber: number = 1;
+ flowRules: FlowRules[] = [];
+ qosFlows: QosFlows[] = [];
+ chargingDatas: ChargingData[] = [];
+
+ private buildDnns(subscription: SubscriptionDTO): {
+ snssai: string;
+ dnn: string;
+ sliceCharingData: ChargingDataDTO;
+ flowRules: FlowRulesDTO[];
+ }[] {
+ return subscription.SnssaiConfigurations.reduce(
+ (acc, s) => {
+ const snssai = s.sst.toString().padStart(2, "0") + s.sd;
+ const dnns = Object.entries(s.dnnConfigurations).map(([dnn, dnnConfig]) => ({
+ snssai: snssai,
+ dnn: dnn,
+ sliceCharingData: s.chargingData,
+ flowRules: dnnConfig.flowRules,
+ }));
+ dnns.forEach((dnn) => acc.push(dnn));
+ return acc;
+ },
+ [] as {
+ snssai: string;
+ dnn: string;
+ sliceCharingData: ChargingDataDTO;
+ flowRules: FlowRulesDTO[];
+ }[],
+ );
+ }
+
+ map(subscription: SubscriptionDTO): FlowsDTO {
+ const dnns = this.buildDnns(subscription);
+
+ dnns.forEach((dnn) => {
+ const snssai = dnn.snssai;
+
+ this.chargingDatas.push({
+ ...dnn.sliceCharingData,
+ snssai: snssai,
+ dnn: "",
+ filter: "",
+ });
+
+ dnn.flowRules.forEach((flow) => {
+ const qosRef = this.refNumber++;
+
+ this.flowRules.push({
+ filter: flow.filter,
+ precedence: flow.precedence,
+ snssai: snssai,
+ dnn: dnn.dnn,
+ qosRef,
+ });
+
+ this.qosFlows.push({
+ snssai: snssai,
+ dnn: dnn.dnn,
+ qosRef,
+ "5qi": flow["5qi"],
+ mbrUL: flow.mbrUL,
+ mbrDL: flow.mbrDL,
+ gbrUL: flow.gbrUL,
+ gbrDL: flow.gbrDL,
+ });
+
+ this.chargingDatas.push({
+ ...flow.chargingData,
+ snssai: snssai,
+ dnn: dnn.dnn,
+ filter: flow.filter,
+ qosRef,
+ });
+ });
+ });
+
+ return {
+ flowRules: this.flowRules,
+ qosFlows: this.qosFlows,
+ chargingDatas: this.chargingDatas,
+ };
+ }
+}
+
+interface SubscriptionMapper {
+ mapFromDto(subscription: SubscriptionDTO): Subscription;
+ mapFromSubscription(subscription: Subscription): SubscriptionDTO;
+}
+
+class SubscriptionMapperImpl implements SubscriptionMapper {
+ constructor(private readonly flowsBuilder: FlowsMapper) {}
+
+ mapFromDto(subscription: SubscriptionDTO): Subscription {
+ const flows = this.flowsBuilder.map(subscription);
+
+ return {
+ userNumber: subscription.userNumber,
+ ueId: subscription.ueId,
+ plmnID: subscription.plmnID,
+ AuthenticationSubscription: this.buildSubscriberAuth(subscription.auth),
+ AccessAndMobilitySubscriptionData: {
+ gpsis: [`msisdn-${subscription.gpsi ?? ""}`],
+ subscribedUeAmbr: this.buildSubscriberAmbr(subscription.subscribedUeAmbr),
+ nssai: {
+ defaultSingleNssais: subscription.SnssaiConfigurations.filter((s) => s.isDefault).map(
+ (s) => this.buildNssai(s),
+ ),
+ singleNssais: subscription.SnssaiConfigurations.filter((s) => !s.isDefault).map((s) =>
+ this.buildNssai(s),
+ ),
+ },
+ },
+
+ SessionManagementSubscriptionData: subscription.SnssaiConfigurations.map((s) =>
+ this.buildSessionManagementSubscriptionData(s),
+ ),
+
+ SmfSelectionSubscriptionData: {
+ subscribedSnssaiInfos: Object.fromEntries(
+ subscription.SnssaiConfigurations.map((s) => [
+ s.sst.toString().padStart(2, "0") + s.sd,
+ {
+ dnnInfos: Object.keys(s.dnnConfigurations).map((dnn) => ({
+ dnn: dnn,
+ })),
+ },
+ ]),
+ ),
+ },
+
+ AmPolicyData: {
+ subscCats: ["free5gc"],
+ },
+
+ SmPolicyData: {
+ smPolicySnssaiData: Object.fromEntries(
+ subscription.SnssaiConfigurations.map((s) => [
+ s.sst.toString().padStart(2, "0") + s.sd,
+ {
+ snssai: this.buildNssai(s),
+ smPolicyDnnData: Object.fromEntries(
+ Object.keys(s.dnnConfigurations).map((dnn) => [dnn, { dnn: dnn }]),
+ ),
+ },
+ ]),
+ ),
+ },
+
+ FlowRules: flows.flowRules,
+ QosFlows: flows.qosFlows,
+ ChargingDatas: flows.chargingDatas,
+ };
+ }
+
+ mapFromSubscription(subscription: Subscription): SubscriptionDTO {
+ return {
+ userNumber: 1,
+ ueId: subscription.ueId,
+ plmnID: subscription.plmnID,
+ gpsi: subscription.AccessAndMobilitySubscriptionData.gpsis?.[0].slice(7) ?? "",
+ auth: {
+ authenticationManagementField:
+ subscription.AuthenticationSubscription.authenticationManagementField ?? "",
+ authenticationMethod: subscription.AuthenticationSubscription.authenticationMethod,
+ sequenceNumber: subscription.AuthenticationSubscription.sequenceNumber,
+ permanentKey: subscription.AuthenticationSubscription.permanentKey.permanentKeyValue,
+ operatorCodeType: subscription.AuthenticationSubscription.milenage?.op?.opValue
+ ? "OP"
+ : "OPc",
+ operatorCode: subscription.AuthenticationSubscription.milenage?.op?.opValue
+ ? subscription.AuthenticationSubscription.milenage.op.opValue
+ : subscription.AuthenticationSubscription.opc?.opcValue ?? "",
+ },
+ subscribedUeAmbr: {
+ uplink: subscription.AccessAndMobilitySubscriptionData.subscribedUeAmbr?.uplink ?? "",
+ downlink: subscription.AccessAndMobilitySubscriptionData.subscribedUeAmbr?.downlink ?? "",
+ },
+ SnssaiConfigurations: subscription.SessionManagementSubscriptionData.map((s) => ({
+ sst: s.singleNssai.sst,
+ sd: s.singleNssai.sd ?? "",
+ isDefault: this.snssaiIsDefault(s.singleNssai, subscription),
+ chargingData: this.findSliceChargingData(s.singleNssai, subscription),
+ dnnConfigurations: Object.fromEntries(
+ Object.entries(s.dnnConfigurations ?? {}).map(([key, value]) => [
+ key,
+ {
+ default5qi: value["5gQosProfile"]?.["5qi"] ?? DEFAULT_5QI,
+ sessionAmbr: {
+ uplink: value.sessionAmbr?.uplink ?? "",
+ downlink: value.sessionAmbr?.downlink ?? "",
+ },
+ enableStaticIpv4Address: value.staticIpAddress?.length !== 0,
+ flowRules: this.parseDnnFlowRules(s.singleNssai, key, subscription),
+ upSecurity: value.upSecurity,
+ } satisfies DnnConfigurationDTO,
+ ]),
+ ),
+ })),
+ };
+ }
+
+ private snssaiIsDefault(nssai: Nssai, subscription: Subscription): boolean {
+ return (
+ subscription.AccessAndMobilitySubscriptionData.nssai?.defaultSingleNssais?.some(
+ (n) => n.sst === nssai.sst && n.sd === nssai.sd,
+ ) ?? false
+ );
+ }
+
+ private findSliceChargingData(nssai: Nssai, subscription: Subscription): ChargingDataDTO {
+ const charingData = subscription.ChargingDatas.find((c) => {
+ if (c.dnn !== "" || c.filter !== "") {
+ return false;
+ }
+
+ return c.snssai === nssai.sst.toString().padStart(2, "0") + nssai.sd;
+ });
+
+ return {
+ chargingMethod: charingData?.chargingMethod === "Online" ? "Online" : "Offline",
+ quota: charingData?.quota ?? "",
+ unitCost: charingData?.unitCost ?? "",
+ };
+ }
+
+ private parseDnnFlowRules(
+ snssai: Nssai,
+ dnn: string,
+ subscription: Subscription,
+ ): FlowRulesDTO[] {
+ const qosFlows = subscription.QosFlows.filter(
+ (f) => f.dnn === dnn && f.snssai === snssai.sst.toString().padStart(2, "0") + snssai.sd,
+ );
+
+ return qosFlows.map((f) => {
+ const flowRule = subscription.FlowRules.find((r) => r.qosRef === f.qosRef);
+ const chargingData = subscription.ChargingDatas.find((c) => c.qosRef === f.qosRef);
+
+ return {
+ filter: flowRule?.filter ?? "",
+ precedence: flowRule?.precedence ?? 0,
+ "5qi": f["5qi"] ?? DEFAULT_5QI,
+ gbrUL: f.gbrUL ?? "",
+ gbrDL: f.gbrDL ?? "",
+ mbrUL: f.mbrUL ?? "",
+ mbrDL: f.mbrDL ?? "",
+ chargingData: {
+ chargingMethod: chargingData?.chargingMethod === "Online" ? "Online" : "Offline",
+ quota: chargingData?.quota ?? "",
+ unitCost: chargingData?.unitCost ?? "",
+ },
+ }
+ });
+ }
+
+ private buildSubscriberAuth(data: SubscriberAuthDTO): AuthenticationSubscription {
+ return {
+ authenticationMethod: data.authenticationMethod,
+ permanentKey: {
+ permanentKeyValue: data.permanentKey,
+ encryptionKey: 0,
+ encryptionAlgorithm: 0,
+ },
+ sequenceNumber: data.sequenceNumber,
+ authenticationManagementField: data.authenticationManagementField,
+ milenage:
+ data.operatorCodeType === "OP"
+ ? {
+ op: {
+ opValue: data.operatorCode,
+ encryptionKey: 0,
+ encryptionAlgorithm: 0,
+ },
+ }
+ : { op: { opValue: "", encryptionKey: 0, encryptionAlgorithm: 0 } },
+ opc:
+ data.operatorCodeType === "OPc"
+ ? { opcValue: data.operatorCode, encryptionKey: 0, encryptionAlgorithm: 0 }
+ : { opcValue: "", encryptionKey: 0, encryptionAlgorithm: 0 },
+ };
+ }
+
+ buildSubscriberAmbr(data: AmbrDTO): SubscribedUeAmbr {
+ return {
+ uplink: data.uplink,
+ downlink: data.downlink,
+ };
+ }
+
+ buildNssai(data: SnssaiConfiurationDTO): Nssai {
+ return {
+ sst: data.sst,
+ sd: data.sd,
+ };
+ }
+
+ buildSessionManagementSubscriptionData(
+ data: SnssaiConfiurationDTO,
+ ): SessionManagementSubscriptionData {
+ return {
+ singleNssai: {
+ sst: data.sst,
+ sd: data.sd,
+ },
+ dnnConfigurations: Object.fromEntries(
+ Object.entries(data.dnnConfigurations).map(([key, value]) => [
+ key,
+ this.buildDnnConfiguration(value),
+ ]),
+ ),
+ };
+ }
+
+ buildDnnConfiguration(data: DnnConfigurationDTO): DnnConfiguration {
+ return {
+ pduSessionTypes: {
+ defaultSessionType: "IPV4",
+ allowedSessionTypes: ["IPV4"],
+ },
+ sscModes: {
+ defaultSscMode: "SSC_MODE_1",
+ allowedSscModes: ["SSC_MODE_2", "SSC_MODE_3"],
+ },
+ "5gQosProfile": {
+ "5qi": data.default5qi,
+ arp: {
+ priorityLevel: 8,
+ preemptCap: "",
+ preemptVuln: "",
+ },
+ priorityLevel: 8,
+ },
+ sessionAmbr: this.buildSubscriberAmbr(data.sessionAmbr),
+ staticIpAddress: data.enableStaticIpv4Address ? [{ ipv4Addr: data.staticIpv4Address }] : [],
+ upSecurity: this.buildUpSecurity(data.upSecurity),
+ };
+ }
+
+ buildUpSecurity(data: UpSecurityDTO | undefined): UpSecurity | undefined {
+ if (!data) {
+ return undefined;
+ }
+
+ return {
+ upIntegr: data.upIntegr,
+ upConfid: data.upConfid,
+ };
+ }
+}
+
+export const defaultSubscriptionDTO = (): SubscriptionDTO => ({
+ userNumber: 1,
+ ueId: "imsi-208930000000001",
+ plmnID: "20893",
+ gpsi: "",
+ auth: {
+ authenticationManagementField: "8000",
+ authenticationMethod: "5G_AKA",
+ sequenceNumber: "000000000023",
+ permanentKey: "8baf473f2f8fd09487cccbd7097c6862",
+ operatorCodeType: "OPc",
+ operatorCode: "8e27b6af0e692e750f32667a3b14605d",
+ },
+ subscribedUeAmbr: {
+ uplink: "1 Gbps",
+ downlink: "2 Gbps",
+ },
+ SnssaiConfigurations: [
+ {
+ sst: 1,
+ sd: "010203",
+ isDefault: true,
+ chargingData: {
+ chargingMethod: "Offline",
+ quota: "100000",
+ unitCost: "1",
+ },
+ dnnConfigurations: {
+ internet: {
+ enableStaticIpv4Address: false,
+ default5qi: 9,
+ sessionAmbr: {
+ uplink: "1000 Mbps",
+ downlink: "1000 Mbps",
+ },
+ flowRules: [
+ {
+ filter: "1.1.1.1/32",
+ precedence: 128,
+ "5qi": 8,
+ gbrUL: "108 Mbps",
+ gbrDL: "108 Mbps",
+ mbrUL: "208 Mbps",
+ mbrDL: "208 Mbps",
+ chargingData: {
+ chargingMethod: "Offline",
+ quota: "100000",
+ unitCost: "1",
+ },
+ },
+ ],
+ },
+ },
+ },
+ {
+ sst: 1,
+ sd: "112233",
+ isDefault: false,
+ chargingData: {
+ chargingMethod: "Online",
+ quota: "100000",
+ unitCost: "1",
+ },
+ dnnConfigurations: {
+ internet: {
+ enableStaticIpv4Address: false,
+ default5qi: 8,
+ sessionAmbr: {
+ uplink: "1000 Mbps",
+ downlink: "1000 Mbps",
+ },
+ flowRules: [
+ {
+ filter: "1.1.1.1/32",
+ precedence: 127,
+ "5qi": 7,
+ gbrUL: "207 Mbps",
+ gbrDL: "207 Mbps",
+ mbrUL: "407 Mbps",
+ mbrDL: "407 Mbps",
+ chargingData: {
+ chargingMethod: "Online",
+ quota: "5000",
+ unitCost: "1",
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+});
+
+export const defaultSnssaiConfiguration = (): SnssaiConfiurationDTO => ({
+ sst: 1,
+ sd: "",
+ isDefault: false,
+ chargingData: {
+ chargingMethod: "Offline",
+ quota: "100000",
+ unitCost: "1",
+ },
+ dnnConfigurations: { internet: defaultDnnConfig() },
+});
+
+export const defaultDnnConfig = (): DnnConfigurationDTO => ({
+ default5qi: DEFAULT_5QI,
+ sessionAmbr: {
+ uplink: "1000 Mbps",
+ downlink: "1000 Mbps",
+ },
+ enableStaticIpv4Address: false,
+ staticIpv4Address: "",
+ flowRules: [defaultFlowRule()],
+ upSecurity: undefined,
+});
+
+export const defaultFlowRule = (): FlowRulesDTO => ({
+ filter: "1.1.1.1/32",
+ precedence: 128,
+ "5qi": 9,
+ gbrUL: "208 Mbps",
+ gbrDL: "208 Mbps",
+ mbrUL: "108 Mbps",
+ mbrDL: "108 Mbps",
+ chargingData: {
+ chargingMethod: "Online",
+ quota: "10000",
+ unitCost: "1",
+ },
+});
+
+export const defaultUpSecurity = (): UpSecurityDTO => ({
+ upIntegr: "NOT_NEEDED",
+ upConfid: "NOT_NEEDED",
+});
+
+export {
+ type SubscriptionDTO,
+ type FlowRulesDTO,
+ type SnssaiConfiurationDTO,
+ type DnnConfigurationDTO,
+ FlowsMapperImpl,
+ SubscriptionMapperImpl,
+};
diff --git a/frontend/src/lib/schemas/subscription.ts b/frontend/src/lib/schemas/subscription.ts
new file mode 100644
index 00000000..3f1bec2f
--- /dev/null
+++ b/frontend/src/lib/schemas/subscription.ts
@@ -0,0 +1,265 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+/* Auto generated by https://transform.tools/typescript-to-zod */
+/* NOTE: Contraints are manually added */
+
+import { z } from "zod";
+
+export const aNInformationSchema = z.object({
+ IPAddress: z.string(),
+ TEID: z.number(),
+});
+
+export const amPolicyDataSchema = z.object({
+ subscCats: z.array(z.string()).optional(),
+});
+
+export const arpSchema = z.object({
+ priorityLevel: z.number(),
+ preemptCap: z.string(),
+ preemptVuln: z.string(),
+});
+
+export const chargingDataSchema = z.object({
+ snssai: z.string().optional(),
+ dnn: z.string(),
+ qosRef: z.number().optional(),
+ filter: z.string(),
+ chargingMethod: z.string().optional(),
+ quota: z.string().optional(),
+ unitCost: z.string().optional(),
+ ueId: z.string().optional(),
+});
+
+export const dnnSchema = z.object({
+ dnn: z.string(),
+});
+
+export const flowChargingRecordSchema = z.object({
+ Supi: z.string(),
+ Snssai: z.string(),
+ Dnn: z.string(),
+ Filter: z.string(),
+ QuotaLeft: z.string(),
+ Usage: z.string(),
+ TotalVol: z.string(),
+ UlVol: z.string(),
+ DlVol: z.string(),
+});
+
+export const flowRulesSchema = z.object({
+ filter: z.string().optional(),
+ precedence: z.number().optional(),
+ snssai: z.string().optional(),
+ dnn: z.string().optional(),
+ qosRef: z.number().optional(),
+});
+
+export const ipAddressSchema = z.object({
+ ipv4Addr: z.string().optional(),
+ ipv6Addr: z.string().optional(),
+ ipv6Prefix: z.string().optional(),
+});
+
+export const metaSchema = z.object({
+ next: z.string().optional(),
+ prev: z.string().optional(),
+ limit: z.number().optional(),
+ total: z.number().optional(),
+});
+
+export const milenageOpSchema = z.object({
+ opValue: z.string(),
+ encryptionKey: z.number(),
+ encryptionAlgorithm: z.number(),
+});
+
+export const model5gQosProfileSchema = z.object({
+ "5qi": z.number(),
+ arp: arpSchema,
+ priorityLevel: z.number().optional(),
+});
+
+export const nssaiSchema = z.object({
+ sst: z.number(),
+ sd: z.string().optional(),
+});
+
+export const opcSchema = z.object({
+ opcValue: z.string(),
+ encryptionKey: z.number(),
+ encryptionAlgorithm: z.number(),
+});
+
+export const pduSessionSchema = z.object({
+ Dnn: z.string(),
+ PduSessionId: z.string(),
+ Sd: z.string(),
+ SmContextRef: z.string(),
+ Sst: z.string(),
+});
+
+export const pduSessionTypesSchema = z.object({
+ defaultSessionType: z.string(),
+ allowedSessionTypes: z.array(z.string()).optional(),
+});
+
+export const permanentKeySchema = z.object({
+ permanentKeyValue: z.string().regex(/^[A-Fa-f0-9]+$/), // hex string
+ encryptionKey: z.number(),
+ encryptionAlgorithm: z.number(),
+});
+
+export const qosFlowsSchema = z.object({
+ snssai: z.string(),
+ dnn: z.string(),
+ qosRef: z.number(),
+ "5qi": z.number(),
+ mbrUL: z.string().optional(),
+ mbrDL: z.string().optional(),
+ gbrUL: z.string().optional(),
+ gbrDL: z.string().optional(),
+});
+
+export const sessionAmbrSchema = z.object({
+ uplink: z.string(),
+ downlink: z.string(),
+});
+
+export const sessionRuleSchema = z.object({
+ sessRuleId: z.string(),
+});
+
+export const smPolicySnssaiSchema = z.object({
+ snssai: nssaiSchema,
+ smPolicyDnnData: z.record(dnnSchema).optional(),
+});
+
+export const sscModesSchema = z.object({
+ defaultSscMode: z.string(),
+ allowedSscModes: z.array(z.string()).optional(),
+});
+
+export const subscribedSnssaiInfoSchema = z.object({
+ dnnInfos: z.array(dnnSchema),
+});
+
+export const subscribedUeAmbrSchema = z.object({
+ uplink: z.string(),
+ downlink: z.string(),
+});
+
+export const subscriberSchema = z.object({
+ plmnID: z.string(),
+ ueId: z.string(),
+ gpsi: z.string(),
+});
+
+export const tenantSchema = z.object({
+ tenantId: z.string().optional(),
+ tenantName: z.string(),
+});
+
+export const tunnelSchema = z.object({
+ ANInformation: aNInformationSchema,
+ DataPathPool: z.any(),
+ PathIDGenerator: z.any(),
+});
+
+export const ueContextSchema = z.object({
+ AccessType: z.string(),
+ CmState: z.string(),
+ Guti: z.string(),
+ Mcc: z.string(),
+ Mnc: z.string(),
+ PduSessions: z.array(pduSessionSchema),
+ Supi: z.string(),
+ Tac: z.string(),
+});
+
+export const upSecuritySchema = z.object({
+ upIntegr: z.string(),
+ upConfid: z.string(),
+});
+
+export const userSchema = z.object({
+ userId: z.string().optional(),
+ tenantId: z.string().optional(),
+ email: z.string(),
+ encryptedPassword: z.string(),
+});
+
+export const defaultSingleNssaisSchema = z.object({
+ defaultSingleNssais: z.array(nssaiSchema),
+ singleNssais: z.array(nssaiSchema).optional(),
+});
+
+export const dnnConfigurationSchema = z.object({
+ pduSessionTypes: pduSessionTypesSchema,
+ sscModes: sscModesSchema,
+ "5gQosProfile": model5gQosProfileSchema.optional(),
+ sessionAmbr: sessionAmbrSchema.optional(),
+ staticIpAddress: z.array(ipAddressSchema).optional(),
+ upSecurity: upSecuritySchema.optional(),
+});
+
+export const milenageSchema = z.object({
+ op: milenageOpSchema.optional(),
+});
+
+export const pduSessionInfoSchema = z.object({
+ AnType: z.string(),
+ Dnn: z.string(),
+ LocalSEID: z.string().optional(),
+ PDUAddress: z.string(),
+ PDUSessionID: z.string(),
+ RemoteSEID: z.string().optional(),
+ Sd: z.string(),
+ SessionRule: sessionRuleSchema,
+ Sst: z.string(),
+ Supi: z.string(),
+ Tunnel: tunnelSchema,
+ UpCnxState: z.string(),
+});
+
+export const sessionManagementSubscriptionDataSchema = z.object({
+ singleNssai: nssaiSchema,
+ dnnConfigurations: z.record(dnnConfigurationSchema).optional(),
+});
+
+export const smPolicyDataSchema = z.object({
+ smPolicySnssaiData: z.record(smPolicySnssaiSchema),
+});
+
+export const smfSelectionSubscriptionDataSchema = z.object({
+ subscribedSnssaiInfos: z.record(subscribedSnssaiInfoSchema).optional(),
+});
+
+export const accessAndMobilitySubscriptionDataSchema = z.object({
+ gpsis: z.array(z.string().startsWith("msisdn-")).optional(),
+ subscribedUeAmbr: subscribedUeAmbrSchema.optional(),
+ nssai: defaultSingleNssaisSchema.optional(),
+});
+
+export const authenticationSubscriptionSchema = z.object({
+ authenticationMethod: z.enum(["5G_AKA", "EAP_AKA_PRIME"]),
+ permanentKey: permanentKeySchema,
+ sequenceNumber: z.string().regex(/^[A-Fa-f0-9]{12}$/), // 48 bit hex string
+ authenticationManagementField: z.string().regex(/^[A-Fa-f0-9]{4}$/), // 16 bit hex string
+ milenage: milenageSchema.optional(),
+ opc: opcSchema.optional(),
+});
+
+export const subscriptionSchema = z.object({
+ userNumber: z.number().positive().int().optional(),
+ plmnID: z.string().length(5),
+ ueId: z.string().length(20).startsWith("imsi-"),
+ AuthenticationSubscription: authenticationSubscriptionSchema,
+ AccessAndMobilitySubscriptionData: accessAndMobilitySubscriptionDataSchema,
+ SessionManagementSubscriptionData: z.array(sessionManagementSubscriptionDataSchema),
+ SmfSelectionSubscriptionData: smfSelectionSubscriptionDataSchema,
+ AmPolicyData: amPolicyDataSchema,
+ SmPolicyData: smPolicyDataSchema,
+ FlowRules: z.array(flowRulesSchema),
+ QosFlows: z.array(qosFlowsSchema),
+ ChargingDatas: z.array(chargingDataSchema),
+});
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
new file mode 100644
index 00000000..32278fbd
--- /dev/null
+++ b/frontend/src/lib/utils.ts
@@ -0,0 +1,3 @@
+export function toHex(v: number | undefined): string {
+ return ("00" + v?.toString(16).toUpperCase()).substr(-2);
+}
diff --git a/frontend/src/pages/SubscriberCreate.tsx b/frontend/src/pages/SubscriberCreate.tsx
deleted file mode 100644
index 96ac29e6..00000000
--- a/frontend/src/pages/SubscriberCreate.tsx
+++ /dev/null
@@ -1,2069 +0,0 @@
-import React from "react";
-import { useState, useEffect } from "react";
-import { useNavigate, useParams } from "react-router-dom";
-
-import axios from "../axios";
-import {
- Subscription,
- Nssai,
- DnnConfiguration,
- AccessAndMobilitySubscriptionData,
- QosFlows,
- IpAddress,
-} from "../api/api";
-
-import Dashboard from "../Dashboard";
-import {
- Button,
- Box,
- Card,
- Checkbox,
- FormControl,
- Grid,
- InputLabel,
- MenuItem,
- Select,
- SelectChangeEvent,
- Table,
- TableBody,
- TableCell,
- TableRow,
- TextField,
- FormControlLabel,
- Switch,
-} from "@mui/material";
-
-interface VerifyScope {
- supi: string;
- sd: string;
- sst: number;
- dnn: string;
- ipaddr: string;
-}
-
-interface VerifyResult {
- ipaddr: string;
- valid: boolean;
- cause: string;
-}
-
-import { RawOff } from "@mui/icons-material";
-
-let isNewSubscriber = false;
-
-export default function SubscriberCreate() {
- const { id, plmn } = useParams<{
- id: string;
- plmn: string;
- }>();
-
- isNewSubscriber = id === undefined && plmn === undefined;
- const navigation = useNavigate();
-
- const [data, setData] = useState({
- userNumber: 1,
- plmnID: "20893",
- ueId: "imsi-208930000000001",
- AuthenticationSubscription: {
- authenticationMethod: "5G_AKA",
- sequenceNumber: "000000000023",
- authenticationManagementField: "8000",
- permanentKey: {
- permanentKeyValue: "8baf473f2f8fd09487cccbd7097c6862",
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- milenage: {
- op: {
- opValue: "",
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- opc: {
- opcValue: "8e27b6af0e692e750f32667a3b14605d",
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- AccessAndMobilitySubscriptionData: {
- gpsis: ["msisdn-"],
- subscribedUeAmbr: {
- uplink: "1 Gbps",
- downlink: "2 Gbps",
- },
- nssai: {
- defaultSingleNssais: [
- {
- sst: 1,
- sd: "010203",
- },
- ],
- singleNssais: [
- {
- sst: 1,
- sd: "112233",
- },
- ],
- },
- },
- SessionManagementSubscriptionData: [
- {
- singleNssai: {
- sst: 1,
- sd: "010203",
- },
- dnnConfigurations: {
- internet: {
- pduSessionTypes: {
- defaultSessionType: "IPV4",
- allowedSessionTypes: ["IPV4"],
- },
- sscModes: {
- defaultSscMode: "SSC_MODE_1",
- allowedSscModes: ["SSC_MODE_2", "SSC_MODE_3"],
- },
- "5gQosProfile": {
- "5qi": 9,
- arp: {
- priorityLevel: 8,
- preemptCap: "",
- preemptVuln: "",
- },
- priorityLevel: 8,
- },
- sessionAmbr: {
- uplink: "1000 Mbps",
- downlink: "1000 Mbps",
- },
- staticIpAddress: [],
- },
- },
- },
- {
- singleNssai: {
- sst: 1,
- sd: "112233",
- },
- dnnConfigurations: {
- internet: {
- pduSessionTypes: {
- defaultSessionType: "IPV4",
- allowedSessionTypes: ["IPV4"],
- },
- sscModes: {
- defaultSscMode: "SSC_MODE_1",
- allowedSscModes: ["SSC_MODE_2", "SSC_MODE_3"],
- },
- "5gQosProfile": {
- "5qi": 8,
- arp: {
- priorityLevel: 8,
- preemptCap: "",
- preemptVuln: "",
- },
- priorityLevel: 8,
- },
- sessionAmbr: {
- uplink: "1000 Mbps",
- downlink: "1000 Mbps",
- },
- staticIpAddress: [],
- },
- },
- },
- ],
- SmfSelectionSubscriptionData: {
- subscribedSnssaiInfos: {
- "01010203": {
- dnnInfos: [
- {
- dnn: "internet",
- },
- ],
- },
- "01112233": {
- dnnInfos: [
- {
- dnn: "internet",
- },
- ],
- },
- },
- },
- AmPolicyData: {
- subscCats: ["free5gc"],
- },
- SmPolicyData: {
- smPolicySnssaiData: {
- "01010203": {
- snssai: {
- sst: 1,
- sd: "010203",
- },
- smPolicyDnnData: {
- internet: {
- dnn: "internet",
- },
- },
- },
- "01112233": {
- snssai: {
- sst: 1,
- sd: "112233",
- },
- smPolicyDnnData: {
- internet: {
- dnn: "internet",
- },
- },
- },
- },
- },
- FlowRules: [
- {
- filter: "1.1.1.1/32",
- precedence: 128,
- snssai: "01010203",
- dnn: "internet",
- qosRef: 1,
- },
- {
- filter: "1.1.1.1/32",
- precedence: 127,
- snssai: "01112233",
- dnn: "internet",
- qosRef: 2,
- },
- ],
- QosFlows: [
- {
- snssai: "01010203",
- dnn: "internet",
- qosRef: 1,
- "5qi": 8,
- mbrUL: "208 Mbps",
- mbrDL: "208 Mbps",
- gbrUL: "108 Mbps",
- gbrDL: "108 Mbps",
- },
- {
- snssai: "01112233",
- dnn: "internet",
- qosRef: 2,
- "5qi": 7,
- mbrUL: "407 Mbps",
- mbrDL: "407 Mbps",
- gbrUL: "207 Mbps",
- gbrDL: "207 Mbps",
- },
- ],
- ChargingDatas: [
- {
- snssai: "01010203",
- dnn: "",
- filter: "",
- chargingMethod: "Offline",
- quota: "100000",
- unitCost: "1",
- },
- {
- snssai: "01010203",
- dnn: "internet",
- qosRef: 1,
- filter: "1.1.1.1/32",
- chargingMethod: "Offline",
- quota: "100000",
- unitCost: "1",
- },
- {
- snssai: "01112233",
- dnn: "",
- filter: "",
- chargingMethod: "Online",
- quota: "100000",
- unitCost: "1",
- },
- {
- snssai: "01112233",
- dnn: "internet",
- qosRef: 2,
- filter: "1.1.1.1/32",
- chargingMethod: "Online",
- quota: "5000",
- unitCost: "1",
- },
- ],
- });
- const [opcType, setOpcType] = useState("OPc");
- const [opcValue, setOpcValue] = useState("8e27b6af0e692e750f32667a3b14605d");
- const [dnnName, setDnnName] = useState([]);
-
- if (!isNewSubscriber) {
- useEffect(() => {
- axios.get("/api/subscriber/" + id + "/" + plmn).then((res) => {
- console.log('loaded existing subscriber', res.data);
- setData(res.data);
- });
- }, [id]);
- }
- function toHex(v: number | undefined): string {
- return ("00" + v?.toString(16).toUpperCase()).substr(-2);
- }
-
- const nssai2KeyString = (nssai: Nssai) => {
- return toHex(nssai.sst) + nssai.sd;
- };
-
- const supiIncrement = (supi: string): string => {
- const imsi = supi.split("-", 2);
- if (imsi.length !== 2) {
- return supi;
- }
- let number = Number(imsi[1]);
- number += 1;
- return "imsi-" + number;
- };
-
- const onCreate = () => {
- if (data.SessionManagementSubscriptionData === undefined) {
- alert("Please add at least one S-NSSAI");
- return;
- }
- for (let i = 0; i < data.SessionManagementSubscriptionData!.length; i++) {
- const nssai = data.SessionManagementSubscriptionData![i];
- const key = nssai2KeyString(nssai.singleNssai!);
- Object.keys(nssai.dnnConfigurations!).map((dnn) => {
- if (data.SmfSelectionSubscriptionData!.subscribedSnssaiInfos![key] === undefined) {
- data.SmfSelectionSubscriptionData!.subscribedSnssaiInfos![key] = {
- dnnInfos: [{ dnn: dnn }],
- };
- } else {
- data.SmfSelectionSubscriptionData!.subscribedSnssaiInfos![key].dnnInfos!.push({
- dnn: dnn,
- });
- }
- if (data.SmPolicyData!.smPolicySnssaiData![key] === undefined) {
- data.SmPolicyData!.smPolicySnssaiData![key] = {
- snssai: nssai.singleNssai,
- smPolicyDnnData: {},
- };
- }
- data.SmPolicyData!.smPolicySnssaiData![key].smPolicyDnnData![dnn] = {
- dnn: dnn,
- };
- });
- }
- // Iterate subscriber data number.
- let supi = data.ueId!;
- for (let i = 0; i < data.userNumber!; i++) {
- data.ueId = supi;
- axios
- .post("/api/subscriber/" + data.ueId + "/" + data.plmnID, data)
- .then(() => {
- navigation("/subscriber");
- })
- .catch((err) => {
- if (err.response) {
- if (err.response.data.cause) {
- alert(err.response.data.cause);
- } else {
- alert(err.response.data);
- }
- } else {
- alert(err.message);
- }
- return;
- });
- supi = supiIncrement(supi);
- }
- };
-
- const onUpdate = () => {
- data.SmfSelectionSubscriptionData = {
- subscribedSnssaiInfos: {},
- };
- data.SmPolicyData = {
- smPolicySnssaiData: {},
- };
- for (let i = 0; i < data.SessionManagementSubscriptionData!.length; i++) {
- const nssai = data.SessionManagementSubscriptionData![i];
- const key = nssai2KeyString(nssai.singleNssai!);
- if (nssai.dnnConfigurations !== undefined) {
- Object.keys(nssai.dnnConfigurations!).map((dnn) => {
- if (data.SmfSelectionSubscriptionData!.subscribedSnssaiInfos![key] === undefined) {
- data.SmfSelectionSubscriptionData!.subscribedSnssaiInfos![key] = {
- dnnInfos: [{ dnn: dnn }],
- };
- } else {
- data.SmfSelectionSubscriptionData!.subscribedSnssaiInfos![key].dnnInfos!.push({
- dnn: dnn,
- });
- }
- if (data.SmPolicyData!.smPolicySnssaiData![key] === undefined) {
- data.SmPolicyData!.smPolicySnssaiData![key] = {
- snssai: nssai.singleNssai,
- smPolicyDnnData: {},
- };
- }
- data.SmPolicyData!.smPolicySnssaiData![key].smPolicyDnnData![dnn] = {
- dnn: dnn,
- };
- });
- }
- }
- axios
- .put("/api/subscriber/" + data.ueId + "/" + data.plmnID, data)
- .then(() => {
- navigation("/subscriber/" + data.ueId + "/" + data.plmnID);
- })
- .catch((err) => {
- if (err.response) {
- if (err.response.data.cause) {
- alert(err.response.data.cause);
- } else {
- alert(err.response.data);
- }
- } else {
- alert(err.message);
- }
- });
- };
-
- const onSnssai = () => {
- if (
- data.SessionManagementSubscriptionData === undefined ||
- data.SessionManagementSubscriptionData!.length === 0
- ) {
- data.SessionManagementSubscriptionData = [
- {
- singleNssai: {
- sst: 1,
- sd: "010203",
- },
- dnnConfigurations: {},
- },
- ];
- setData({ ...data });
- } else {
- data.SessionManagementSubscriptionData.push({
- singleNssai: {
- sst: 1,
- },
- dnnConfigurations: {},
- });
- setData({ ...data });
- }
- };
-
- const onSnssaiDelete = (index: number, snssaiToDelete: string) => {
- if (data.SessionManagementSubscriptionData !== undefined) {
- console.log("delete", snssaiToDelete);
-
- // Remove charging data if found
- data.ChargingDatas = data!.ChargingDatas!.filter(
- (chargingData) => chargingData.snssai !== snssaiToDelete,
- );
-
- data.SessionManagementSubscriptionData.splice(index, 1);
- setData({ ...data });
- }
- };
-
- const onDnnAdd = (index: number) => {
- if (data.SessionManagementSubscriptionData !== undefined) {
- const name = dnnName[index];
- if (name === undefined || name === "") {
- return;
- }
- // TODO: add charging rule for this DNN
- const session = data.SessionManagementSubscriptionData![index];
- session.dnnConfigurations![name] = {
- pduSessionTypes: {
- defaultSessionType: "IPV4",
- allowedSessionTypes: ["IPV4"],
- },
- sscModes: {
- defaultSscMode: "SSC_MODE_1",
- allowedSscModes: ["SSC_MODE_2", "SSC_MODE_3"],
- },
- "5gQosProfile": {
- "5qi": 9,
- arp: {
- priorityLevel: 8,
- preemptCap: "",
- preemptVuln: "",
- },
- priorityLevel: 8,
- },
- sessionAmbr: {
- uplink: "",
- downlink: "",
- },
- };
- setData({ ...data });
- dnnName[index] = "";
- setDnnName({ ...dnnName });
- }
- };
-
- const onDnnDelete = (index: number, dnn: string, slice: string) => {
- if (data.SessionManagementSubscriptionData !== undefined) {
- delete data.SessionManagementSubscriptionData![index].dnnConfigurations![dnn];
- setData({ ...data });
- }
- // Remove all flow-based charging rule in this DNN
- if (data.ChargingDatas !== undefined) {
- for (let i = 0; i < data.ChargingDatas!.length; i++) {
- if (data.ChargingDatas![i].dnn === dnn && data.ChargingDatas![i].snssai === slice) {
- data.ChargingDatas!.splice(i, 1);
- i--;
- }
- }
- }
- setData({ ...data });
- };
-
- const onFlowRulesDelete = (dnn: string, flowKey: string, qosRef: number | undefined) => {
- if (data.FlowRules !== undefined) {
- for (let i = 0; i < data.FlowRules!.length; i++) {
- if (
- data.FlowRules![i].dnn === dnn &&
- data.FlowRules![i].snssai === flowKey &&
- data.FlowRules![i].qosRef === qosRef
- ) {
- data.FlowRules!.splice(i, 1);
- i--;
- }
- }
- }
- if (data.QosFlows !== undefined) {
- for (let i = 0; i < data.QosFlows!.length; i++) {
- if (
- data.QosFlows![i].dnn === dnn &&
- data.QosFlows![i].snssai === flowKey &&
- data.QosFlows![i].qosRef === qosRef
- ) {
- data.QosFlows!.splice(i, 1);
- i--;
- }
- }
- }
- if (data.ChargingDatas !== undefined) {
- for (let i = 0; i < data.ChargingDatas!.length; i++) {
- if (data.ChargingDatas![i].qosRef === qosRef) {
- data.ChargingDatas!.splice(i, 1);
- i--;
- }
- }
- }
- setData({ ...data });
- };
-
- const onUpSecurity = (dnn: DnnConfiguration | undefined) => {
- if (dnn !== undefined) {
- dnn.upSecurity = {
- upIntegr: "NOT_NEEDED",
- upConfid: "NOT_NEEDED",
- };
- }
- setData({ ...data });
- };
-
- function selectQosRef(): number {
- const UsedQosRef = [];
- for (let i = 0; i < data.QosFlows!.length; i++) {
- UsedQosRef.push(data.QosFlows![i]!.qosRef);
- }
- for (let i = 1; i < 256; i++) {
- if (!UsedQosRef.includes(i)) {
- return i;
- }
- }
-
- window.alert("Cannot select qosRef in 1~128.");
- return -1;
- }
-
- function select5Qi(dnn: string, snssai: Nssai): number {
- const sstsd = toHex(snssai.sst) + snssai.sd!;
- const filteredQosFlows = data.QosFlows!.filter(
- (qos) => qos.dnn === dnn && qos.snssai === sstsd,
- );
- const Used5Qi = [];
- for (let i = 0; i < filteredQosFlows.length; i++) {
- Used5Qi.push(filteredQosFlows[i]["5qi"]);
- }
- Used5Qi.sort((a, b) => a! - b!);
- if (Used5Qi[Used5Qi.length - 1]! < 255) {
- return Used5Qi[Used5Qi.length - 1]! + 1;
- }
- return 255;
- }
-
- const onFlowRulesAdd = (dnn: string, snssai: Nssai) => {
- const sstSd = toHex(snssai.sst) + snssai.sd!;
- let filter = "8.8.8.8/32";
- for (;;) {
- let flag = false;
- for (let i = 0; i < data.FlowRules!.length; i++) {
- if (filter === data.FlowRules![i]!.filter) {
- const c = Math.floor(Math.random() * 256);
- const d = Math.floor(Math.random() * 256);
- filter = "10.10." + c.toString() + "." + d.toString() + "/32";
- flag = true;
- break;
- }
- }
-
- if (!flag) break;
- }
-
- const selected5Qi = select5Qi(dnn, snssai);
- const selectedQosRef = selectQosRef();
- data.FlowRules!.push({
- filter: filter,
- precedence: 127,
- snssai: sstSd,
- dnn: dnn,
- qosRef: selectedQosRef,
- });
-
- data.QosFlows!.push({
- snssai: sstSd,
- dnn: dnn,
- qosRef: selectedQosRef,
- "5qi": selected5Qi,
- mbrUL: "200 Mbps",
- mbrDL: "200 Mbps",
- gbrUL: "100 Mbps",
- gbrDL: "100 Mbps",
- });
-
- data.ChargingDatas!.push({
- snssai: sstSd,
- dnn: dnn,
- qosRef: selectedQosRef,
- filter: filter,
- chargingMethod: "Online",
- quota: "10000",
- unitCost: "1",
- });
-
- setData({ ...data });
- };
-
- const onUpSecurityDelete = (dnn: DnnConfiguration) => {
- dnn.upSecurity = undefined;
- setData({ ...data });
- };
-
- const isDefaultNssai = (nssai: Nssai) => {
- if (nssai === undefined || data.AccessAndMobilitySubscriptionData === undefined) {
- return false;
- } else {
- for (
- let i = 0;
- i < data.AccessAndMobilitySubscriptionData!.nssai!.defaultSingleNssais!.length;
- i++
- ) {
- const defaultNssai = data.AccessAndMobilitySubscriptionData!.nssai!.defaultSingleNssais![i];
- if (defaultNssai.sd === nssai.sd && defaultNssai.sst === nssai.sst) {
- return true;
- }
- }
- return false;
- }
- };
-
- const imsiValue = (imsi: string | undefined) => {
- if (imsi === undefined) {
- return "";
- } else {
- return imsi.replace("imsi-", "");
- }
- };
-
- const msisdnValue = (subData: AccessAndMobilitySubscriptionData | undefined) => {
- if (subData === undefined) {
- return "";
- } else {
- if (subData.gpsis !== undefined && subData.gpsis!.length !== 0) {
- return subData.gpsis[0].replace("msisdn-", "");
- } else {
- return "";
- }
- }
- };
-
- const handleChangeUserNumber = (
- event: React.ChangeEvent,
- ): void => {
- if (event.target.value === undefined) {
- setData({ ...data, userNumber: undefined });
- } else {
- const userNumber = Number(event.target.value);
- if (userNumber >= 1) {
- setData({ ...data, userNumber: Number(event.target.value) });
- }
- }
- };
-
- const handleChangePlmnId = (
- event: React.ChangeEvent,
- ): void => {
- setData({ ...data, plmnID: event.target.value });
- };
-
- const handleChangeUeId = (
- event: React.ChangeEvent,
- ): void => {
- setData({ ...data, ueId: "imsi-" + event.target.value });
- };
-
- const handleChangeMsisdn = (
- event: React.ChangeEvent,
- ): void => {
- setData({
- ...data,
- AccessAndMobilitySubscriptionData: {
- ...data.AccessAndMobilitySubscriptionData,
- gpsis: ["msisdn-" + event.target.value],
- },
- });
- };
-
- const handleChangeAuthenticationManagementField = (
- event: React.ChangeEvent,
- ): void => {
- setData({
- ...data,
- AuthenticationSubscription: {
- ...data.AuthenticationSubscription,
- authenticationManagementField: event.target.value,
- },
- });
- };
-
- const handleChangeAuthenticationMethod = (event: SelectChangeEvent): void => {
- setData({
- ...data,
- AuthenticationSubscription: {
- ...data.AuthenticationSubscription,
- authenticationMethod: event.target.value,
- },
- });
- };
-
- const handleChangeK = (
- event: React.ChangeEvent,
- ): void => {
- setData({
- ...data,
- AuthenticationSubscription: {
- ...data.AuthenticationSubscription,
- permanentKey: {
- permanentKeyValue: event.target.value,
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- });
- };
-
- const handleChangeOperatorCodeType = (event: SelectChangeEvent): void => {
- if (event.target.value === "OP") {
- setOpcType("OP");
- const tmp = {
- ...data,
- AuthenticationSubscription: {
- ...data.AuthenticationSubscription,
- milenage: {
- op: {
- opValue: opcValue,
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- opc: {
- opcValue: "",
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- };
- setData(tmp);
- } else {
- setOpcType("OPc");
- const tmp = {
- ...data,
- AuthenticationSubscription: {
- ...data.AuthenticationSubscription,
- milenage: {
- op: {
- opValue: "",
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- opc: {
- opcValue: opcValue,
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- };
- setData(tmp);
- }
- };
-
- const handleChangeOperatorCodeValue = (
- event: React.ChangeEvent,
- ): void => {
- setOpcValue(event.target.value);
- const auth = data.AuthenticationSubscription;
- if (auth !== undefined) {
- if (opcType === "OP") {
- const tmp = {
- ...data,
- AuthenticationSubscription: {
- ...data.AuthenticationSubscription,
- milenage: {
- op: {
- opValue: event.target.value,
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- opc: {
- opcValue: "",
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- };
- setData(tmp);
- } else {
- const tmp = {
- ...data,
- AuthenticationSubscription: {
- ...data.AuthenticationSubscription,
- milenage: {
- op: {
- opValue: "",
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- opc: {
- opcValue: event.target.value,
- encryptionKey: 0,
- encryptionAlgorithm: 0,
- },
- },
- };
- setData(tmp);
- }
- }
- };
-
- const handleChangeSQN = (
- event: React.ChangeEvent,
- ): void => {
- setData({
- ...data,
- AuthenticationSubscription: {
- ...data.AuthenticationSubscription,
- sequenceNumber: event.target.value,
- },
- });
- };
-
- const handleChangeSubAmbrUplink = (
- event: React.ChangeEvent,
- ): void => {
- setData({
- ...data,
- AccessAndMobilitySubscriptionData: {
- ...data.AccessAndMobilitySubscriptionData,
- subscribedUeAmbr: {
- uplink: event.target.value,
- downlink: data.AccessAndMobilitySubscriptionData.subscribedUeAmbr?.downlink ?? "",
- },
- },
- });
- };
-
- const handleChangeSubAmbrDownlink = (
- event: React.ChangeEvent,
- ): void => {
- setData({
- ...data,
- AccessAndMobilitySubscriptionData: {
- ...data.AccessAndMobilitySubscriptionData,
- subscribedUeAmbr: {
- uplink: data.AccessAndMobilitySubscriptionData.subscribedUeAmbr?.uplink ?? "",
- downlink: event.target.value,
- },
- },
- });
- };
-
- const handleChangeSST = (
- event: React.ChangeEvent,
- index: number,
- ): void => {
- if (event.target.value === "") {
- data.SessionManagementSubscriptionData![index].singleNssai!.sst = 0;
- } else {
- data.SessionManagementSubscriptionData![index].singleNssai!.sst! = Number(event.target.value);
- }
- setData({ ...data });
- };
-
- const handleChangeSD = (
- event: React.ChangeEvent,
- index: number,
- ): void => {
- data.SessionManagementSubscriptionData![index].singleNssai!.sd! = event.target.value;
- setData({ ...data });
- };
-
- const handleChangeDefaultSnssai = (
- _event: React.ChangeEvent,
- nssai: Nssai | undefined,
- ) => {
- if (nssai === undefined) {
- return;
- }
- let isDefault = false;
- let def = undefined;
- if (data.AccessAndMobilitySubscriptionData!.nssai!.defaultSingleNssais !== undefined) {
- def = data.AccessAndMobilitySubscriptionData!.nssai!.defaultSingleNssais!;
- for (let i = 0; i < def.length; i++) {
- if (def[i].sd === nssai.sd && def[i].sst === nssai.sst) {
- def.splice(i, 1);
- isDefault = true;
- }
- }
- }
- let single = undefined;
- if (data.AccessAndMobilitySubscriptionData!.nssai!.singleNssais !== undefined) {
- single = data.AccessAndMobilitySubscriptionData!.nssai!.singleNssais!;
- for (let i = 0; i < single.length; i++) {
- if (single[i].sd === nssai.sd && single[i].sst === nssai.sst) {
- single.splice(i, 1);
- }
- }
- }
- if (isDefault) {
- if (single !== undefined) {
- single.push(nssai);
- }
- } else {
- if (def !== undefined) {
- def.push(nssai);
- }
- }
- data.AccessAndMobilitySubscriptionData!.nssai!.defaultSingleNssais = def ?? [];
- data.AccessAndMobilitySubscriptionData!.nssai!.singleNssais = single;
- setData({ ...data });
- };
-
- const dnnValue = (index: number) => {
- return dnnName[index];
- };
-
- const handleChangeDNN = (
- event: React.ChangeEvent,
- index: number,
- ): void => {
- dnnName[index] = event.target.value;
- setDnnName({ ...dnnName });
- };
-
- const handleChangeUplinkAMBR = (
- event: React.ChangeEvent,
- index: number,
- dnn: string,
- ): void => {
- data.SessionManagementSubscriptionData![index].dnnConfigurations![dnn].sessionAmbr!.uplink =
- event.target.value;
- setData({ ...data });
- };
-
- const handleChangeDownlinkAMBR = (
- event: React.ChangeEvent,
- index: number,
- dnn: string,
- ): void => {
- data.SessionManagementSubscriptionData![index].dnnConfigurations![dnn].sessionAmbr!.downlink =
- event.target.value;
- setData({ ...data });
- };
-
- const handleChangeDefault5QI = (
- event: React.ChangeEvent,
- index: number,
- dnn: string,
- ): void => {
- if (event.target.value === "") {
- data.SessionManagementSubscriptionData![index].dnnConfigurations![dnn]["5gQosProfile"]![
- "5qi"
- ] = 8;
- } else {
- data.SessionManagementSubscriptionData![index].dnnConfigurations![dnn]["5gQosProfile"]![
- "5qi"
- ] = Number(event.target.value);
- }
- setData({ ...data });
- };
-
- const handleChangeStaticIp = (
- event: React.ChangeEvent,
- index: number,
- dnn: string,
- ): void => {
- data.SessionManagementSubscriptionData![index].dnnConfigurations![dnn][
- "staticIpAddress"
- ]![0].ipv4Addr = event.target.value;
- setData({ ...data });
- };
-
- const handleChangeFilter = (
- event: React.ChangeEvent,
- dnn: string,
- flowKey: string,
- qosRef: number,
- ): void => {
- if (data.FlowRules !== undefined) {
- for (let i = 0; i < data.FlowRules!.length; i++) {
- if (
- data.FlowRules![i].snssai === flowKey &&
- data.FlowRules![i].dnn === dnn &&
- data.QosFlows![i].qosRef === qosRef
- ) {
- data.FlowRules![i].filter = event.target.value;
- setData({ ...data });
- }
- }
- }
- if (data.ChargingDatas !== undefined) {
- for (let i = 0; i < data.ChargingDatas!.length; i++) {
- if (
- data.ChargingDatas![i].snssai === flowKey &&
- data.ChargingDatas![i].dnn === dnn &&
- data.ChargingDatas![i].qosRef === qosRef
- ) {
- data.ChargingDatas![i].filter = event.target.value;
- setData({ ...data });
- }
- }
- }
- };
-
- const handleChangePrecedence = (
- event: React.ChangeEvent,
- dnn: string,
- flowKey: string,
- qosRef: number,
- ): void => {
- if (data.FlowRules !== undefined) {
- for (let i = 0; i < data.FlowRules!.length; i++) {
- if (
- data.FlowRules![i].snssai === flowKey &&
- data.FlowRules![i].dnn === dnn &&
- data.QosFlows![i].qosRef === qosRef
- ) {
- if (event.target.value == "") {
- data.FlowRules![i].precedence = undefined;
- } else {
- data.FlowRules![i].precedence = Number(event.target.value);
- }
- setData({ ...data });
- }
- }
- }
- };
-
- const handleChange5QI = (
- event: React.ChangeEvent,
- dnn: string,
- flowKey: string,
- qosRef: number,
- ): void => {
- if (data.QosFlows !== undefined) {
- for (let i = 0; i < data.QosFlows!.length; i++) {
- if (
- data.QosFlows![i].snssai === flowKey &&
- data.QosFlows![i].dnn === dnn &&
- data.QosFlows![i].qosRef === qosRef
- ) {
- if (event.target.value == "") {
- data.QosFlows![i]["5qi"] = 8;
- } else {
- data.QosFlows![i]["5qi"] = Number(event.target.value);
- }
- setData({ ...data });
- }
- }
- }
- };
-
- const handleChangeUplinkGBR = (
- event: React.ChangeEvent,
- dnn: string,
- flowKey: string,
- qosRef: number,
- ): void => {
- if (data.QosFlows !== undefined) {
- for (let i = 0; i < data.QosFlows!.length; i++) {
- if (
- data.QosFlows![i].snssai === flowKey &&
- data.QosFlows![i].dnn === dnn &&
- data.QosFlows![i].qosRef === qosRef
- ) {
- data.QosFlows![i].gbrUL = event.target.value;
- setData({ ...data });
- }
- }
- }
- };
-
- const handleChangeDownlinkGBR = (
- event: React.ChangeEvent,
- dnn: string,
- flowKey: string,
- qosRef: number,
- ): void => {
- if (data.QosFlows !== undefined) {
- for (let i = 0; i < data.QosFlows!.length; i++) {
- if (
- data.QosFlows![i].snssai === flowKey &&
- data.QosFlows![i].dnn === dnn &&
- data.QosFlows![i].qosRef === qosRef
- ) {
- data.QosFlows![i].gbrDL = event.target.value;
- setData({ ...data });
- }
- }
- }
- };
-
- const handleChangeUplinkMBR = (
- event: React.ChangeEvent,
- dnn: string,
- flowKey: string,
- qosRef: number,
- ): void => {
- if (data.QosFlows !== undefined) {
- for (let i = 0; i < data.QosFlows!.length; i++) {
- if (
- data.QosFlows![i].snssai === flowKey &&
- data.QosFlows![i].dnn === dnn &&
- data.QosFlows![i].qosRef === qosRef
- ) {
- data.QosFlows![i].mbrUL = event.target.value;
- setData({ ...data });
- }
- }
- }
- };
-
- const handleChangeDownlinkMBR = (
- event: React.ChangeEvent,
- dnn: string,
- flowKey: string,
- qosRef: number,
- ): void => {
- if (data.QosFlows !== undefined) {
- for (let i = 0; i < data.QosFlows!.length; i++) {
- if (
- data.QosFlows![i].snssai === flowKey &&
- data.QosFlows![i].dnn === dnn &&
- data.QosFlows![i].qosRef === qosRef
- ) {
- data.QosFlows![i].mbrDL = event.target.value;
- setData({ ...data });
- }
- }
- }
- };
-
- const handleChangeChargingMethod = (
- event: SelectChangeEvent,
- dnn: string | undefined,
- flowKey: string,
- filter: string | undefined,
- ): void => {
- for (let i = 0; i < data.ChargingDatas!.length; i++) {
- for (let j = 0; j < data.QosFlows!.length; j++) {
- if (
- data.ChargingDatas![i].snssai === flowKey &&
- data.ChargingDatas![i].dnn === dnn &&
- data.ChargingDatas![i].filter === filter
- ) {
- data.ChargingDatas![i]!.chargingMethod = event.target.value;
- setData({ ...data });
- }
- }
- }
- };
-
- const handleChangeChargingQuota = (
- event: React.ChangeEvent,
- dnn: string | undefined,
- flowKey: string,
- filter: string | undefined,
- ): void => {
- for (let i = 0; i < data.ChargingDatas!.length; i++) {
- for (let j = 0; j < data.QosFlows!.length; j++) {
- if (
- data.ChargingDatas![i].snssai === flowKey &&
- data.ChargingDatas![i].dnn === dnn &&
- data.ChargingDatas![i].filter === filter
- ) {
- data.ChargingDatas![i]!.quota = event.target.value;
- setData({ ...data });
- }
- }
- }
- };
-
- const handleChangeChargingUnitCost = (
- event: React.ChangeEvent,
- dnn: string | undefined,
- flowKey: string,
- filter: string | undefined,
- ): void => {
- for (let i = 0; i < data.ChargingDatas!.length; i++) {
- for (let j = 0; j < data.QosFlows!.length; j++) {
- if (
- data.ChargingDatas![i].snssai === flowKey &&
- data.ChargingDatas![i].dnn === dnn &&
- data.ChargingDatas![i].filter === filter
- ) {
- data.ChargingDatas![i]!.unitCost = event.target.value;
- setData({ ...data });
- }
- }
- }
- };
-
- const handleChangeUpIntegrity = (
- event: SelectChangeEvent,
- dnn: DnnConfiguration,
- ): void => {
- if (dnn.upSecurity !== undefined) {
- dnn.upSecurity!.upIntegr = event.target.value;
- }
- setData({ ...data });
- };
-
- const handleChangeUpConfidentiality = (
- event: SelectChangeEvent,
- dnn: DnnConfiguration,
- ): void => {
- if (dnn.upSecurity !== undefined) {
- dnn.upSecurity!.upConfid = event.target.value;
- }
- setData({ ...data });
- };
-
- const qosFlow = (
- sstSd: string,
- dnn: string,
- qosRef: number | undefined,
- ): QosFlows | undefined => {
- if (data.QosFlows !== undefined) {
- for (let i = 0; i < data.QosFlows?.length; i++) {
- const qos = data.QosFlows![i];
- if (qos.snssai === sstSd && qos.dnn === dnn && qos.qosRef == qosRef) {
- return qos;
- }
- }
- }
- };
-
- const chargingConfig = (dnn: string | undefined, snssai: Nssai, filter: string | undefined) => {
- const flowKey = toHex(snssai.sst) + snssai.sd;
- for (let i = 0; i < data.ChargingDatas!.length; i++) {
- const chargingData = data.ChargingDatas![i];
- const idPrefix = snssai + "-" + dnn + "-" + chargingData.qosRef + "-";
- const isOnlineCharging = data.ChargingDatas![i].chargingMethod === "Online";
- if (
- chargingData.snssai === flowKey &&
- chargingData.dnn === dnn &&
- chargingData.filter === filter
- ) {
- return (
- <>
-
-
-
-
- Charging Method
-
-
-
-
- handleChangeChargingQuota(ev, dnn, flowKey, filter)}
- />
-
-
- handleChangeChargingUnitCost(ev, dnn, flowKey, filter)}
- />
-
-
-
- >
- );
- }
- }
- };
-
- const flowRule = (dnn: string, snssai: Nssai) => {
- const flowKey = toHex(snssai.sst) + snssai.sd;
- const idPrefix = flowKey + "-" + dnn + "-";
- if (data.FlowRules !== undefined) {
- return data.FlowRules.filter((flow) => flow.dnn === dnn && flow.snssai === flowKey).map(
- (flow) => (
-
-
-
-
- Flow Rules {flow.qosRef}
-
-
-
-
-
-
-
-
-
-
-
-
- handleChangeFilter(ev, dnn, flowKey, flow.qosRef!)}
- />
-
-
- handleChangePrecedence(ev, dnn, flowKey, flow.qosRef!)}
- />
-
-
- handleChange5QI(ev, dnn, flowKey, flow.qosRef!)}
- />
-
-
-
-
-
-
- handleChangeUplinkGBR(ev, dnn, flowKey, flow.qosRef!)}
- />
-
-
- handleChangeDownlinkGBR(ev, dnn, flowKey, flow.qosRef!)}
- />
-
-
- handleChangeUplinkMBR(ev, dnn, flowKey, flow.qosRef!)}
- />
-
-
- handleChangeDownlinkMBR(ev, dnn, flowKey, flow.qosRef!)}
- />
-
-
-
-
- {chargingConfig(dnn, snssai, flow.filter!)}
-
-
-
- ),
- );
- }
- };
-
- const upSecurity = (dnn: DnnConfiguration | undefined) => {
- if (dnn !== undefined && dnn!.upSecurity !== undefined) {
- const security = dnn!.upSecurity!;
- return (
-
-
-
-
- UP Security
-
-
-
-
-
-
-
-
-
-
-
-
-
- Integrity of UP Security
-
-
-
-
-
-
-
-
-
- Confidentiality of UP Security
-
-
-
-
-
-
-
-
-
- );
- } else {
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
- }
- };
-
- const handleVerifyStaticIp = (sd: string, sst: number, dnn: string, ipaddr: string) => {
- const scope: VerifyScope = {
- supi: "",
- sd: sd,
- sst: sst,
- dnn: dnn,
- ipaddr: ipaddr,
- };
- axios.post("/api/verify-staticip", scope).then((res) => {
- const result = res.data as VerifyResult;
- console.log(result);
- if (result["valid"] === true) {
- alert("OK\n" + result.ipaddr);
- } else {
- alert("NO!\nCause: " + result["cause"]);
- }
- });
- };
-
- return (
- {}}>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Authentication Method
-
-
-
-
-
-
-
-
-
- Operator Code Type
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Subscribed UE AMBR
-
-
-
- {data.SessionManagementSubscriptionData?.map((row, index) => (
-
-
-
- S-NSSAI Configuration ({toHex(row.singleNssai!.sst) + row.singleNssai!.sd!})
-
-
-
-
-
-
-
-
-
-
-
-
- handleChangeSST(ev, index)}
- />
-
-
- handleChangeSD(ev, index)}
- />
-
-
-
-
-
- Default S-NSSAI
-
- handleChangeDefaultSnssai(ev, row.singleNssai)}
- />
-
-
-
-
- {chargingConfig("", row.singleNssai!, "")}
- {row.dnnConfigurations &&
- Object.keys(row.dnnConfigurations!).map((dnn) => (
-
-
-
-
- DNN Configurations
-
-
-
-
-
-
-
-
-
-
-
-
- {dnn}
-
-
-
-
-
-
- handleChangeUplinkAMBR(ev, index, dnn)}
- />
-
-
- handleChangeDownlinkAMBR(ev, index, dnn)}
- />
-
-
- handleChangeDefault5QI(ev, index, dnn)}
- />
-
-
-
-
-
-
-
-
- {
- if (event.target.checked) {
- var ipaddr: IpAddress = { ipv4Addr: "10.60.100.1" };
- data.SessionManagementSubscriptionData![
- index
- ].dnnConfigurations![dnn]["staticIpAddress"] = [ipaddr];
- } else {
- data.SessionManagementSubscriptionData![
- index
- ].dnnConfigurations![dnn]["staticIpAddress"] = [];
- }
- setData({ ...data });
- }}
- />
- label="Static IPv4 Address"
- />
-
-
- handleChangeStaticIp(ev, index, dnn)}
- />
-
-
-
-
-
-
-
-
- {chargingConfig(dnn, row.singleNssai!, undefined)}
-
- {flowRule(dnn, row.singleNssai!)}
-
-
-
-
-
-
-
-
-
-
-
-
- {upSecurity(row.dnnConfigurations![dnn])}
-
-
-
- ))}
-
-
-
- handleChangeDNN(ev, index)}
- />
-
-
-
-
-
-
-
-
-
-
- ))}
-
-
-
-
-
-
- {isNewSubscriber ? (
-
- ) : (
-
- )}
-
-
- );
-}
diff --git a/frontend/src/pages/SubscriberCreate/FormCharingConfig.tsx b/frontend/src/pages/SubscriberCreate/FormCharingConfig.tsx
new file mode 100644
index 00000000..de8d98dd
--- /dev/null
+++ b/frontend/src/pages/SubscriberCreate/FormCharingConfig.tsx
@@ -0,0 +1,201 @@
+import {
+ FormControl,
+ InputLabel,
+ MenuItem,
+ Select,
+ Table,
+ TableBody,
+ TableCell,
+ TableRow,
+ TextField,
+} from "@mui/material";
+import { useSubscriptionForm } from "../../hooks/subscription-form";
+import { Controller } from "react-hook-form";
+
+interface FormCharginConfigProps {
+ snssaiIndex: number;
+ dnn?: string;
+ filterIndex?: number;
+}
+
+function FormSliceChargingConfig({ snssaiIndex }: FormCharginConfigProps) {
+ const { register, validationErrors, watch, control } = useSubscriptionForm();
+
+ const isOnlineCharging =
+ watch(`SnssaiConfigurations.${snssaiIndex}.chargingData.chargingMethod`) === "Online";
+
+ return (
+
+
+
+
+
+ Charging Method
+ (
+
+ )}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function FormFlowChargingConfig({ snssaiIndex, dnn, filterIndex }: FormCharginConfigProps) {
+ const { register, validationErrors, watch, control } = useSubscriptionForm();
+
+ if (dnn === undefined) {
+ throw new Error("dnn is undefined");
+ }
+ if (filterIndex === undefined) {
+ throw new Error("filterIndex is undefined");
+ }
+
+ const isOnlineCharging =
+ watch(
+ `SnssaiConfigurations.${snssaiIndex}.dnnConfigurations.${dnn}.flowRules.${filterIndex}.chargingData.chargingMethod`,
+ ) === "Online";
+
+ return (
+
+
+
+
+
+ Charging Method
+ (
+
+ )}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default function FormChargingConfig(props: FormCharginConfigProps) {
+ if (props.dnn === undefined) {
+ return ;
+ }
+
+ return ;
+}
diff --git a/frontend/src/pages/SubscriberCreate/FormFlowRule.tsx b/frontend/src/pages/SubscriberCreate/FormFlowRule.tsx
new file mode 100644
index 00000000..c6c4b8eb
--- /dev/null
+++ b/frontend/src/pages/SubscriberCreate/FormFlowRule.tsx
@@ -0,0 +1,267 @@
+import {
+ Button,
+ Box,
+ Card,
+ Grid,
+ Table,
+ TableBody,
+ TableCell,
+ TableRow,
+ TextField,
+} from "@mui/material";
+import type { Nssai } from "../../api";
+import { useSubscriptionForm } from "../../hooks/subscription-form";
+import { toHex } from "../../lib/utils";
+import FormChargingConfig from "./FormCharingConfig";
+import { useFieldArray } from "react-hook-form";
+import { defaultFlowRule } from "../../lib/dtos/subscription";
+
+interface FormFlowRuleProps {
+ snssaiIndex: number;
+ dnn: string;
+ snssai: Nssai;
+}
+
+export default function FormFlowRule({ snssaiIndex, dnn, snssai }: FormFlowRuleProps) {
+ const { register, validationErrors, control } = useSubscriptionForm();
+ const {
+ fields: flowRules,
+ append: appendFlowRule,
+ remove: removeFlowRule,
+ } = useFieldArray({
+ control,
+ name: `SnssaiConfigurations.${snssaiIndex}.dnnConfigurations.${dnn}.flowRules`,
+ });
+
+ const flowKey = toHex(snssai.sst) + snssai.sd;
+ const idPrefix = flowKey + "-" + dnn + "-";
+
+ return (
+ <>
+ {flowRules.map((flow, index) => (
+
+
+
+
+ Flow Rules {index + 1}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Keep layout aligned*/}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/SubscriberCreate/FormUpSecurity.tsx b/frontend/src/pages/SubscriberCreate/FormUpSecurity.tsx
new file mode 100644
index 00000000..83b9b798
--- /dev/null
+++ b/frontend/src/pages/SubscriberCreate/FormUpSecurity.tsx
@@ -0,0 +1,161 @@
+import {
+ Button,
+ Box,
+ Card,
+ FormControl,
+ Grid,
+ InputLabel,
+ MenuItem,
+ Select,
+ Table,
+ TableBody,
+ TableCell,
+ TableRow,
+ SelectChangeEvent,
+} from "@mui/material";
+import { Controller } from "react-hook-form";
+import { useSubscriptionForm } from "../../hooks/subscription-form";
+import { defaultUpSecurity } from "../../lib/dtos/subscription";
+
+interface FormUpSecurityProps {
+ sessionIndex: number;
+ dnnKey: string;
+}
+
+function NoUpSecurity(props: FormUpSecurityProps) {
+ const { watch, setValue } = useSubscriptionForm();
+
+ const dnnConfig = watch(
+ `SnssaiConfigurations.${props.sessionIndex}.dnnConfigurations.${props.dnnKey}`,
+ );
+
+ const onUpSecurity = () => {
+ dnnConfig.upSecurity = defaultUpSecurity();
+
+ setValue(
+ `SnssaiConfigurations.${props.sessionIndex}.dnnConfigurations.${props.dnnKey}`,
+ dnnConfig,
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default function FormUpSecurity(props: FormUpSecurityProps) {
+ const { register, validationErrors, watch, control, getValues, setValue } = useSubscriptionForm();
+
+ const dnnConfig = watch(
+ `SnssaiConfigurations.${props.sessionIndex}.dnnConfigurations.${props.dnnKey}`,
+ );
+
+ if (!(dnnConfig.upSecurity !== undefined)) {
+ return ;
+ }
+
+ const onUpSecurityDelete = () => {
+ setValue(`SnssaiConfigurations.${props.sessionIndex}.dnnConfigurations.${props.dnnKey}`, {
+ ...dnnConfig,
+ upSecurity: undefined,
+ });
+ };
+
+ return (
+
+
+
+
+ UP Security
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrity of UP Security
+ (
+
+ )}
+ />
+
+
+
+
+
+
+
+
+
+ Confidentiality of UP Security
+ (
+
+ )}
+ />
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/SubscriberCreate/SubscriberFormBasic.tsx b/frontend/src/pages/SubscriberCreate/SubscriberFormBasic.tsx
new file mode 100644
index 00000000..03e7c2de
--- /dev/null
+++ b/frontend/src/pages/SubscriberCreate/SubscriberFormBasic.tsx
@@ -0,0 +1,183 @@
+import { Controller } from "react-hook-form";
+import { useSubscriptionForm } from "../../hooks/subscription-form";
+import {
+ Card,
+ FormControl,
+ InputLabel,
+ MenuItem,
+ Select,
+ Table,
+ TableBody,
+ TableCell,
+ TableRow,
+ TextField,
+} from "@mui/material";
+
+export default function SubscriberFormBasic() {
+ const { register, validationErrors, control } = useSubscriptionForm();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Authentication Method
+ (
+
+ )}
+ />
+
+
+
+
+
+
+
+
+ Operator Code Type
+ (
+
+ )}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/SubscriberCreate/SubscriberFormSessions.tsx b/frontend/src/pages/SubscriberCreate/SubscriberFormSessions.tsx
new file mode 100644
index 00000000..644c5940
--- /dev/null
+++ b/frontend/src/pages/SubscriberCreate/SubscriberFormSessions.tsx
@@ -0,0 +1,373 @@
+import {
+ Button,
+ Box,
+ Card,
+ Checkbox,
+ Grid,
+ Table,
+ TableBody,
+ TableCell,
+ TableRow,
+ TextField,
+ Switch,
+} from "@mui/material";
+import { useSubscriptionForm } from "../../hooks/subscription-form";
+import { toHex } from "../../lib/utils";
+import FormChargingConfig from "./FormCharingConfig";
+import FormFlowRule from "./FormFlowRule";
+import FormUpSecurity from "./FormUpSecurity";
+import axios from "../../axios";
+import { Controller, useFieldArray } from "react-hook-form";
+import { defaultDnnConfig, defaultSnssaiConfiguration } from "../../lib/dtos/subscription";
+import { useState } from "react";
+
+interface VerifyScope {
+ supi: string;
+ sd: string;
+ sst: number;
+ dnn: string;
+ ipaddr: string;
+}
+
+interface VerifyResult {
+ ipaddr: string;
+ valid: boolean;
+ cause: string;
+}
+
+const handleVerifyStaticIp = (sd: string, sst: number, dnn: string, ipaddr: string) => {
+ const scope: VerifyScope = {
+ supi: "",
+ sd: sd,
+ sst: sst,
+ dnn: dnn,
+ ipaddr: ipaddr,
+ };
+ axios.post("/api/verify-staticip", scope).then((res) => {
+ const result = res.data as VerifyResult;
+ console.log(result);
+ if (result["valid"] === true) {
+ alert("OK\n" + result.ipaddr);
+ } else {
+ alert("NO!\nCause: " + result["cause"]);
+ }
+ });
+};
+
+export default function SubscriberFormSessions() {
+ const { register, validationErrors, watch, control, setFocus } = useSubscriptionForm();
+
+ const {
+ fields: snssaiConfigurations,
+ append: appendSnssaiConfiguration,
+ remove: removeSnssaiConfiguration,
+ update: updateSnssaiConfiguration,
+ } = useFieldArray({
+ control,
+ name: "SnssaiConfigurations",
+ });
+
+ const [dnnName, setDnnName] = useState(Array(snssaiConfigurations.length).fill(""));
+
+ const handleChangeDNN = (
+ event: React.ChangeEvent,
+ index: number,
+ ): void => {
+ setDnnName((dnnName) => dnnName.map((name, i) => (index === i ? event.target.value : name)));
+ };
+
+ const onDnnAdd = (index: number) => {
+ const name = dnnName[index];
+ if (name === undefined || name === "") {
+ return;
+ }
+
+ const snssaiConfig = watch(`SnssaiConfigurations.${index}`);
+ updateSnssaiConfiguration(index, {
+ ...snssaiConfig,
+ dnnConfigurations: {
+ ...snssaiConfig.dnnConfigurations,
+ [name]: defaultDnnConfig(),
+ },
+ });
+
+ setTimeout(() => {
+ /* IMPORTANT: setFocus after rerender */
+ setFocus(`SnssaiConfigurations.${index}.dnnConfigurations.${name}.sessionAmbr.uplink`);
+ });
+
+ // restore input field
+ setDnnName((dnnName) => dnnName.map((name, i) => (index === i ? "" : name)));
+ };
+
+ const onDnnDelete = (index: number, dnn: string, slice: string) => {
+ const snssaiConfig = watch(`SnssaiConfigurations.${index}`);
+ const newDnnConfigurations = { ...snssaiConfig.dnnConfigurations };
+ delete newDnnConfigurations[dnn];
+
+ updateSnssaiConfiguration(index, {
+ ...snssaiConfig,
+ dnnConfigurations: newDnnConfigurations,
+ });
+ };
+
+ return (
+ <>
+ {snssaiConfigurations?.map((row, index) => (
+
+
+
+ S-NSSAI Configuragtion ({toHex(row.sst) + row.sd})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Default S-NSSAI
+
+ }
+ />
+
+
+
+
+
+
+
+ {Object.keys(row.dnnConfigurations).map((dnn) => (
+
+
+
+
+ DNN Configurations
+
+
+
+
+
+
+
+
+
+
+
+
+ {dnn}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+ handleChangeDNN(ev, index)}
+ />
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/SubscriberCreate/SubscriberFormUeAmbr.tsx b/frontend/src/pages/SubscriberCreate/SubscriberFormUeAmbr.tsx
new file mode 100644
index 00000000..655acb94
--- /dev/null
+++ b/frontend/src/pages/SubscriberCreate/SubscriberFormUeAmbr.tsx
@@ -0,0 +1,43 @@
+import { Card, Table, TableBody, TableCell, TableRow, TextField } from "@mui/material";
+import { useSubscriptionForm } from "../../hooks/subscription-form";
+
+export default function SubscriberFormUeAmbr() {
+ const { register, validationErrors } = useSubscriptionForm();
+
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/pages/SubscriberCreate/index.tsx b/frontend/src/pages/SubscriberCreate/index.tsx
new file mode 100644
index 00000000..fecae841
--- /dev/null
+++ b/frontend/src/pages/SubscriberCreate/index.tsx
@@ -0,0 +1,166 @@
+import React, { useState } from "react";
+import { useEffect } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+
+import axios from "../../axios";
+
+import Dashboard from "../../Dashboard";
+import { Button, Grid } from "@mui/material";
+import { SubscriberFormProvider, useSubscriptionForm } from "../../hooks/subscription-form";
+import SubscriberFormBasic from "./SubscriberFormBasic";
+import SubscriberFormUeAmbr from "./SubscriberFormUeAmbr";
+import SubscriberFormSessions from "./SubscriberFormSessions";
+import { FlowsMapperImpl, SubscriptionMapperImpl } from "../../lib/dtos/subscription";
+
+function FormHOC(Component: React.ComponentType) {
+ return function (props: any) {
+ return (
+
+
+
+ );
+ };
+}
+
+export default FormHOC(SubscriberCreate);
+
+function SubscriberCreate() {
+ const { id, plmn } = useParams<{
+ id: string;
+ plmn: string;
+ }>();
+
+ const isNewSubscriber = id === undefined && plmn === undefined;
+ const navigation = useNavigate();
+ const [loading, setLoading] = useState(false);
+
+ const { handleSubmit, getValues, reset } = useSubscriptionForm();
+
+ if (!isNewSubscriber) {
+ useEffect(() => {
+ setLoading(true);
+
+ axios
+ .get("/api/subscriber/" + id + "/" + plmn)
+ .then((res) => {
+ const subscriberMapper = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriberMapper.mapFromSubscription(res.data);
+ reset(subscription);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }, [id]);
+ }
+
+ if (loading) {
+ return Loading...
;
+ }
+
+ const supiIncrement = (supi: string): string => {
+ const imsi = supi.split("-", 2);
+ if (imsi.length !== 2) {
+ return supi;
+ }
+ let number = Number(imsi[1]);
+ number += 1;
+ return "imsi-" + number;
+ };
+
+ const onCreate = () => {
+ console.log("trace: onCreate");
+
+ const data = getValues();
+
+ if (data.SnssaiConfigurations.length === 0) {
+ alert("Please add at least one S-NSSAI");
+ return;
+ }
+
+ const subscriberMapper = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriberMapper.mapFromDto(data);
+
+ // Iterate subscriber data number.
+ let supi = subscription.ueId;
+ for (let i = 0; i < subscription.userNumber!; i++) {
+ subscription.ueId = supi;
+ axios
+ .post("/api/subscriber/" + subscription.ueId + "/" + subscription.plmnID, subscription)
+ .then(() => {
+ navigation("/subscriber");
+ })
+ .catch((err) => {
+ if (err.response) {
+ const msg = "Status: " + err.response.status;
+ if (err.response.data.cause) {
+ alert(msg + ", cause: " + err.response.data.cause);
+ } else if (err.response.data) {
+ alert(msg + ", data:" + err.response.data);
+ } else {
+ alert(msg);
+ }
+ } else {
+ alert(err.message);
+ }
+ console.log(err);
+ return;
+ });
+ supi = supiIncrement(supi);
+ }
+ };
+
+ const onUpdate = () => {
+ console.log("trace: onUpdate");
+
+ const data = getValues();
+ const subscriberMapper = new SubscriptionMapperImpl(new FlowsMapperImpl());
+ const subscription = subscriberMapper.mapFromDto(data);
+
+ axios
+ .put("/api/subscriber/" + subscription.ueId + "/" + subscription.plmnID, subscription)
+ .then(() => {
+ navigation("/subscriber/" + subscription.ueId + "/" + subscription.plmnID);
+ })
+ .catch((err) => {
+ if (err.response) {
+ const msg = "Status: " + err.response.status;
+ if (err.response.data.cause) {
+ alert(msg + ", cause: " + err.response.data.cause);
+ } else if (err.response.data) {
+ alert(msg + ", data:" + err.response.data);
+ } else {
+ alert(msg);
+ }
+ } else {
+ alert(err.message);
+ }
+ });
+ };
+
+ const formSubmitFn = isNewSubscriber ? onCreate : onUpdate;
+ const formSubmitText = isNewSubscriber ? "CREATE" : "UPDATE";
+
+ return (
+ {}}>
+
+
+ );
+}
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 8ed0ab72..0774377a 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -12,6 +12,16 @@ __metadata:
languageName: node
linkType: hard
+"@ampproject/remapping@npm:^2.3.0":
+ version: 2.3.0
+ resolution: "@ampproject/remapping@npm:2.3.0"
+ dependencies:
+ "@jridgewell/gen-mapping": "npm:^0.3.5"
+ "@jridgewell/trace-mapping": "npm:^0.3.24"
+ checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed
+ languageName: node
+ linkType: hard
+
"@babel/code-frame@npm:^7.0.0":
version: 7.23.5
resolution: "@babel/code-frame@npm:7.23.5"
@@ -229,6 +239,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/aix-ppc64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/aix-ppc64@npm:0.21.5"
+ conditions: os=aix & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/android-arm64@npm:0.20.2"
@@ -236,6 +253,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/android-arm64@npm:0.21.5"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/android-arm@npm:0.20.2"
@@ -243,6 +267,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/android-arm@npm:0.21.5"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/android-x64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/android-x64@npm:0.20.2"
@@ -250,6 +281,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/android-x64@npm:0.21.5"
+ conditions: os=android & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-arm64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/darwin-arm64@npm:0.20.2"
@@ -257,6 +295,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-arm64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/darwin-arm64@npm:0.21.5"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-x64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/darwin-x64@npm:0.20.2"
@@ -264,6 +309,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/darwin-x64@npm:0.21.5"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-arm64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/freebsd-arm64@npm:0.20.2"
@@ -271,6 +323,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-arm64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/freebsd-arm64@npm:0.21.5"
+ conditions: os=freebsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-x64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/freebsd-x64@npm:0.20.2"
@@ -278,6 +337,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/freebsd-x64@npm:0.21.5"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/linux-arm64@npm:0.20.2"
@@ -285,6 +351,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-arm64@npm:0.21.5"
+ conditions: os=linux & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/linux-arm@npm:0.20.2"
@@ -292,6 +365,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-arm@npm:0.21.5"
+ conditions: os=linux & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ia32@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/linux-ia32@npm:0.20.2"
@@ -299,6 +379,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ia32@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-ia32@npm:0.21.5"
+ conditions: os=linux & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-loong64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/linux-loong64@npm:0.20.2"
@@ -306,6 +393,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-loong64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-loong64@npm:0.21.5"
+ conditions: os=linux & cpu=loong64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-mips64el@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/linux-mips64el@npm:0.20.2"
@@ -313,6 +407,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-mips64el@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-mips64el@npm:0.21.5"
+ conditions: os=linux & cpu=mips64el
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ppc64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/linux-ppc64@npm:0.20.2"
@@ -320,6 +421,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ppc64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-ppc64@npm:0.21.5"
+ conditions: os=linux & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-riscv64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/linux-riscv64@npm:0.20.2"
@@ -327,6 +435,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-riscv64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-riscv64@npm:0.21.5"
+ conditions: os=linux & cpu=riscv64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-s390x@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/linux-s390x@npm:0.20.2"
@@ -334,6 +449,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-s390x@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-s390x@npm:0.21.5"
+ conditions: os=linux & cpu=s390x
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-x64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/linux-x64@npm:0.20.2"
@@ -341,6 +463,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/linux-x64@npm:0.21.5"
+ conditions: os=linux & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/netbsd-x64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/netbsd-x64@npm:0.20.2"
@@ -348,6 +477,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/netbsd-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/netbsd-x64@npm:0.21.5"
+ conditions: os=netbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/openbsd-x64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/openbsd-x64@npm:0.20.2"
@@ -355,6 +491,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openbsd-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/openbsd-x64@npm:0.21.5"
+ conditions: os=openbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/sunos-x64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/sunos-x64@npm:0.20.2"
@@ -362,6 +505,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/sunos-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/sunos-x64@npm:0.21.5"
+ conditions: os=sunos & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-arm64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/win32-arm64@npm:0.20.2"
@@ -369,6 +519,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-arm64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/win32-arm64@npm:0.21.5"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-ia32@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/win32-ia32@npm:0.20.2"
@@ -376,6 +533,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-ia32@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/win32-ia32@npm:0.21.5"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-x64@npm:0.20.2":
version: 0.20.2
resolution: "@esbuild/win32-x64@npm:0.20.2"
@@ -383,6 +547,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-x64@npm:0.21.5":
+ version: 0.21.5
+ resolution: "@esbuild/win32-x64@npm:0.21.5"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0":
version: 4.4.0
resolution: "@eslint-community/eslint-utils@npm:4.4.0"
@@ -463,6 +634,15 @@ __metadata:
languageName: node
linkType: hard
+"@hookform/resolvers@npm:^3.6.0":
+ version: 3.6.0
+ resolution: "@hookform/resolvers@npm:3.6.0"
+ peerDependencies:
+ react-hook-form: ^7.0.0
+ checksum: 10c0/3577949a82b02b823b81592ec44630d28dc8748e9c42e57d8a19046670bb6d270d5e2c4780d65193c206bc18abb5575a0a5e261fa7aac17198faee1823208be6
+ languageName: node
+ linkType: hard
+
"@humanwhocodes/config-array@npm:^0.11.14":
version: 0.11.14
resolution: "@humanwhocodes/config-array@npm:0.11.14"
@@ -502,6 +682,48 @@ __metadata:
languageName: node
linkType: hard
+"@jridgewell/gen-mapping@npm:^0.3.5":
+ version: 0.3.5
+ resolution: "@jridgewell/gen-mapping@npm:0.3.5"
+ dependencies:
+ "@jridgewell/set-array": "npm:^1.2.1"
+ "@jridgewell/sourcemap-codec": "npm:^1.4.10"
+ "@jridgewell/trace-mapping": "npm:^0.3.24"
+ checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb
+ languageName: node
+ linkType: hard
+
+"@jridgewell/resolve-uri@npm:^3.1.0":
+ version: 3.1.2
+ resolution: "@jridgewell/resolve-uri@npm:3.1.2"
+ checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e
+ languageName: node
+ linkType: hard
+
+"@jridgewell/set-array@npm:^1.2.1":
+ version: 1.2.1
+ resolution: "@jridgewell/set-array@npm:1.2.1"
+ checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4
+ languageName: node
+ linkType: hard
+
+"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15":
+ version: 1.5.0
+ resolution: "@jridgewell/sourcemap-codec@npm:1.5.0"
+ checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18
+ languageName: node
+ linkType: hard
+
+"@jridgewell/trace-mapping@npm:^0.3.24":
+ version: 0.3.25
+ resolution: "@jridgewell/trace-mapping@npm:0.3.25"
+ dependencies:
+ "@jridgewell/resolve-uri": "npm:^3.1.0"
+ "@jridgewell/sourcemap-codec": "npm:^1.4.14"
+ checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4
+ languageName: node
+ linkType: hard
+
"@mui/base@npm:5.0.0-beta.37":
version: 5.0.0-beta.37
resolution: "@mui/base@npm:5.0.0-beta.37"
@@ -988,7 +1210,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/estree@npm:1.0.5":
+"@types/estree@npm:1.0.5, @types/estree@npm:^1.0.0":
version: 1.0.5
resolution: "@types/estree@npm:1.0.5"
checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d
@@ -1216,6 +1438,69 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/expect@npm:2.0.2":
+ version: 2.0.2
+ resolution: "@vitest/expect@npm:2.0.2"
+ dependencies:
+ "@vitest/spy": "npm:2.0.2"
+ "@vitest/utils": "npm:2.0.2"
+ chai: "npm:^5.1.1"
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10c0/6f541f2f25244f41e9054699713ac9aedf1c82b82f6e0d4d4863565b352ff32794c2220f23603a01fc22b1eecbb9ea8e09eb2c93d80f7322c2b438a5e084ec08
+ languageName: node
+ linkType: hard
+
+"@vitest/pretty-format@npm:2.0.2, @vitest/pretty-format@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "@vitest/pretty-format@npm:2.0.2"
+ dependencies:
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10c0/85749fae2ebcf7950c7a019a11b4272def00ee6568b6179e377e7374fb3b9ef6bd5bbef16c110b17881a3d1c772e315cb13a852758b08296b5a4bc665426952b
+ languageName: node
+ linkType: hard
+
+"@vitest/runner@npm:2.0.2":
+ version: 2.0.2
+ resolution: "@vitest/runner@npm:2.0.2"
+ dependencies:
+ "@vitest/utils": "npm:2.0.2"
+ pathe: "npm:^1.1.2"
+ checksum: 10c0/f4454b67f0c11318515ed6498cf8aa58ae18b6630a13d31201b55626c1c166dc07ceedd11ef373229595559724a6d3e8c961a8dfd8cc627c3b82bb38de0be40e
+ languageName: node
+ linkType: hard
+
+"@vitest/snapshot@npm:2.0.2":
+ version: 2.0.2
+ resolution: "@vitest/snapshot@npm:2.0.2"
+ dependencies:
+ "@vitest/pretty-format": "npm:2.0.2"
+ magic-string: "npm:^0.30.10"
+ pathe: "npm:^1.1.2"
+ checksum: 10c0/7cb5e16d8a10ce71ec33cec57b191d28b82c4204b986a0ad04a956b401ae47d019f28a3680ee4256105bf9259f6df1208bff93fc4dc4b3e333a8c273f6b39200
+ languageName: node
+ linkType: hard
+
+"@vitest/spy@npm:2.0.2":
+ version: 2.0.2
+ resolution: "@vitest/spy@npm:2.0.2"
+ dependencies:
+ tinyspy: "npm:^3.0.0"
+ checksum: 10c0/7ef32945fc2a83add963da9baf35c6c2fa5b35afb03ab96fe1289ef5bbf3e85ef30bb6b80706f06935351ef10399551d37a993d58958260fe4b21f605a08c1a0
+ languageName: node
+ linkType: hard
+
+"@vitest/utils@npm:2.0.2":
+ version: 2.0.2
+ resolution: "@vitest/utils@npm:2.0.2"
+ dependencies:
+ "@vitest/pretty-format": "npm:2.0.2"
+ estree-walker: "npm:^3.0.3"
+ loupe: "npm:^3.1.1"
+ tinyrainbow: "npm:^1.2.0"
+ checksum: 10c0/d1f99ab1ea38ff36150405b2df390ec09cd0f014d05d3ae500c44bdc8746a14f34be3bf76c2a44c352b1c40d130bdf477c1043159ac62f2d63765aa1a8bb82a5
+ languageName: node
+ linkType: hard
+
"abbrev@npm:^2.0.0":
version: 2.0.0
resolution: "abbrev@npm:2.0.0"
@@ -1452,6 +1737,13 @@ __metadata:
languageName: node
linkType: hard
+"assertion-error@npm:^2.0.1":
+ version: 2.0.1
+ resolution: "assertion-error@npm:2.0.1"
+ checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8
+ languageName: node
+ linkType: hard
+
"asynciterator.prototype@npm:^1.0.0":
version: 1.0.0
resolution: "asynciterator.prototype@npm:1.0.0"
@@ -1534,6 +1826,13 @@ __metadata:
languageName: node
linkType: hard
+"cac@npm:^6.7.14":
+ version: 6.7.14
+ resolution: "cac@npm:6.7.14"
+ checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10
+ languageName: node
+ linkType: hard
+
"cacache@npm:^18.0.0":
version: 18.0.2
resolution: "cacache@npm:18.0.2"
@@ -1574,6 +1873,19 @@ __metadata:
languageName: node
linkType: hard
+"chai@npm:^5.1.1":
+ version: 5.1.1
+ resolution: "chai@npm:5.1.1"
+ dependencies:
+ assertion-error: "npm:^2.0.1"
+ check-error: "npm:^2.1.1"
+ deep-eql: "npm:^5.0.1"
+ loupe: "npm:^3.1.0"
+ pathval: "npm:^2.0.0"
+ checksum: 10c0/e7f00e5881e3d5224f08fe63966ed6566bd9fdde175863c7c16dd5240416de9b34c4a0dd925f4fd64ad56256ca6507d32cf6131c49e1db65c62578eb31d4566c
+ languageName: node
+ linkType: hard
+
"chalk@npm:^2.4.2":
version: 2.4.2
resolution: "chalk@npm:2.4.2"
@@ -1595,6 +1907,13 @@ __metadata:
languageName: node
linkType: hard
+"check-error@npm:^2.1.1":
+ version: 2.1.1
+ resolution: "check-error@npm:2.1.1"
+ checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e
+ languageName: node
+ linkType: hard
+
"chownr@npm:^2.0.0":
version: 2.0.0
resolution: "chownr@npm:2.0.0"
@@ -1691,7 +2010,7 @@ __metadata:
languageName: node
linkType: hard
-"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2":
+"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
version: 7.0.3
resolution: "cross-spawn@npm:7.0.3"
dependencies:
@@ -1737,6 +2056,25 @@ __metadata:
languageName: node
linkType: hard
+"debug@npm:^4.3.5":
+ version: 4.3.5
+ resolution: "debug@npm:4.3.5"
+ dependencies:
+ ms: "npm:2.1.2"
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ checksum: 10c0/082c375a2bdc4f4469c99f325ff458adad62a3fc2c482d59923c260cb08152f34e2659f72b3767db8bb2f21ca81a60a42d1019605a412132d7b9f59363a005cc
+ languageName: node
+ linkType: hard
+
+"deep-eql@npm:^5.0.1":
+ version: 5.0.2
+ resolution: "deep-eql@npm:5.0.2"
+ checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247
+ languageName: node
+ linkType: hard
+
"deep-is@npm:^0.1.3":
version: 0.1.4
resolution: "deep-is@npm:0.1.4"
@@ -2069,6 +2407,86 @@ __metadata:
languageName: node
linkType: hard
+"esbuild@npm:^0.21.3":
+ version: 0.21.5
+ resolution: "esbuild@npm:0.21.5"
+ dependencies:
+ "@esbuild/aix-ppc64": "npm:0.21.5"
+ "@esbuild/android-arm": "npm:0.21.5"
+ "@esbuild/android-arm64": "npm:0.21.5"
+ "@esbuild/android-x64": "npm:0.21.5"
+ "@esbuild/darwin-arm64": "npm:0.21.5"
+ "@esbuild/darwin-x64": "npm:0.21.5"
+ "@esbuild/freebsd-arm64": "npm:0.21.5"
+ "@esbuild/freebsd-x64": "npm:0.21.5"
+ "@esbuild/linux-arm": "npm:0.21.5"
+ "@esbuild/linux-arm64": "npm:0.21.5"
+ "@esbuild/linux-ia32": "npm:0.21.5"
+ "@esbuild/linux-loong64": "npm:0.21.5"
+ "@esbuild/linux-mips64el": "npm:0.21.5"
+ "@esbuild/linux-ppc64": "npm:0.21.5"
+ "@esbuild/linux-riscv64": "npm:0.21.5"
+ "@esbuild/linux-s390x": "npm:0.21.5"
+ "@esbuild/linux-x64": "npm:0.21.5"
+ "@esbuild/netbsd-x64": "npm:0.21.5"
+ "@esbuild/openbsd-x64": "npm:0.21.5"
+ "@esbuild/sunos-x64": "npm:0.21.5"
+ "@esbuild/win32-arm64": "npm:0.21.5"
+ "@esbuild/win32-ia32": "npm:0.21.5"
+ "@esbuild/win32-x64": "npm:0.21.5"
+ dependenciesMeta:
+ "@esbuild/aix-ppc64":
+ optional: true
+ "@esbuild/android-arm":
+ optional: true
+ "@esbuild/android-arm64":
+ optional: true
+ "@esbuild/android-x64":
+ optional: true
+ "@esbuild/darwin-arm64":
+ optional: true
+ "@esbuild/darwin-x64":
+ optional: true
+ "@esbuild/freebsd-arm64":
+ optional: true
+ "@esbuild/freebsd-x64":
+ optional: true
+ "@esbuild/linux-arm":
+ optional: true
+ "@esbuild/linux-arm64":
+ optional: true
+ "@esbuild/linux-ia32":
+ optional: true
+ "@esbuild/linux-loong64":
+ optional: true
+ "@esbuild/linux-mips64el":
+ optional: true
+ "@esbuild/linux-ppc64":
+ optional: true
+ "@esbuild/linux-riscv64":
+ optional: true
+ "@esbuild/linux-s390x":
+ optional: true
+ "@esbuild/linux-x64":
+ optional: true
+ "@esbuild/netbsd-x64":
+ optional: true
+ "@esbuild/openbsd-x64":
+ optional: true
+ "@esbuild/sunos-x64":
+ optional: true
+ "@esbuild/win32-arm64":
+ optional: true
+ "@esbuild/win32-ia32":
+ optional: true
+ "@esbuild/win32-x64":
+ optional: true
+ bin:
+ esbuild: bin/esbuild
+ checksum: 10c0/fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de
+ languageName: node
+ linkType: hard
+
"escape-string-regexp@npm:^1.0.5":
version: 1.0.5
resolution: "escape-string-regexp@npm:1.0.5"
@@ -2321,6 +2739,15 @@ __metadata:
languageName: node
linkType: hard
+"estree-walker@npm:^3.0.3":
+ version: 3.0.3
+ resolution: "estree-walker@npm:3.0.3"
+ dependencies:
+ "@types/estree": "npm:^1.0.0"
+ checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d
+ languageName: node
+ linkType: hard
+
"esutils@npm:^2.0.2":
version: 2.0.3
resolution: "esutils@npm:2.0.3"
@@ -2328,6 +2755,23 @@ __metadata:
languageName: node
linkType: hard
+"execa@npm:^8.0.1":
+ version: 8.0.1
+ resolution: "execa@npm:8.0.1"
+ dependencies:
+ cross-spawn: "npm:^7.0.3"
+ get-stream: "npm:^8.0.1"
+ human-signals: "npm:^5.0.0"
+ is-stream: "npm:^3.0.0"
+ merge-stream: "npm:^2.0.0"
+ npm-run-path: "npm:^5.1.0"
+ onetime: "npm:^6.0.0"
+ signal-exit: "npm:^4.1.0"
+ strip-final-newline: "npm:^3.0.0"
+ checksum: 10c0/2c52d8775f5bf103ce8eec9c7ab3059909ba350a5164744e9947ed14a53f51687c040a250bda833f906d1283aa8803975b84e6c8f7a7c42f99dc8ef80250d1af
+ languageName: node
+ linkType: hard
+
"exponential-backoff@npm:^3.1.1":
version: 3.1.1
resolution: "exponential-backoff@npm:3.1.1"
@@ -2548,6 +2992,13 @@ __metadata:
languageName: node
linkType: hard
+"get-func-name@npm:^2.0.1":
+ version: 2.0.2
+ resolution: "get-func-name@npm:2.0.2"
+ checksum: 10c0/89830fd07623fa73429a711b9daecdb304386d237c71268007f788f113505ef1d4cc2d0b9680e072c5082490aec9df5d7758bf5ac6f1c37062855e8e3dc0b9df
+ languageName: node
+ linkType: hard
+
"get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4":
version: 1.2.4
resolution: "get-intrinsic@npm:1.2.4"
@@ -2561,6 +3012,13 @@ __metadata:
languageName: node
linkType: hard
+"get-stream@npm:^8.0.1":
+ version: 8.0.1
+ resolution: "get-stream@npm:8.0.1"
+ checksum: 10c0/5c2181e98202b9dae0bb4a849979291043e5892eb40312b47f0c22b9414fc9b28a3b6063d2375705eb24abc41ecf97894d9a51f64ff021511b504477b27b4290
+ languageName: node
+ linkType: hard
+
"get-symbol-description@npm:^1.0.2":
version: 1.0.2
resolution: "get-symbol-description@npm:1.0.2"
@@ -2772,6 +3230,13 @@ __metadata:
languageName: node
linkType: hard
+"human-signals@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "human-signals@npm:5.0.0"
+ checksum: 10c0/5a9359073fe17a8b58e5a085e9a39a950366d9f00217c4ff5878bd312e09d80f460536ea6a3f260b5943a01fe55c158d1cea3fc7bee3d0520aeef04f6d915c82
+ languageName: node
+ linkType: hard
+
"iconv-lite@npm:^0.6.2":
version: 0.6.3
resolution: "iconv-lite@npm:0.6.3"
@@ -3031,6 +3496,13 @@ __metadata:
languageName: node
linkType: hard
+"is-stream@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "is-stream@npm:3.0.0"
+ checksum: 10c0/eb2f7127af02ee9aa2a0237b730e47ac2de0d4e76a4a905a50a11557f2339df5765eaea4ceb8029f1efa978586abe776908720bfcb1900c20c6ec5145f6f29d8
+ languageName: node
+ linkType: hard
+
"is-string@npm:^1.0.5, is-string@npm:^1.0.7":
version: 1.0.7
resolution: "is-string@npm:1.0.7"
@@ -3260,6 +3732,15 @@ __metadata:
languageName: node
linkType: hard
+"loupe@npm:^3.1.0, loupe@npm:^3.1.1":
+ version: 3.1.1
+ resolution: "loupe@npm:3.1.1"
+ dependencies:
+ get-func-name: "npm:^2.0.1"
+ checksum: 10c0/99f88badc47e894016df0c403de846fedfea61154aadabbf776c8428dd59e8d8378007135d385d737de32ae47980af07d22ba7bec5ef7beebd721de9baa0a0af
+ languageName: node
+ linkType: hard
+
"lru-cache@npm:^10.0.1, lru-cache@npm:^9.1.1 || ^10.0.0":
version: 10.2.0
resolution: "lru-cache@npm:10.2.0"
@@ -3276,6 +3757,15 @@ __metadata:
languageName: node
linkType: hard
+"magic-string@npm:^0.30.10":
+ version: 0.30.10
+ resolution: "magic-string@npm:0.30.10"
+ dependencies:
+ "@jridgewell/sourcemap-codec": "npm:^1.4.15"
+ checksum: 10c0/aa9ca17eae571a19bce92c8221193b6f93ee8511abb10f085e55ffd398db8e4c089a208d9eac559deee96a08b7b24d636ea4ab92f09c6cf42a7d1af51f7fd62b
+ languageName: node
+ linkType: hard
+
"make-fetch-happen@npm:^13.0.0":
version: 13.0.0
resolution: "make-fetch-happen@npm:13.0.0"
@@ -3295,6 +3785,13 @@ __metadata:
languageName: node
linkType: hard
+"merge-stream@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "merge-stream@npm:2.0.0"
+ checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5
+ languageName: node
+ linkType: hard
+
"merge2@npm:^1.3.0, merge2@npm:^1.4.1":
version: 1.4.1
resolution: "merge2@npm:1.4.1"
@@ -3328,6 +3825,13 @@ __metadata:
languageName: node
linkType: hard
+"mimic-fn@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "mimic-fn@npm:4.0.0"
+ checksum: 10c0/de9cc32be9996fd941e512248338e43407f63f6d497abe8441fa33447d922e927de54d4cc3c1a3c6d652857acd770389d5a3823f311a744132760ce2be15ccbf
+ languageName: node
+ linkType: hard
+
"minimatch@npm:9.0.3, minimatch@npm:^9.0.1":
version: 9.0.3
resolution: "minimatch@npm:9.0.3"
@@ -3466,6 +3970,7 @@ __metadata:
dependencies:
"@emotion/react": "npm:^11.11.4"
"@emotion/styled": "npm:^11.11.0"
+ "@hookform/resolvers": "npm:^3.6.0"
"@mui/icons-material": "npm:^5.15.11"
"@mui/material": "npm:^5.15.11"
"@types/node": "npm:^20.11.24"
@@ -3486,10 +3991,13 @@ __metadata:
prettier: "npm:^3.2.5"
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
+ react-hook-form: "npm:^7.51.5"
react-router-dom: "npm:^6.22.2"
typescript: "npm:^5.3.3"
- vite: "npm:^5.1.7"
+ vite: "npm:^5.1.4"
+ vitest: "npm:^2.0.2"
web-vitals: "npm:^3.5.2"
+ zod: "npm:^3.23.8"
languageName: unknown
linkType: soft
@@ -3547,6 +4055,15 @@ __metadata:
languageName: node
linkType: hard
+"npm-run-path@npm:^5.1.0":
+ version: 5.3.0
+ resolution: "npm-run-path@npm:5.3.0"
+ dependencies:
+ path-key: "npm:^4.0.0"
+ checksum: 10c0/124df74820c40c2eb9a8612a254ea1d557ddfab1581c3e751f825e3e366d9f00b0d76a3c94ecd8398e7f3eee193018622677e95816e8491f0797b21e30b2deba
+ languageName: node
+ linkType: hard
+
"object-assign@npm:^4.1.1":
version: 4.1.1
resolution: "object-assign@npm:4.1.1"
@@ -3645,6 +4162,15 @@ __metadata:
languageName: node
linkType: hard
+"onetime@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "onetime@npm:6.0.0"
+ dependencies:
+ mimic-fn: "npm:^4.0.0"
+ checksum: 10c0/4eef7c6abfef697dd4479345a4100c382d73c149d2d56170a54a07418c50816937ad09500e1ed1e79d235989d073a9bade8557122aee24f0576ecde0f392bb6c
+ languageName: node
+ linkType: hard
+
"optionator@npm:^0.9.3":
version: 0.9.3
resolution: "optionator@npm:0.9.3"
@@ -3728,6 +4254,13 @@ __metadata:
languageName: node
linkType: hard
+"path-key@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "path-key@npm:4.0.0"
+ checksum: 10c0/794efeef32863a65ac312f3c0b0a99f921f3e827ff63afa5cb09a377e202c262b671f7b3832a4e64731003fa94af0263713962d317b9887bd1e0c48a342efba3
+ languageName: node
+ linkType: hard
+
"path-parse@npm:^1.0.7":
version: 1.0.7
resolution: "path-parse@npm:1.0.7"
@@ -3752,6 +4285,20 @@ __metadata:
languageName: node
linkType: hard
+"pathe@npm:^1.1.2":
+ version: 1.1.2
+ resolution: "pathe@npm:1.1.2"
+ checksum: 10c0/64ee0a4e587fb0f208d9777a6c56e4f9050039268faaaaecd50e959ef01bf847b7872785c36483fa5cdcdbdfdb31fef2ff222684d4fc21c330ab60395c681897
+ languageName: node
+ linkType: hard
+
+"pathval@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "pathval@npm:2.0.0"
+ checksum: 10c0/602e4ee347fba8a599115af2ccd8179836a63c925c23e04bd056d0674a64b39e3a081b643cc7bc0b84390517df2d800a46fcc5598d42c155fe4977095c2f77c5
+ languageName: node
+ linkType: hard
+
"picocolors@npm:^1.0.0":
version: 1.0.0
resolution: "picocolors@npm:1.0.0"
@@ -3759,6 +4306,13 @@ __metadata:
languageName: node
linkType: hard
+"picocolors@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "picocolors@npm:1.0.1"
+ checksum: 10c0/c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400
+ languageName: node
+ linkType: hard
+
"picomatch@npm:^2.3.1":
version: 2.3.1
resolution: "picomatch@npm:2.3.1"
@@ -3784,6 +4338,17 @@ __metadata:
languageName: node
linkType: hard
+"postcss@npm:^8.4.39":
+ version: 8.4.39
+ resolution: "postcss@npm:8.4.39"
+ dependencies:
+ nanoid: "npm:^3.3.7"
+ picocolors: "npm:^1.0.1"
+ source-map-js: "npm:^1.2.0"
+ checksum: 10c0/16f5ac3c4e32ee76d1582b3c0dcf1a1fdb91334a45ad755eeb881ccc50318fb8d64047de4f1601ac96e30061df203f0f2e2edbdc0bfc49b9c57bc9fb9bedaea3
+ languageName: node
+ linkType: hard
+
"prelude-ls@npm:^1.2.1":
version: 1.2.1
resolution: "prelude-ls@npm:1.2.1"
@@ -3870,6 +4435,15 @@ __metadata:
languageName: node
linkType: hard
+"react-hook-form@npm:^7.51.5":
+ version: 7.51.5
+ resolution: "react-hook-form@npm:7.51.5"
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18
+ checksum: 10c0/5b13f99a125d92ee618f2d4e218d5ec854f8cac1b568e83b4b125efb002cc79eef0f5c06b54015c78e16a830cf1ea356a646e8c6abb00b7ca8225dba9a92091e
+ languageName: node
+ linkType: hard
+
"react-is@npm:^16.13.1, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
@@ -4232,7 +4806,14 @@ __metadata:
languageName: node
linkType: hard
-"signal-exit@npm:^4.0.1":
+"siginfo@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "siginfo@npm:2.0.0"
+ checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34
+ languageName: node
+ linkType: hard
+
+"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0":
version: 4.1.0
resolution: "signal-exit@npm:4.1.0"
checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83
@@ -4304,6 +4885,20 @@ __metadata:
languageName: node
linkType: hard
+"stackback@npm:0.0.2":
+ version: 0.0.2
+ resolution: "stackback@npm:0.0.2"
+ checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983
+ languageName: node
+ linkType: hard
+
+"std-env@npm:^3.7.0":
+ version: 3.7.0
+ resolution: "std-env@npm:3.7.0"
+ checksum: 10c0/60edf2d130a4feb7002974af3d5a5f3343558d1ccf8d9b9934d225c638606884db4a20d2fe6440a09605bca282af6b042ae8070a10490c0800d69e82e478f41e
+ languageName: node
+ linkType: hard
+
"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0":
version: 4.2.3
resolution: "string-width@npm:4.2.3"
@@ -4401,6 +4996,13 @@ __metadata:
languageName: node
linkType: hard
+"strip-final-newline@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "strip-final-newline@npm:3.0.0"
+ checksum: 10c0/a771a17901427bac6293fd416db7577e2bc1c34a19d38351e9d5478c3c415f523f391003b42ed475f27e33a78233035df183525395f731d3bfb8cdcbd4da08ce
+ languageName: node
+ linkType: hard
+
"strip-json-comments@npm:^3.1.1":
version: 3.1.1
resolution: "strip-json-comments@npm:3.1.1"
@@ -4471,6 +5073,34 @@ __metadata:
languageName: node
linkType: hard
+"tinybench@npm:^2.8.0":
+ version: 2.8.0
+ resolution: "tinybench@npm:2.8.0"
+ checksum: 10c0/5a9a642351fa3e4955e0cbf38f5674be5f3ba6730fd872fd23a5c953ad6c914234d5aba6ea41ef88820180a81829ceece5bd8d3967c490c5171bca1141c2f24d
+ languageName: node
+ linkType: hard
+
+"tinypool@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "tinypool@npm:1.0.0"
+ checksum: 10c0/71b20b9c54366393831c286a0772380c20f8cad9546d724c484edb47aea3228f274c58e98cf51d28c40869b39f5273209ef3ea94a9d2a23f8b292f4731cd3e4e
+ languageName: node
+ linkType: hard
+
+"tinyrainbow@npm:^1.2.0":
+ version: 1.2.0
+ resolution: "tinyrainbow@npm:1.2.0"
+ checksum: 10c0/7f78a4b997e5ba0f5ecb75e7ed786f30bab9063716e7dff24dd84013fb338802e43d176cb21ed12480561f5649a82184cf31efb296601a29d38145b1cdb4c192
+ languageName: node
+ linkType: hard
+
+"tinyspy@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "tinyspy@npm:3.0.0"
+ checksum: 10c0/eb0dec264aa5370efd3d29743825eb115ed7f1ef8a72a431e9a75d5c9e7d67e99d04b0d61d86b8cd70c79ec27863f241ad0317bc453f78762e0cbd76d2c332d0
+ languageName: node
+ linkType: hard
+
"to-fast-properties@npm:^2.0.0":
version: 2.0.0
resolution: "to-fast-properties@npm:2.0.0"
@@ -4649,9 +5279,64 @@ __metadata:
languageName: node
linkType: hard
-"vite@npm:^5.1.7":
- version: 5.2.8
- resolution: "vite@npm:5.2.8"
+"vite-node@npm:2.0.2":
+ version: 2.0.2
+ resolution: "vite-node@npm:2.0.2"
+ dependencies:
+ cac: "npm:^6.7.14"
+ debug: "npm:^4.3.5"
+ pathe: "npm:^1.1.2"
+ tinyrainbow: "npm:^1.2.0"
+ vite: "npm:^5.0.0"
+ bin:
+ vite-node: vite-node.mjs
+ checksum: 10c0/cf6fa40844134bd11d149ada94313ee2a47756ba7a98e143698b37c73b72c5850a9aaa799bd3076c09520e1be17079665846c763d3696b59359b88be77f299b6
+ languageName: node
+ linkType: hard
+
+"vite@npm:^5.0.0":
+ version: 5.3.3
+ resolution: "vite@npm:5.3.3"
+ dependencies:
+ esbuild: "npm:^0.21.3"
+ fsevents: "npm:~2.3.3"
+ postcss: "npm:^8.4.39"
+ rollup: "npm:^4.13.0"
+ peerDependencies:
+ "@types/node": ^18.0.0 || >=20.0.0
+ less: "*"
+ lightningcss: ^1.21.0
+ sass: "*"
+ stylus: "*"
+ sugarss: "*"
+ terser: ^5.4.0
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ bin:
+ vite: bin/vite.js
+ checksum: 10c0/a796872e1d11875d994615cd00da185c80eeb7753034d35c096050bf3c269c02004070cf623c5fe2a4a90ea2f12488e6f9d13933ec810f117f1b931e1b5e3385
+ languageName: node
+ linkType: hard
+
+"vite@npm:^5.1.4":
+ version: 5.2.13
+ resolution: "vite@npm:5.2.13"
dependencies:
esbuild: "npm:^0.20.1"
fsevents: "npm:~2.3.3"
@@ -4685,7 +5370,56 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
- checksum: 10c0/b5717bb00c2570c08ff6d8ed917655e79184efcafa9dd62d52eea19c5d6dfc5a708ec3de9ebc670a7165fc5d401c2bdf1563bb39e2748d8e51e1593d286a9a13
+ checksum: 10c0/f7a99da71884e69cc581dcfb43d73c8d56d73b9668d6980131603c544d6323c6003a20f376531dc0cfcf36bf5009bc465f89e6c5f8bd9d22868987aba4e4af1b
+ languageName: node
+ linkType: hard
+
+"vitest@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "vitest@npm:2.0.2"
+ dependencies:
+ "@ampproject/remapping": "npm:^2.3.0"
+ "@vitest/expect": "npm:2.0.2"
+ "@vitest/pretty-format": "npm:^2.0.2"
+ "@vitest/runner": "npm:2.0.2"
+ "@vitest/snapshot": "npm:2.0.2"
+ "@vitest/spy": "npm:2.0.2"
+ "@vitest/utils": "npm:2.0.2"
+ chai: "npm:^5.1.1"
+ debug: "npm:^4.3.5"
+ execa: "npm:^8.0.1"
+ magic-string: "npm:^0.30.10"
+ pathe: "npm:^1.1.2"
+ std-env: "npm:^3.7.0"
+ tinybench: "npm:^2.8.0"
+ tinypool: "npm:^1.0.0"
+ tinyrainbow: "npm:^1.2.0"
+ vite: "npm:^5.0.0"
+ vite-node: "npm:2.0.2"
+ why-is-node-running: "npm:^2.2.2"
+ peerDependencies:
+ "@edge-runtime/vm": "*"
+ "@types/node": ^18.0.0 || >=20.0.0
+ "@vitest/browser": 2.0.2
+ "@vitest/ui": 2.0.2
+ happy-dom: "*"
+ jsdom: "*"
+ peerDependenciesMeta:
+ "@edge-runtime/vm":
+ optional: true
+ "@types/node":
+ optional: true
+ "@vitest/browser":
+ optional: true
+ "@vitest/ui":
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+ bin:
+ vitest: vitest.mjs
+ checksum: 10c0/4ef4d8d5a32ee91f34715b8ae1895062df9ca36be5a88ff916ee4bb2a5e01dfa3f105031f7a5939c98f6401b3a6b2bfd1de6caab0ac84cca0e2df364a19c3526
languageName: node
linkType: hard
@@ -4776,6 +5510,18 @@ __metadata:
languageName: node
linkType: hard
+"why-is-node-running@npm:^2.2.2":
+ version: 2.3.0
+ resolution: "why-is-node-running@npm:2.3.0"
+ dependencies:
+ siginfo: "npm:^2.0.0"
+ stackback: "npm:0.0.2"
+ bin:
+ why-is-node-running: cli.js
+ checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054
+ languageName: node
+ linkType: hard
+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version: 7.0.0
resolution: "wrap-ansi@npm:7.0.0"
@@ -4825,3 +5571,10 @@ __metadata:
checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f
languageName: node
linkType: hard
+
+"zod@npm:^3.23.8":
+ version: 3.23.8
+ resolution: "zod@npm:3.23.8"
+ checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69
+ languageName: node
+ linkType: hard
diff --git a/go.mod b/go.mod
index 60355fe5..73611c3f 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/fclairamb/go-log v0.4.1
github.com/free5gc/chf v1.0.1
- github.com/free5gc/openapi v1.0.8
+ github.com/free5gc/openapi v1.0.9-0.20240730084323-449098e08462
github.com/free5gc/util v1.0.6
github.com/gin-contrib/cors v1.4.0
github.com/gin-gonic/gin v1.9.1
@@ -41,6 +41,7 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
+ github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
@@ -62,7 +63,6 @@ require (
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/sftp v1.13.5 // indirect
- github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
@@ -90,7 +90,7 @@ require (
)
require (
- github.com/free5gc/smf v1.2.3
+ github.com/free5gc/smf v1.2.4
google.golang.org/grpc v1.56.3 // indirect
)
@@ -102,6 +102,6 @@ require (
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
- golang.org/x/oauth2 v0.8.0 // indirect
+ golang.org/x/oauth2 v0.8.0
google.golang.org/api v0.122.0 // indirect
)
diff --git a/go.sum b/go.sum
index ea7befca..38593cee 100644
--- a/go.sum
+++ b/go.sum
@@ -154,10 +154,10 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/free5gc/chf v1.0.1 h1:VGPQRZMaV0v4yMq1kcc3ldJWXA76xgCbCT/A/0O8Yt4=
github.com/free5gc/chf v1.0.1/go.mod h1:VSBqz2ryx4LVmNsswnk9q2yCI8XjnRBuwyTjnrWPL1w=
-github.com/free5gc/openapi v1.0.8 h1:QjfQdB6VVA1GRnzOJ7nILzrI7gMiY0lH64JHVW7vF34=
-github.com/free5gc/openapi v1.0.8/go.mod h1:w6y9P/uySczc1d9OJZAEuB2FImR/z60Wg2BekPAVt3M=
-github.com/free5gc/smf v1.2.3 h1:S59mwcnTL0sHExWSwHmrGZNV0ypYo70CjS5zzJHreHg=
-github.com/free5gc/smf v1.2.3/go.mod h1:jzkHW3A+eacBSuMYeUlajqASzFqiRwOITp8AleCwPLE=
+github.com/free5gc/openapi v1.0.9-0.20240730084323-449098e08462 h1:bK9UWqOhoddpAW9RfZRp4DPZNnPfEUeHvo4I86YAuzA=
+github.com/free5gc/openapi v1.0.9-0.20240730084323-449098e08462/go.mod h1:afeuEQ21QCQDxZHnFjCmYrq3gBi+cd/WDNSUbaMcILo=
+github.com/free5gc/smf v1.2.4 h1:tYB/6UKWlUCEzWrg7icM7vxXkGlWKo2WixLtYmf2qW4=
+github.com/free5gc/smf v1.2.4/go.mod h1:ggQ9oJ/m9rfQOjb2piz00O5S6629pHhv8AsE9Xvk5oA=
github.com/free5gc/util v1.0.6 h1:dBt9drcXtYKE/cY5XuQcuffgsYclPIpIArhSeS6M+DQ=
github.com/free5gc/util v1.0.6/go.mod h1:eSGN7POUM8LNTvg/E591XR6447a6/w1jFWGKNZPHcXw=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -209,6 +209,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -262,8 +264,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -369,8 +371,9 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -466,8 +469,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=